From 49d0d39eeb67f756f93f8c96580ecc13bf069553 Mon Sep 17 00:00:00 2001 From: u8array Date: Tue, 5 May 2026 17:23:42 +0200 Subject: [PATCH 1/8] fix: force transformer to re-measure bounds after resize commit --- .../Canvas/hooks/useKonvaTransformer.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/Canvas/hooks/useKonvaTransformer.ts b/src/components/Canvas/hooks/useKonvaTransformer.ts index b60fcc25..1fb0a826 100644 --- a/src/components/Canvas/hooks/useKonvaTransformer.ts +++ b/src/components/Canvas/hooks/useKonvaTransformer.ts @@ -147,6 +147,16 @@ export function useKonvaTransformer({ .map((id) => objects.find((o) => o.id === id)?.type ?? "") .join(","); + // Signature of the selected objects' size-relevant state (position + props). + // Changes after commitTransform → forces the transformer to re-measure the + // attached node so its bounding box matches the new rendered size. + const selectedSignature = selectedIds + .map((id) => { + const o = objects.find((obj) => obj.id === id); + return o ? `${id}:${o.x}:${o.y}:${JSON.stringify(o.props)}` : id; + }) + .join("|"); + useEffect(() => { if (!transformerRef.current || !stageRef.current) return; if (selectedIds.length === 0) { @@ -167,10 +177,15 @@ export function useKonvaTransformer({ .filter((n): n is Konva.Node => n != null); transformerRef.current.nodes(nodes); } + // Force a re-measure: after commitTransform the node's getClientRect has + // changed but the transformer caches its bounds from the last interaction. + transformerRef.current.forceUpdate(); // selectedTypesKey encodes the type of every selected object — sufficient to // detect the line/non-line distinction that governs transformer attachment. + // selectedSignature triggers a re-measure when an object's size or position + // changes (e.g. after commitTransform finishes a resize). // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedIds, selectedTypesKey, stageRef, transformerRef]); + }, [selectedIds, selectedTypesKey, selectedSignature, stageRef, transformerRef]); const resizeEnabled = selectedIds.length <= 1; const enabledAnchors: string[] | undefined = From 2afaa67b3bce1cf255f16460bf13c83e760e2c81 Mon Sep 17 00:00:00 2001 From: u8array Date: Tue, 5 May 2026 17:39:27 +0200 Subject: [PATCH 2/8] fix: keep barcode HRI text at constant size during resize drag --- src/components/Canvas/BarcodeObject.tsx | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/components/Canvas/BarcodeObject.tsx b/src/components/Canvas/BarcodeObject.tsx index c0a5efbb..e8a67c43 100644 --- a/src/components/Canvas/BarcodeObject.tsx +++ b/src/components/Canvas/BarcodeObject.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useRef } from "react"; import bwipjs from "bwip-js/browser"; import { Image as KImage, Group, Rect, Text } from "react-konva"; import type Konva from "konva"; @@ -43,6 +43,9 @@ export function BarcodeObject({ onChange, snap, }: Props) { + const groupRef = useRef(null); + const textRef = useRef(null); + const opts = buildBwipOptions(obj, scale, dpmm); let barcodeCanvas: HTMLCanvasElement | null = null; let errorMsg: string | null = null; @@ -449,8 +452,25 @@ export function BarcodeObject({ const clipY = isTextAbove ? -(textFontSize + aboveGap) : 0; const clipHeight = Math.max(h, 1) + textFontSize + aboveGap; + // Counter-scale the human-readable text so it stays at constant pixel size + // while bars stretch with the parent group's scaleY during a resize drag. + const handleTransform = () => { + const grp = groupRef.current; + const txt = textRef.current; + if (!grp || !txt) return; + const sy = grp.scaleY(); + if (sy <= 0) return; + txt.scaleY(1 / sy); + txt.y( + isTextAbove + ? -(textFontSize + aboveGap) / sy + : Math.max(h, 1) + textGap / sy, + ); + }; + return ( Date: Tue, 5 May 2026 17:52:59 +0200 Subject: [PATCH 3/8] fix: reset text scaleY on transform end so subsequent drags render correctly --- src/components/Canvas/BarcodeObject.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/Canvas/BarcodeObject.tsx b/src/components/Canvas/BarcodeObject.tsx index e8a67c43..97041dfc 100644 --- a/src/components/Canvas/BarcodeObject.tsx +++ b/src/components/Canvas/BarcodeObject.tsx @@ -468,6 +468,14 @@ export function BarcodeObject({ ); }; + // react-konva does not track imperatively-set scaleY, so we must clear + // it here. Otherwise the next drag inherits the previous inverse scale + // and the text renders distorted. + const handleTransformEnd = () => { + const txt = textRef.current; + if (txt) txt.scaleY(1); + }; + return ( Date: Tue, 5 May 2026 18:08:11 +0200 Subject: [PATCH 4/8] fix: anchor barcode resize at bar top and tighten bbox around bars only --- src/components/Canvas/BarcodeObject.tsx | 28 +++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/components/Canvas/BarcodeObject.tsx b/src/components/Canvas/BarcodeObject.tsx index 97041dfc..3d086ed8 100644 --- a/src/components/Canvas/BarcodeObject.tsx +++ b/src/components/Canvas/BarcodeObject.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from "react"; +import React, { useCallback, useRef } from "react"; import bwipjs from "bwip-js/browser"; import { Image as KImage, Group, Rect, Text } from "react-konva"; import type Konva from "konva"; @@ -46,6 +46,18 @@ export function BarcodeObject({ const groupRef = useRef(null); const textRef = useRef(null); + // Exclude the HRI text from the parent Group's getClientRect. This anchors + // the resize at the bar top (logmars: was anchoring at text top above bars) + // and keeps the Transformer's bbox tight around the bars, eliminating the + // (h + textArea)*sy vs h*sy + textArea discrepancy during drag. + const setTextRef = useCallback((node: Konva.Text | null) => { + textRef.current = node; + if (node) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (node as any).getSelfRect = () => ({ x: 0, y: 0, width: 0, height: 0 }); + } + }, []); + const opts = buildBwipOptions(obj, scale, dpmm); let barcodeCanvas: HTMLCanvasElement | null = null; let errorMsg: string | null = null; @@ -468,12 +480,16 @@ export function BarcodeObject({ ); }; - // react-konva does not track imperatively-set scaleY, so we must clear - // it here. Otherwise the next drag inherits the previous inverse scale - // and the text renders distorted. + // react-konva does not track imperatively-set scaleY/y. Reset both here + // so the next drag starts clean. For logmars the JSX y is constant + // (-(textFontSize+aboveGap)), so without an explicit reset react-konva + // would not re-apply it on the post-commit render and the text would + // stay at its last drag-time y. const handleTransformEnd = () => { const txt = textRef.current; - if (txt) txt.scaleY(1); + if (!txt) return; + txt.scaleY(1); + txt.y(txtY); }; return ( @@ -509,7 +525,7 @@ export function BarcodeObject({ strokeWidth={isSelected ? 2 : 0} /> Date: Tue, 5 May 2026 18:10:32 +0200 Subject: [PATCH 5/8] fix: keep barcode selection stroke at constant width during resize --- src/components/Canvas/BarcodeObject.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/Canvas/BarcodeObject.tsx b/src/components/Canvas/BarcodeObject.tsx index 3d086ed8..69f834fb 100644 --- a/src/components/Canvas/BarcodeObject.tsx +++ b/src/components/Canvas/BarcodeObject.tsx @@ -428,6 +428,7 @@ export function BarcodeObject({ imageSmoothingEnabled={false} stroke={isSelected ? "#6366f1" : undefined} strokeWidth={isSelected ? 2 : 0} + strokeScaleEnabled={false} /> {textNodes} @@ -523,6 +524,7 @@ export function BarcodeObject({ imageSmoothingEnabled={false} stroke={isSelected ? "#6366f1" : undefined} strokeWidth={isSelected ? 2 : 0} + strokeScaleEnabled={false} /> onSelect(e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey) From 198a4da8d9b6af85333d758ed2e293e9ae6604d3 Mon Sep 17 00:00:00 2001 From: u8array Date: Tue, 5 May 2026 18:16:03 +0200 Subject: [PATCH 6/8] fix: drop redundant clip on showText group so HRI text is not cut off mid-resize --- src/components/Canvas/BarcodeObject.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/Canvas/BarcodeObject.tsx b/src/components/Canvas/BarcodeObject.tsx index 69f834fb..a25aa69c 100644 --- a/src/components/Canvas/BarcodeObject.tsx +++ b/src/components/Canvas/BarcodeObject.tsx @@ -462,8 +462,6 @@ export function BarcodeObject({ ? Math.max(dotsToPx(LOGMARS_TEXT_ABOVE_GAP_DOTS, scale, dpmm), 3) : textGap; const txtY = isTextAbove ? -(textFontSize + aboveGap) : Math.max(h, 1) + textGap; - const clipY = isTextAbove ? -(textFontSize + aboveGap) : 0; - const clipHeight = Math.max(h, 1) + textFontSize + aboveGap; // Counter-scale the human-readable text so it stays at constant pixel size // while bars stretch with the parent group's scaleY during a resize drag. @@ -499,10 +497,6 @@ export function BarcodeObject({ id={obj.id} x={x} y={y} - clipX={0} - clipY={clipY} - clipWidth={Math.max(w, 1)} - clipHeight={clipHeight} draggable onClick={(e) => onSelect(e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey) From 6621ffd64076153d8528ada9ce94e27da614063b Mon Sep 17 00:00:00 2001 From: u8array Date: Tue, 5 May 2026 18:19:53 +0200 Subject: [PATCH 7/8] refactor: drop unsafe any cast and consolidate HRI text-y formula --- src/components/Canvas/BarcodeObject.tsx | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/Canvas/BarcodeObject.tsx b/src/components/Canvas/BarcodeObject.tsx index a25aa69c..470ff060 100644 --- a/src/components/Canvas/BarcodeObject.tsx +++ b/src/components/Canvas/BarcodeObject.tsx @@ -53,8 +53,7 @@ export function BarcodeObject({ const setTextRef = useCallback((node: Konva.Text | null) => { textRef.current = node; if (node) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (node as any).getSelfRect = () => ({ x: 0, y: 0, width: 0, height: 0 }); + node.getSelfRect = () => ({ x: 0, y: 0, width: 0, height: 0 }); } }, []); @@ -461,10 +460,16 @@ export function BarcodeObject({ const aboveGap = isTextAbove ? Math.max(dotsToPx(LOGMARS_TEXT_ABOVE_GAP_DOTS, scale, dpmm), 3) : textGap; - const txtY = isTextAbove ? -(textFontSize + aboveGap) : Math.max(h, 1) + textGap; + // Local y for the HRI text. The /sy form keeps a constant *visual* offset + // when the group is being scaled (sy = 1 at rest, ≠ 1 during a drag). + const textLocalY = (sy: number) => + isTextAbove + ? -(textFontSize + aboveGap) / sy + : Math.max(h, 1) + textGap / sy; + const txtY = textLocalY(1); - // Counter-scale the human-readable text so it stays at constant pixel size - // while bars stretch with the parent group's scaleY during a resize drag. + // Counter-scale the text so it stays at constant pixel size while the + // bars stretch with the parent group's scaleY during a resize drag. const handleTransform = () => { const grp = groupRef.current; const txt = textRef.current; @@ -472,18 +477,13 @@ export function BarcodeObject({ const sy = grp.scaleY(); if (sy <= 0) return; txt.scaleY(1 / sy); - txt.y( - isTextAbove - ? -(textFontSize + aboveGap) / sy - : Math.max(h, 1) + textGap / sy, - ); + txt.y(textLocalY(sy)); }; // react-konva does not track imperatively-set scaleY/y. Reset both here - // so the next drag starts clean. For logmars the JSX y is constant - // (-(textFontSize+aboveGap)), so without an explicit reset react-konva - // would not re-apply it on the post-commit render and the text would - // stay at its last drag-time y. + // so the next drag starts clean. For logmars the JSX y is constant, so + // without an explicit reset react-konva would not re-apply it on the + // post-commit render and the text would stay at its last drag-time y. const handleTransformEnd = () => { const txt = textRef.current; if (!txt) return; From 89fcacb626c4a69ae393006fb19c1e303965319c Mon Sep 17 00:00:00 2001 From: u8array Date: Tue, 5 May 2026 18:41:56 +0200 Subject: [PATCH 8/8] perf: drop position from transformer re-measure signature --- src/components/Canvas/hooks/useKonvaTransformer.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/Canvas/hooks/useKonvaTransformer.ts b/src/components/Canvas/hooks/useKonvaTransformer.ts index 1fb0a826..e5b9ed91 100644 --- a/src/components/Canvas/hooks/useKonvaTransformer.ts +++ b/src/components/Canvas/hooks/useKonvaTransformer.ts @@ -147,13 +147,15 @@ export function useKonvaTransformer({ .map((id) => objects.find((o) => o.id === id)?.type ?? "") .join(","); - // Signature of the selected objects' size-relevant state (position + props). - // Changes after commitTransform → forces the transformer to re-measure the - // attached node so its bounding box matches the new rendered size. + // Signature of the selected objects' size-relevant props. Changes after + // commitTransform → forces the transformer to re-measure the attached node + // so its bounding box matches the new rendered size. Position is excluded: + // moves don't change bbox dimensions, and Konva tracks the node's position + // automatically. const selectedSignature = selectedIds .map((id) => { const o = objects.find((obj) => obj.id === id); - return o ? `${id}:${o.x}:${o.y}:${JSON.stringify(o.props)}` : id; + return o ? `${id}:${JSON.stringify(o.props)}` : id; }) .join("|");