@@ -193,7 +193,7 @@ function autoScroll(e) {
193193
194194function handleDragOver ( e ) {
195195 e . preventDefault ( ) ;
196- if ( ! isDragging ) return ;
196+ if ( ! isDragging || ! draggedItem ) return ; // Added check for draggedItem
197197
198198 e . dataTransfer . dropEffect = 'move' ;
199199 autoScroll ( e ) ;
@@ -205,15 +205,18 @@ function handleDragOver(e) {
205205
206206 const parent = target . parentNode ;
207207 // --- Get ALL child elements BEFORE the DOM change ---
208- // Convert HTMLCollection to an array for easier handling
209208 const children = Array . from ( parent . children ) ;
210209
211- // --- FLIP: First - Record the starting positions of ALL elements (except the dragged one) ---
210+ // --- FLIP: First - Record the starting positions ---
212211 const firstPositions = new Map ( ) ;
212+ let firstDraggedRect = null ; // Store draggedItem's initial position
213+
213214 children . forEach ( child => {
214- // Exclude the dragged item itself from animation calculations
215- if ( child !== draggedItem ) {
216- firstPositions . set ( child , child . getBoundingClientRect ( ) ) ;
215+ const rect = child . getBoundingClientRect ( ) ;
216+ if ( child === draggedItem ) {
217+ firstDraggedRect = rect ; // Record initial position of dragged item
218+ } else {
219+ firstPositions . set ( child , rect ) ; // Record for background items
217220 }
218221 } ) ;
219222
@@ -222,63 +225,96 @@ function handleDragOver(e) {
222225 const offsetY = e . clientY - bounding . top ;
223226 const isBefore = offsetY < bounding . height / 2 ;
224227
225- // Remember the current siblings to avoid inserting the item relative to itself
226228 const currentNextSibling = draggedItem . nextSibling ;
227229 const currentPreviousSibling = draggedItem . previousSibling ;
228230
231+ let domChanged = false ; // Flag to track if DOM was actually modified
229232 if ( isBefore ) {
230- // Insert ONLY if the target is not the current next sibling
231233 if ( target !== currentNextSibling ) {
232234 parent . insertBefore ( draggedItem , target ) ;
235+ domChanged = true ;
233236 }
234237 } else {
235- // Insert ONLY if the target is not the current previous sibling
236238 if ( target !== currentPreviousSibling ) {
237- parent . insertBefore ( draggedItem , target . nextSibling ) ;
239+ parent . insertBefore ( draggedItem , target . nextSibling ) ;
240+ domChanged = true ;
238241 }
239242 }
240243 // --- END of DOM change ---
241244
242-
243- // --- FLIP: Last, Invert, Play This is for items that are not dragged, the ones at the background ---
244- children . forEach ( child => {
245- // Again, process only elements that are NOT the dragged item
246- if ( child !== draggedItem && firstPositions . has ( child ) ) {
247- const firstRect = firstPositions . get ( child ) ;
248- // --- Last: Get the new positions AFTER the DOM change ---
249- const lastRect = child . getBoundingClientRect ( ) ;
250-
251- // --- Invert: Calculate the offset and apply the inverse transform ---
252- const deltaX = firstRect . left - lastRect . left ;
253- const deltaY = firstRect . top - lastRect . top ;
254-
255- // If the element actually moved
256- if ( deltaX !== 0 || deltaY !== 0 ) {
257- // Apply the inverse transform WITHOUT animation
258- child . style . transition = 'transform 0s' ;
259- child . style . transform = `translate(${ deltaX } px, ${ deltaY } px)` ;
260-
261- // --- Important: Force the browser to recalculate styles ---
262- // Reading offsetWidth forces the browser to apply the styles immediately
263- child . offsetWidth ;
264-
265- // --- Play: Enable the animation and remove the transform ---
266- child . style . transition = 'transform 200ms ease-in-out' ; // Set your desired duration and easing
267- child . style . transform = '' ; // Animate back to the natural position (transform: none)
268-
269- // --- Cleanup (Recommended): Remove the transition after animation completes ---
270- // so it doesn't interfere with subsequent operations or other styles
271- child . addEventListener ( 'transitionend' , ( ) => {
245+ // --- Only proceed with animations if the DOM actually changed ---
246+ if ( domChanged ) {
247+ // --- FLIP: Last, Invert, Play for BACKGROUND items ---
248+ children . forEach ( child => {
249+ if ( child !== draggedItem && firstPositions . has ( child ) ) {
250+ const firstRect = firstPositions . get ( child ) ;
251+ const lastRect = child . getBoundingClientRect ( ) ; // Last
252+
253+ const deltaX = firstRect . left - lastRect . left ;
254+ const deltaY = firstRect . top - lastRect . top ;
255+
256+ if ( deltaX !== 0 || deltaY !== 0 ) {
257+ child . style . transition = 'transform 0s' ; // Invert (No transition)
258+ child . style . transform = `translate(${ deltaX } px, ${ deltaY } px)` ;
259+ child . offsetWidth ; // Force reflow
260+
261+ child . style . transition = 'transform 200ms ease-in-out' ; // Play
262+ child . style . transform = '' ;
263+
264+ child . addEventListener ( 'transitionend' , ( ) => {
265+ child . style . transition = '' ;
266+ } , { once : true } ) ;
267+ } else {
272268 child . style . transition = '' ;
273- } , { once : true } ) ; // Executes once and removes itself
269+ child . style . transform = '' ;
270+ }
271+ }
272+ } ) ;
273+
274+ // --- FLIP: Last, Invert, Play for DRAGGED item ---
275+ if ( firstDraggedRect ) { // Ensure we recorded the initial position
276+ const lastDraggedRect = draggedItem . getBoundingClientRect ( ) ; // Last
277+
278+ const deltaDraggedX = firstDraggedRect . left - lastDraggedRect . left ;
279+ const deltaDraggedY = firstDraggedRect . top - lastDraggedRect . top ;
280+
281+ // Check if the element's calculated position actually changed
282+ if ( deltaDraggedX !== 0 || deltaDraggedY !== 0 ) {
283+ // Apply inverse transform immediately WITHOUT transition
284+ draggedItem . style . transition = 'transform 0s' ;
285+ draggedItem . style . transform = `translate(${ deltaDraggedX } px, ${ deltaDraggedY } px)` ;
286+
287+ // Force reflow is crucial here
288+ draggedItem . offsetWidth ;
289+
290+ // Play: Apply transition and animate back to natural position (transform: '')
291+ // Use a slightly shorter duration as it's actively moving
292+ draggedItem . style . transition = 'transform 150ms ease-out' ;
293+ draggedItem . style . transform = '' ;
294+
295+ // Cleanup: Remove transition after animation.
296+ // NOTE: This might be problematic due to rapid 'dragover' events.
297+ // A flag or debouncing might be needed for robust cleanup.
298+ // For simplicity here, we'll use 'once', but be aware it might
299+ // get interrupted by the next dragover event.
300+ draggedItem . addEventListener ( 'transitionend' , ( ) => {
301+ // Only remove transition if it hasn't been reset by another dragover
302+ if ( draggedItem . style . transition . includes ( '150ms' ) ) {
303+ draggedItem . style . transition = '' ;
304+ }
305+ } , { once : true } ) ;
274306
275307 } else {
276- // If the element didn't move, ensure it has no animation styles
277- child . style . transition = '' ;
278- child . style . transform = '' ;
308+ // If it didn't move position-wise, ensure no leftover styles
309+ // Check if a transition is currently active from a previous move
310+ // If not, clear styles. If yes, let it finish.
311+ if ( ! draggedItem . style . transition . includes ( '150ms' ) ) {
312+ draggedItem . style . transition = '' ;
313+ draggedItem . style . transform = '' ;
314+ }
279315 }
280316 }
281- } ) ;
317+ } // end if(domChanged)
282318}
283319
284320function handleDrop ( e ) {
0 commit comments