Major update for v1 and tests

This commit is contained in:
khalid@traclabs.com
2026-05-23 03:28:58 -05:00
parent 628a6765f4
commit b1fb6fa3aa
1748 changed files with 27723 additions and 1854 deletions

60
src/utils/konvaCoords.js Normal file
View File

@@ -0,0 +1,60 @@
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,
};
}