@@ -82,11 +82,9 @@ export const DraggableGhost: React.FC<DraggableGhostProps> = ({
8282 const startRef = useRef < XYPosition | null > ( null )
8383 const maxTravelRef = useRef ( 0 )
8484 const pointerTypeRef = useRef < string > ( "mouse" )
85- // Where the cursor grabbed the preview, as a FRACTION of its box; backed out on
86- // drop so that same point stays under the pointer. A pixel offset would not
87- // work: the ghost and the dropped node are drawn at sizes the preview does not
88- // share, so only a fraction lines up at any zoom.
89- const grabFractionRef = useRef < XYPosition > ( { x : 0 , y : 0 } )
85+ // Where the cursor grabbed the preview in the dropped node's flow-space units;
86+ // the same offset positions both the ghost and the committed node.
87+ const grabOffsetRef = useRef < XYPosition > ( { x : 0 , y : 0 } )
9088 // True once a press has turned into a drag, so the trailing click is ignored.
9189 const draggedRef = useRef ( false )
9290
@@ -113,13 +111,19 @@ export const DraggableGhost: React.FC<DraggableGhostProps> = ({
113111 const previewRect = (
114112 previewElement ?? event . currentTarget
115113 ) . getBoundingClientRect ( )
116- const grabX = previewRect . width
117- ? ( event . clientX - previewRect . left ) / previewRect . width
118- : 0
119- const grabY = previewRect . height
120- ? ( event . clientY - previewRect . top ) / previewRect . height
121- : 0
122- grabFractionRef . current = { x : grabX , y : grabY }
114+ // Sidebar previews scale uniformly. Recover the grab point in their
115+ // unscaled painted coordinates from the horizontal scale (the vertical
116+ // extent may include a label band), then map each axis onto the drop size.
117+ const previewScale = previewRect . width / dropElementConfig . width || 1
118+ const grabOffset = {
119+ x :
120+ ( ( event . clientX - previewRect . left ) / previewScale ) *
121+ ( ghostDropWidth / dropElementConfig . width ) ,
122+ y :
123+ ( ( event . clientY - previewRect . top ) / previewScale ) *
124+ ( ghostDropHeight / dropElementConfig . height ) ,
125+ }
126+ grabOffsetRef . current = grabOffset
123127
124128 // Draw the ghost at the on-screen size the node will have at this zoom. The
125129 // drop size is used rather than the palette size so an element that drops
@@ -129,12 +133,13 @@ export const DraggableGhost: React.FC<DraggableGhostProps> = ({
129133 const zoom = getViewport ( ) . zoom
130134 setGhostRender ( { scale : zoom } )
131135
132- const ghostWidth = ghostDropWidth * zoom
133- const ghostHeight = ghostDropHeight * zoom
134- setGhostOffset ( { x : grabX * ghostWidth , y : grabY * ghostHeight } )
136+ setGhostOffset ( {
137+ x : grabOffset . x * zoom ,
138+ y : grabOffset . y * zoom ,
139+ } )
135140 setGhostPosition ( {
136- x : event . clientX - grabX * ghostWidth ,
137- y : event . clientY - grabY * ghostHeight ,
141+ x : event . clientX - grabOffset . x * zoom ,
142+ y : event . clientY - grabOffset . y * zoom ,
138143 } )
139144
140145 setIsDragging ( true )
@@ -195,7 +200,7 @@ export const DraggableGhost: React.FC<DraggableGhostProps> = ({
195200 : DROPS . TAP_SLOP_MOUSE_PX
196201 const placed =
197202 maxTravelRef . current >= slop &&
198- dropRef . current ( event , grabFractionRef . current )
203+ dropRef . current ( event , grabOffsetRef . current )
199204 if ( placed ) suppressTrailingClick ( )
200205 else draggedRef . current = false
201206 }
0 commit comments