(node: ShapeNodeData, ctx: RenderContext)
| 286 | * Render a shape node into an absolutely-positioned HTML element with SVG geometry. |
| 287 | */ |
| 288 | export function renderShape(node: ShapeNodeData, ctx: RenderContext): HTMLElement { |
| 289 | const wrapper = document.createElement('div') |
| 290 | wrapper.style.position = 'absolute' |
| 291 | wrapper.style.left = `${node.position.x}px` |
| 292 | wrapper.style.top = `${node.position.y}px` |
| 293 | wrapper.style.width = `${node.size.w}px` |
| 294 | // Line-like: preset line/connector, or cxnSp (connection shape), or flat extent (one dimension 0) |
| 295 | const presetKey = node.presetGeometry?.toLowerCase() ?? '' |
| 296 | const outlineOnlyPresets = new Set([ |
| 297 | 'arc', |
| 298 | 'leftbracket', |
| 299 | 'rightbracket', |
| 300 | 'leftbrace', |
| 301 | 'rightbrace', |
| 302 | 'bracketpair', |
| 303 | 'bracepair', |
| 304 | ]) |
| 305 | const presetIsLine = |
| 306 | !!presetKey && |
| 307 | (presetKey === 'line' || |
| 308 | presetKey === 'lineinv' || |
| 309 | presetKey.includes('connector') || |
| 310 | outlineOnlyPresets.has(presetKey)) |
| 311 | const isConnectorShape = node.source.localName === 'cxnSp' |
| 312 | const flatExtent = |
| 313 | (node.size.w > 0 && node.size.h === 0) || (node.size.w === 0 && node.size.h > 0) |
| 314 | const isLineLike = presetIsLine || isConnectorShape || flatExtent |
| 315 | const minH = isLineLike && node.size.h === 0 ? 1 : node.size.h |
| 316 | const minW = isLineLike && node.size.w === 0 ? 1 : node.size.w |
| 317 | wrapper.style.height = `${minH}px` |
| 318 | if (node.size.w === 0) wrapper.style.width = `${minW}px` |
| 319 | wrapper.style.overflow = 'visible' |
| 320 | // Apply transforms (rotation + flip) |
| 321 | const transforms: string[] = [] |
| 322 | if (node.rotation !== 0) { |
| 323 | transforms.push(`rotate(${node.rotation}deg)`) |
| 324 | } |
| 325 | if (node.flipH) { |
| 326 | transforms.push('scaleX(-1)') |
| 327 | } |
| 328 | if (node.flipV) { |
| 329 | transforms.push('scaleY(-1)') |
| 330 | } |
| 331 | if (transforms.length > 0) { |
| 332 | wrapper.style.transform = transforms.join(' ') |
| 333 | } |
| 334 | |
| 335 | const w = node.size.w |
| 336 | const h = node.size.h |
| 337 | // For path generation, pass original w/h so preset functions can detect zero-extent |
| 338 | // directions (e.g. line preset draws vertical when w=0, horizontal when h=0). |
| 339 | // For SVG viewport, use minW/minH to guarantee a visible container. |
| 340 | const pathW = w |
| 341 | const pathH = h |
| 342 | |
| 343 | // Style references (needed for path fallback and line resolution) |
| 344 | const styleNode = node.source.child('style') |
| 345 | const lnRef = styleNode.exists() ? styleNode.child('lnRef') : undefined |
no test coverage detected