@@ -50,7 +50,7 @@ import { useColorScheme } from "../../hooks/useColorScheme";
5050import { useT } from "../../hooks/useT" ;
5151import { useCanvasPanZoom } from "./hooks/useCanvasPanZoom" ;
5252import { useCanvasLasso } from "./hooks/useCanvasLasso" ;
53- import { useKonvaTransformer } from "./hooks/useKonvaTransformer" ;
53+ import { useKonvaTransformer , MULTI_RESIZE_PROXY_ID } from "./hooks/useKonvaTransformer" ;
5454import { useKonvaDragController } from "./hooks/useKonvaDragController" ;
5555import { useSnapBypassRef } from "./hooks/useSnapBypassRef" ;
5656import { PaginationControl } from "./PaginationControl" ;
@@ -355,6 +355,8 @@ export const LabelCanvas = forwardRef<LabelCanvasHandle, Props>(function LabelCa
355355 if ( isEditableTarget ( e . target as HTMLElement ) ) return ;
356356 // Preview is a frozen snapshot; editing would drift the comparison.
357357 if ( selectPreviewLocksEditor ( useLabelStore . getState ( ) ) ) return ;
358+ // Deleting mid-resize would destroy the nodes under the active gesture.
359+ if ( transformActiveRef . current ) return ;
358360 const { selectedIds : ids } = useLabelStore . getState ( ) ;
359361 if ( ids . length === 0 ) return ;
360362 e . preventDefault ( ) ;
@@ -558,6 +560,7 @@ export const LabelCanvas = forwardRef<LabelCanvasHandle, Props>(function LabelCa
558560 // part (follows the drag) and a static part (locked, stays put) so the chrome
559561 // reflects the real union, not a rigid translate.
560562 const selectionFrameRef = useRef < Konva . Rect > ( null ) ;
563+ const multiResizeProxyRef = useRef < Konva . Rect > ( null ) ;
561564 // Movable group sub-outlines share one drag offset.
562565 const subOutlineGroupRef = useRef < Konva . Group > ( null ) ;
563566 const isMultiFrame = attachableIds . length > 1 ;
@@ -586,7 +589,14 @@ export const LabelCanvas = forwardRef<LabelCanvasHandle, Props>(function LabelCa
586589 // (single drags too), so compute them for any selection.
587590 const hasSelection = visibleSelIds . length > 0 ;
588591 const selectionFrameBase = isMultiFrame ? toFramePx ( selectionUnionDots ( objects , visibleSelIds , frameCtx ) ) : null ;
589- const movableFrameBase = hasSelection ? toFramePx ( selectionUnionDots ( objects , movableSelIds , frameCtx ) ) : null ;
592+ const movableUnionDots = hasSelection ? selectionUnionDots ( objects , movableSelIds , frameCtx ) : null ;
593+ // Needs >1 visible movable leaf (attachableIds counts hidden ones) and no
594+ // locked member; either would break the projected union.
595+ const multiResizeBboxDots =
596+ movableSelIds . length > 1 && staticSelIds . length === 0 && ! previewLocks
597+ ? movableUnionDots
598+ : null ;
599+ const movableFrameBase = toFramePx ( movableUnionDots ) ;
590600 const staticFrameBase = hasSelection ? toFramePx ( selectionUnionDots ( objects , staticSelIds , frameCtx ) ) : null ;
591601 // Preflight runs over the EXPORTABLE leaves (includeInExport), not the visible
592602 // ones, so a hidden-but-exported object is still warned and a visible-but-not-
@@ -720,6 +730,11 @@ export const LabelCanvas = forwardRef<LabelCanvasHandle, Props>(function LabelCa
720730 if ( ! live ) return ;
721731 selectionFrameRef . current ?. position ( { x : live . x , y : live . y } ) ;
722732 selectionFrameRef . current ?. size ( { width : live . width , height : live . height } ) ;
733+ // Proxy follows the drag so the transformer chrome tracks live (the
734+ // controller already forceUpdates the transformer each tick).
735+ if ( multiResizeBboxDots && moved ) {
736+ multiResizeProxyRef . current ?. position ( { x : moved . x , y : moved . y } ) ;
737+ }
723738 // Movable sub-outlines follow the drag (reset to 0 on end).
724739 subOutlineGroupRef . current ?. position ( { x : dx , y : dy } ) ;
725740 // Shared clamp/flip policy; measure the bar lazily since a drag that selects
@@ -895,6 +910,8 @@ export const LabelCanvas = forwardRef<LabelCanvasHandle, Props>(function LabelCa
895910 labelOffsetY,
896911 snap,
897912 updateObject,
913+ updateObjects,
914+ multiResizeBboxDots,
898915 labelRect : transformerSnapLabelRect ,
899916 objectSnapEnabled : smartSnapActive ,
900917 snapBypassRef,
@@ -1583,7 +1600,7 @@ export const LabelCanvas = forwardRef<LabelCanvasHandle, Props>(function LabelCa
15831600
15841601 { /* Multi-select/group frame from model bounds (matches snap borders);
15851602 positioned imperatively in useLayoutEffect + the drag onDelta. */ }
1586- { ! previewLocks && selectionFrameBase && (
1603+ { ! previewLocks && selectionFrameBase && ! multiResizeBboxDots && (
15871604 < Rect
15881605 ref = { selectionFrameRef }
15891606 name = { CAPTURE_CHROME }
@@ -1594,6 +1611,18 @@ export const LabelCanvas = forwardRef<LabelCanvasHandle, Props>(function LabelCa
15941611 />
15951612 ) }
15961613
1614+ { /* Multi-resize proxy; geometry synced by the transformer hook. */ }
1615+ { ! previewLocks && multiResizeBboxDots && (
1616+ < Rect
1617+ ref = { multiResizeProxyRef }
1618+ id = { MULTI_RESIZE_PROXY_ID }
1619+ name = { CAPTURE_CHROME }
1620+ listening = { false }
1621+ fillEnabled = { false }
1622+ strokeEnabled = { false }
1623+ />
1624+ ) }
1625+
15971626 { /* Box per group member: movable parts (blue) drag; locked parts
15981627 (amber, the locked convention) stay put. */ }
15991628 { ! previewLocks && movableGroupRects . length > 0 && (
@@ -1654,6 +1683,7 @@ export const LabelCanvas = forwardRef<LabelCanvasHandle, Props>(function LabelCa
16541683 { ! previewLocks && (
16551684 < Transformer
16561685 ref = { transformerRef }
1686+ borderDash = { isMultiSelection && multiResizeBboxDots ? [ 6 , 4 ] : undefined }
16571687 rotateEnabled = { rotateEnabled }
16581688 resizeEnabled = { resizeEnabled }
16591689 enabledAnchors = { enabledAnchors }
0 commit comments