Files
apparel-designer/src/utils/konvaCoords.js
2026-05-23 03:28:58 -05:00

61 lines
2.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
};
}