@@ -2,7 +2,12 @@ import React from "react";
22import { Transformer as KonvaTransformer } from "react-konva" ;
33import type Konva from "konva" ;
44import type { BezierPoint } from "../types" ;
5- import { applyTransformationToPoints , resetTransformState } from "../utils/transformUtils" ;
5+ import {
6+ applyTransformationToPoints ,
7+ resetTransformState ,
8+ applyTransformationToControlPoints ,
9+ updateOriginalPositions ,
10+ } from "../utils/transformUtils" ;
611
712interface VectorTransformerProps {
813 selectedPoints : Set < number > ;
@@ -57,8 +62,24 @@ export const VectorTransformer: React.FC<VectorTransformerProps> = ({
5762 controlPoint1 ?: { x : number ; y : number } ;
5863 controlPoint2 ?: { x : number ; y : number } ;
5964 } ;
65+ initialRotation ?: number ;
6066 } > ( { } ) ;
6167
68+ // RAF for smooth control point updates
69+ const rafIdRef = React . useRef < number | null > ( null ) ;
70+
71+ // Track if this is the first transformation tick to avoid control point jumping
72+ const isFirstTransformTickRef = React . useRef < boolean > ( true ) ;
73+
74+ // Cleanup RAF on unmount
75+ React . useEffect ( ( ) => {
76+ return ( ) => {
77+ if ( rafIdRef . current ) {
78+ cancelAnimationFrame ( rafIdRef . current ) ;
79+ }
80+ } ;
81+ } , [ ] ) ;
82+
6283 if ( selectedPoints . size <= 1 || initialPoints . length === 0 ) return null ;
6384
6485 // Calculate the bounding box of selected points for the drag area
@@ -135,13 +156,40 @@ export const VectorTransformer: React.FC<VectorTransformerProps> = ({
135156 transformer ,
136157 initialPoints ,
137158 proxyRefs ,
138- true ,
159+ false , // Don't update control points here
139160 originalPositionsRef . current ,
140161 transformerCenter ,
141162 constrainToBounds ,
142163 bounds ,
143164 ) ;
144- onPointsChange ?.( newPoints ) ;
165+
166+ // Skip control point transformations on the first tick to avoid jumping
167+ if ( isFirstTransformTickRef . current ) {
168+ isFirstTransformTickRef . current = false ;
169+ onPointsChange ?.( newPoints ) ;
170+ } else {
171+ // Apply transformation to control points using RAF
172+ if ( rafIdRef . current ) {
173+ cancelAnimationFrame ( rafIdRef . current ) ;
174+ }
175+ rafIdRef . current = requestAnimationFrame ( ( ) => {
176+ // Check if this is actually a rotation operation (not just scaling)
177+ const isActualRotation = Math . abs ( transformer . rotation ( ) ) > 1.0 ;
178+
179+ // Apply transformation to control points using original positions as base
180+ const updatedPoints = applyTransformationToControlPoints (
181+ newPoints ,
182+ originalPositionsRef . current ,
183+ transformer . rotation ( ) ,
184+ transformer . scaleX ( ) ,
185+ transformer . scaleY ( ) ,
186+ transformerCenter . x ,
187+ transformerCenter . y ,
188+ isActualRotation , // Only apply rotation logic if there's actual rotation
189+ ) ;
190+ onPointsChange ?.( updatedPoints ) ;
191+ } ) ;
192+ }
145193 } catch ( error ) {
146194 console . warn ( "Transform error:" , error ) ;
147195 }
@@ -151,19 +199,6 @@ export const VectorTransformer: React.FC<VectorTransformerProps> = ({
151199 // Notify that transformation has started
152200 onTransformationStart ?.( ) ;
153201
154- // Store the initial state when transformation starts
155- const transformer = transformerRef . current ;
156- if ( transformer ) {
157- const initialState = {
158- rotation : transformer . rotation ( ) ,
159- scaleX : transformer . scaleX ( ) ,
160- scaleY : transformer . scaleY ( ) ,
161- centerX : transformer . x ( ) + transformer . width ( ) / 2 ,
162- centerY : transformer . y ( ) + transformer . height ( ) / 2 ,
163- } ;
164- transformerStateRef . current = initialState ;
165- }
166-
167202 // Store original positions of selected points
168203 originalPositionsRef . current = { } ;
169204 Array . from ( selectedPoints ) . forEach ( ( index ) => {
@@ -178,8 +213,14 @@ export const VectorTransformer: React.FC<VectorTransformerProps> = ({
178213 }
179214 } ) ;
180215
216+ // Store initial rotation
217+ originalPositionsRef . current . initialRotation = transformerRef . current ?. rotation ( ) || 0 ;
218+
181219 // Reset the first transform flag to ensure proper rotation tracking
182220 resetTransformState ( ) ;
221+
222+ // Reset the first transform tick flag
223+ isFirstTransformTickRef . current = true ;
183224 } }
184225 onDragStart = { ( _e : any ) => {
185226 // Store original positions when dragging starts (for pure drag operations)
@@ -195,6 +236,12 @@ export const VectorTransformer: React.FC<VectorTransformerProps> = ({
195236 } ;
196237 }
197238 } ) ;
239+
240+ // Store initial rotation
241+ originalPositionsRef . current . initialRotation = transformerRef . current ?. rotation ( ) || 0 ;
242+
243+ // Reset the first transform tick flag
244+ isFirstTransformTickRef . current = true ;
198245 } }
199246 onDragMove = { ( _e : any ) => {
200247 // Apply drag movement to real points in real-time
@@ -209,13 +256,31 @@ export const VectorTransformer: React.FC<VectorTransformerProps> = ({
209256 transformer ,
210257 initialPoints ,
211258 proxyRefs ,
212- true ,
259+ false , // Don't update control points here
213260 originalPositionsRef . current ,
214261 transformerCenter ,
215262 constrainToBounds ,
216263 bounds ,
217264 ) ;
218- onPointsChange ?.( newPoints ) ;
265+
266+ // Apply transformation to control points using RAF
267+ if ( rafIdRef . current ) {
268+ cancelAnimationFrame ( rafIdRef . current ) ;
269+ }
270+ rafIdRef . current = requestAnimationFrame ( ( ) => {
271+ // Apply transformation to control points using original positions as base
272+ const updatedPoints = applyTransformationToControlPoints (
273+ newPoints ,
274+ originalPositionsRef . current ,
275+ transformer . rotation ( ) ,
276+ transformer . scaleX ( ) ,
277+ transformer . scaleY ( ) ,
278+ transformerCenter . x ,
279+ transformerCenter . y ,
280+ false , // isRotation = false for onDragMove (translation only)
281+ ) ;
282+ onPointsChange ?.( updatedPoints ) ;
283+ } ) ;
219284 } catch ( error ) {
220285 console . warn ( "Drag move error:" , error ) ;
221286 }
@@ -244,7 +309,23 @@ export const VectorTransformer: React.FC<VectorTransformerProps> = ({
244309 constrainToBounds ,
245310 bounds ,
246311 ) ;
247- onPointsChange ?.( newPoints ) ;
312+
313+ // Apply control point transformations
314+ const updatedPoints = applyTransformationToControlPoints (
315+ newPoints ,
316+ originalPositionsRef . current ,
317+ transformer . rotation ( ) ,
318+ transformer . scaleX ( ) ,
319+ transformer . scaleY ( ) ,
320+ transformerCenter . x ,
321+ transformerCenter . y ,
322+ false , // isRotation = false for drag operations
323+ ) ;
324+
325+ onPointsChange ?.( updatedPoints ) ;
326+
327+ // Update original positions with the final transformed positions
328+ updateOriginalPositions ( updatedPoints , originalPositionsRef . current ) ;
248329
249330 // Store the transformer state for future updates
250331 onTransformStateChange ?.( {
@@ -284,7 +365,24 @@ export const VectorTransformer: React.FC<VectorTransformerProps> = ({
284365 constrainToBounds ,
285366 bounds ,
286367 ) ;
287- onPointsChange ?.( newPoints ) ;
368+ // Apply control point transformations
369+ const isActualRotation = Math . abs ( transformer . rotation ( ) ) > 1.0 ;
370+ const updatedPoints = applyTransformationToControlPoints (
371+ newPoints ,
372+ originalPositionsRef . current ,
373+ transformer . rotation ( ) ,
374+ transformer . scaleX ( ) ,
375+ transformer . scaleY ( ) ,
376+ transformerCenter . x ,
377+ transformerCenter . y ,
378+ isActualRotation ,
379+ ) ;
380+
381+ onPointsChange ?.( updatedPoints ) ;
382+
383+ // Update original positions with the final transformed positions
384+ // This ensures that subsequent transformations use the current state as the base
385+ updateOriginalPositions ( updatedPoints , originalPositionsRef . current ) ;
288386
289387 // Store the transformer state for future updates
290388 onTransformStateChange ?.( {
0 commit comments