|
11 | 11 | import { showTooltip, hideTooltip } from '$lib/components/Tooltip.svelte'; |
12 | 12 | import { paramInput } from '$lib/actions/paramInput'; |
13 | 13 | import { plotDataStore } from '$lib/plotting/processing/plotDataStore'; |
14 | | - import { NODE, calculatePortPosition, snapTo2G } from '$lib/constants/dimensions'; |
15 | | - import { G } from '$lib/constants/grid'; |
| 14 | + import { NODE, snapTo2G } from '$lib/constants/dimensions'; |
16 | 15 | import PlotPreview from './PlotPreview.svelte'; |
17 | 16 |
|
18 | 17 | interface Props { |
|
136 | 135 |
|
137 | 136 | const maxPortsOnSide = $derived(Math.max(data.inputs.length, data.outputs.length)); |
138 | 137 |
|
139 | | - // Calculate content height for pinned params (each param row ~24px) |
140 | | - const pinnedParamCount = $derived(validPinnedParams().length); |
141 | | - const contentExtraHeight = $derived( |
142 | | - pinnedParamCount > 0 |
143 | | - ? 4 + pinnedParamCount * 24 + 6 // border + rows + padding |
144 | | - : 0 |
145 | | - ); |
146 | | -
|
147 | | - // Node dimensions: always multiples of 2G (20px) for symmetric expansion from center |
148 | | - // Ports require: maxPorts * portSpacing |
| 138 | + // Minimum node dimensions based on port count (grid-aligned to 2G) |
| 139 | + // Content can expand beyond these minimums |
149 | 140 | const minPortDimension = $derived(Math.max(1, maxPortsOnSide) * NODE.portSpacing); |
150 | | - const nodeHeight = $derived( |
| 141 | + const minNodeHeight = $derived( |
151 | 142 | isVertical |
152 | | - ? snapTo2G(Math.max(NODE.baseHeight, NODE.baseHeight + contentExtraHeight)) |
153 | | - : snapTo2G(Math.max(NODE.baseHeight, minPortDimension, NODE.baseHeight + contentExtraHeight)) |
| 143 | + ? snapTo2G(NODE.baseHeight) |
| 144 | + : snapTo2G(Math.max(NODE.baseHeight, minPortDimension)) |
154 | 145 | ); |
155 | | - const nodeWidth = $derived( |
| 146 | + const minNodeWidth = $derived( |
156 | 147 | isVertical |
157 | 148 | ? snapTo2G(Math.max(NODE.baseWidth, minPortDimension)) |
158 | 149 | : snapTo2G(NODE.baseWidth) |
159 | 150 | ); |
160 | 151 |
|
161 | | - // Calculate port position in pixels (grid-aligned) |
162 | | - function getPortPositionPx(index: number, total: number, edgeLength: number): string { |
163 | | - const position = calculatePortPosition(index, total, edgeLength); |
164 | | - return `${position}px`; |
| 152 | + // Calculate port position as offset from center (using calc for CSS) |
| 153 | + // This ensures ports stay grid-aligned regardless of actual node dimensions |
| 154 | + // because the node center is at a grid-aligned position |
| 155 | + function getPortPositionCalc(index: number, total: number): string { |
| 156 | + if (total <= 0 || total === 1) { |
| 157 | + return '50%'; // Single port at center |
| 158 | + } |
| 159 | + // For N ports with spacing S: span = (N-1)*S, offset from center = -span/2 + i*S |
| 160 | + const span = (total - 1) * NODE.portSpacing; |
| 161 | + const offsetFromCenter = -span / 2 + index * NODE.portSpacing; |
| 162 | + if (offsetFromCenter === 0) { |
| 163 | + return '50%'; |
| 164 | + } |
| 165 | + return `calc(50% + ${offsetFromCenter}px)`; |
165 | 166 | } |
166 | 167 |
|
167 | 168 | // Check if this is a Subsystem or Interface node (using shapes utility) |
|
300 | 301 | class:preview-hovered={showPreview} |
301 | 302 | class:subsystem-type={isSubsystemType} |
302 | 303 | data-rotation={rotation} |
303 | | - style="width: {nodeWidth}px; height: {nodeHeight}px; --node-color: {nodeColor};" |
| 304 | + style="min-width: {minNodeWidth}px; min-height: {minNodeHeight}px; --node-color: {nodeColor};" |
304 | 305 | ondblclick={handleDoubleClick} |
305 | 306 | onmouseenter={handleMouseEnter} |
306 | 307 | onmouseleave={handleMouseLeave} |
|
379 | 380 | type="target" |
380 | 381 | position={inputPosition()} |
381 | 382 | id={port.id} |
382 | | - style={isVertical ? `left: ${getPortPositionPx(i, data.inputs.length, nodeWidth)};` : `top: ${getPortPositionPx(i, data.inputs.length, nodeHeight)};`} |
| 383 | + style={isVertical ? `left: ${getPortPositionCalc(i, data.inputs.length)};` : `top: ${getPortPositionCalc(i, data.inputs.length)};`} |
383 | 384 | class="handle handle-input" |
384 | 385 | onmouseenter={(e) => handleInputMouseEnter(e, port)} |
385 | 386 | onmouseleave={() => handleInputMouseLeave(port)} |
|
394 | 395 | type="source" |
395 | 396 | position={outputPosition()} |
396 | 397 | id={port.id} |
397 | | - style={isVertical ? `left: ${getPortPositionPx(i, data.outputs.length, nodeWidth)};` : `top: ${getPortPositionPx(i, data.outputs.length, nodeHeight)};`} |
| 398 | + style={isVertical ? `left: ${getPortPositionCalc(i, data.outputs.length)};` : `top: ${getPortPositionCalc(i, data.outputs.length)};`} |
398 | 399 | class="handle handle-output" |
399 | 400 | onmouseenter={(e) => handleOutputMouseEnter(e, port)} |
400 | 401 | onmouseleave={() => handleOutputMouseLeave(port)} |
|
468 | 469 | z-index: 1000 !important; |
469 | 470 | } |
470 | 471 |
|
471 | | - /* Inner wrapper - uses flexbox to center content vertically */ |
| 472 | + /* Inner wrapper for content */ |
472 | 473 | .node-inner { |
473 | | - flex: 1; |
474 | | - display: flex; |
475 | | - flex-direction: column; |
476 | | - justify-content: center; |
477 | 474 | border-radius: inherit; |
478 | 475 | overflow: hidden; |
479 | 476 | } |
|
0 commit comments