import { MIN_INLINE_EDIT_HEIGHT, MIN_INLINE_EDIT_WIDTH } from '../constants/elements'; /** * Konva node coordinate helpers. * * The inline text editor (S2) needs to know where, in browser-page pixels, * a Konva node is currently rendered. The math involves three layers: * * 1. Konva internal coordinates (the canonical 300×300 design space) * 2. The Stage container's CSS scale (the design-canvas frame is * transform-scaled to fit the available DOM area) * 3. The Stage container's page-level position (relative to the viewport) * * The two TextElement double-tap handlers (`onDblClick`, `onDblTap`) had * identical 12-line copies of this calculation. Centralizing here makes * the conversion testable and prevents drift between mouse and touch paths. */ /** * Compute the page-level rectangle of a Konva node, post-CSS-scale. * * Returns `null` if the node has no stage attached (e.g. unmounted) or if * the stage's internal width is 0 (avoids a NaN cssScale). Callers should * treat null as "skip the overlay this time" — the next render will give * them another chance. * * The returned `width`/`height` apply two floors taken from * `constants/elements`: * - width >= MIN_INLINE_EDIT_WIDTH (so an empty textarea is still hittable) * - height >= MIN_INLINE_EDIT_HEIGHT * * `scale` is included so the caller can scale font-size / padding to match * the on-screen rendition (the inline editor multiplies fontSize by scale * so the textarea text aligns with the canvas text it overlays). * * @param {Konva.Node} node - any Konva node with a parent stage * @returns {{ left, top, width, height, scale } | null} */ export function getNodePageRect(node) { if (!node) return null; const stage = typeof node.getStage === 'function' ? node.getStage() : null; if (!stage) return null; const stageWidth = stage.width(); if (!(stageWidth > 0)) return null; // getClientRect with `relativeTo: stage` returns the node's bounding box // in stage-internal (design) coordinates, accounting for any rotation / // scale on intermediate layers but NOT the Stage's outer CSS transform. const internal = node.getClientRect({ relativeTo: stage }); const stageBox = stage.container().getBoundingClientRect(); const cssScale = stageBox.width / stageWidth; return { left: stageBox.left + internal.x * cssScale, top: stageBox.top + internal.y * cssScale, width: Math.max(MIN_INLINE_EDIT_WIDTH, internal.width * cssScale), height: Math.max(MIN_INLINE_EDIT_HEIGHT, internal.height * cssScale), scale: cssScale, }; }