Skip to content

Commit 72b16db

Browse files
authored
Merge pull request #7 from u8array/fix/transformer-stale-after-resize
Fix/transformer stale after resize
2 parents 39f39fb + 89fcacb commit 72b16db

2 files changed

Lines changed: 70 additions & 9 deletions

File tree

‎src/components/Canvas/BarcodeObject.tsx‎

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, { useCallback, useRef } from "react";
22
import bwipjs from "bwip-js/browser";
33
import { Image as KImage, Group, Rect, Text } from "react-konva";
44
import type Konva from "konva";
@@ -43,6 +43,20 @@ export function BarcodeObject({
4343
onChange,
4444
snap,
4545
}: Props) {
46+
const groupRef = useRef<Konva.Group>(null);
47+
const textRef = useRef<Konva.Text>(null);
48+
49+
// Exclude the HRI text from the parent Group's getClientRect. This anchors
50+
// the resize at the bar top (logmars: was anchoring at text top above bars)
51+
// and keeps the Transformer's bbox tight around the bars, eliminating the
52+
// (h + textArea)*sy vs h*sy + textArea discrepancy during drag.
53+
const setTextRef = useCallback((node: Konva.Text | null) => {
54+
textRef.current = node;
55+
if (node) {
56+
node.getSelfRect = () => ({ x: 0, y: 0, width: 0, height: 0 });
57+
}
58+
}, []);
59+
4660
const opts = buildBwipOptions(obj, scale, dpmm);
4761
let barcodeCanvas: HTMLCanvasElement | null = null;
4862
let errorMsg: string | null = null;
@@ -413,6 +427,7 @@ export function BarcodeObject({
413427
imageSmoothingEnabled={false}
414428
stroke={isSelected ? "#6366f1" : undefined}
415429
strokeWidth={isSelected ? 2 : 0}
430+
strokeScaleEnabled={false}
416431
/>
417432
{textNodes}
418433
</Group>
@@ -445,19 +460,43 @@ export function BarcodeObject({
445460
const aboveGap = isTextAbove
446461
? Math.max(dotsToPx(LOGMARS_TEXT_ABOVE_GAP_DOTS, scale, dpmm), 3)
447462
: textGap;
448-
const txtY = isTextAbove ? -(textFontSize + aboveGap) : Math.max(h, 1) + textGap;
449-
const clipY = isTextAbove ? -(textFontSize + aboveGap) : 0;
450-
const clipHeight = Math.max(h, 1) + textFontSize + aboveGap;
463+
// Local y for the HRI text. The /sy form keeps a constant *visual* offset
464+
// when the group is being scaled (sy = 1 at rest, ≠ 1 during a drag).
465+
const textLocalY = (sy: number) =>
466+
isTextAbove
467+
? -(textFontSize + aboveGap) / sy
468+
: Math.max(h, 1) + textGap / sy;
469+
const txtY = textLocalY(1);
470+
471+
// Counter-scale the text so it stays at constant pixel size while the
472+
// bars stretch with the parent group's scaleY during a resize drag.
473+
const handleTransform = () => {
474+
const grp = groupRef.current;
475+
const txt = textRef.current;
476+
if (!grp || !txt) return;
477+
const sy = grp.scaleY();
478+
if (sy <= 0) return;
479+
txt.scaleY(1 / sy);
480+
txt.y(textLocalY(sy));
481+
};
482+
483+
// react-konva does not track imperatively-set scaleY/y. Reset both here
484+
// so the next drag starts clean. For logmars the JSX y is constant, so
485+
// without an explicit reset react-konva would not re-apply it on the
486+
// post-commit render and the text would stay at its last drag-time y.
487+
const handleTransformEnd = () => {
488+
const txt = textRef.current;
489+
if (!txt) return;
490+
txt.scaleY(1);
491+
txt.y(txtY);
492+
};
451493

452494
return (
453495
<Group
496+
ref={groupRef}
454497
id={obj.id}
455498
x={x}
456499
y={y}
457-
clipX={0}
458-
clipY={clipY}
459-
clipWidth={Math.max(w, 1)}
460-
clipHeight={clipHeight}
461500
draggable
462501
onClick={(e) =>
463502
onSelect(e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey)
@@ -467,6 +506,8 @@ export function BarcodeObject({
467506
e.target.position(snapPos(e.target.x(), e.target.y()))
468507
}
469508
onDragEnd={handleDragEnd}
509+
onTransform={handleTransform}
510+
onTransformEnd={handleTransformEnd}
470511
>
471512
<KImage
472513
x={0}
@@ -477,8 +518,10 @@ export function BarcodeObject({
477518
imageSmoothingEnabled={false}
478519
stroke={isSelected ? "#6366f1" : undefined}
479520
strokeWidth={isSelected ? 2 : 0}
521+
strokeScaleEnabled={false}
480522
/>
481523
<Text
524+
ref={setTextRef}
482525
x={0}
483526
y={txtY}
484527
width={Math.max(w, 1)}
@@ -505,6 +548,7 @@ export function BarcodeObject({
505548
imageSmoothingEnabled={false}
506549
stroke={isSelected ? "#6366f1" : undefined}
507550
strokeWidth={isSelected ? 2 : 0}
551+
strokeScaleEnabled={false}
508552
draggable
509553
onClick={(e) =>
510554
onSelect(e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey)

‎src/components/Canvas/hooks/useKonvaTransformer.ts‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,18 @@ export function useKonvaTransformer({
147147
.map((id) => objects.find((o) => o.id === id)?.type ?? "")
148148
.join(",");
149149

150+
// Signature of the selected objects' size-relevant props. Changes after
151+
// commitTransform → forces the transformer to re-measure the attached node
152+
// so its bounding box matches the new rendered size. Position is excluded:
153+
// moves don't change bbox dimensions, and Konva tracks the node's position
154+
// automatically.
155+
const selectedSignature = selectedIds
156+
.map((id) => {
157+
const o = objects.find((obj) => obj.id === id);
158+
return o ? `${id}:${JSON.stringify(o.props)}` : id;
159+
})
160+
.join("|");
161+
150162
useEffect(() => {
151163
if (!transformerRef.current || !stageRef.current) return;
152164
if (selectedIds.length === 0) {
@@ -167,10 +179,15 @@ export function useKonvaTransformer({
167179
.filter((n): n is Konva.Node => n != null);
168180
transformerRef.current.nodes(nodes);
169181
}
182+
// Force a re-measure: after commitTransform the node's getClientRect has
183+
// changed but the transformer caches its bounds from the last interaction.
184+
transformerRef.current.forceUpdate();
170185
// selectedTypesKey encodes the type of every selected object — sufficient to
171186
// detect the line/non-line distinction that governs transformer attachment.
187+
// selectedSignature triggers a re-measure when an object's size or position
188+
// changes (e.g. after commitTransform finishes a resize).
172189
// eslint-disable-next-line react-hooks/exhaustive-deps
173-
}, [selectedIds, selectedTypesKey, stageRef, transformerRef]);
190+
}, [selectedIds, selectedTypesKey, selectedSignature, stageRef, transformerRef]);
174191

175192
const resizeEnabled = selectedIds.length <= 1;
176193
const enabledAnchors: string[] | undefined =

0 commit comments

Comments
 (0)