61 lines
2.5 KiB
JavaScript
61 lines
2.5 KiB
JavaScript
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,
|
||
};
|
||
}
|