@@ -200,18 +200,85 @@ function handleDragOver(e) {
200200 lastDragPosition = { x : e . clientX , y : e . clientY } ;
201201
202202 const target = e . target . closest ( '.button-item' ) ;
203+ // --- Important: Do nothing if the target is the dragged item itself OR if there is no target ---
203204 if ( ! target || target === draggedItem ) return ;
204205
205- const bounding = target . getBoundingClientRect ( ) ;
206206 const parent = target . parentNode ;
207+ // --- Get ALL child elements BEFORE the DOM change ---
208+ // Convert HTMLCollection to an array for easier handling
209+ const children = Array . from ( parent . children ) ;
210+
211+ // --- FLIP: First - Record the starting positions of ALL elements (except the dragged one) ---
212+ const firstPositions = new Map ( ) ;
213+ children . forEach ( child => {
214+ // Exclude the dragged item itself from animation calculations
215+ if ( child !== draggedItem ) {
216+ firstPositions . set ( child , child . getBoundingClientRect ( ) ) ;
217+ }
218+ } ) ;
219+
220+ // --- Perform the DOM change (your existing code) ---
221+ const bounding = target . getBoundingClientRect ( ) ;
207222 const offsetY = e . clientY - bounding . top ;
208223 const isBefore = offsetY < bounding . height / 2 ;
209224
225+ // Remember the current siblings to avoid inserting the item relative to itself
226+ const currentNextSibling = draggedItem . nextSibling ;
227+ const currentPreviousSibling = draggedItem . previousSibling ;
228+
210229 if ( isBefore ) {
211- parent . insertBefore ( draggedItem , target ) ;
230+ // Insert ONLY if the target is not the current next sibling
231+ if ( target !== currentNextSibling ) {
232+ parent . insertBefore ( draggedItem , target ) ;
233+ }
212234 } else {
213- parent . insertBefore ( draggedItem , target . nextSibling ) ;
235+ // Insert ONLY if the target is not the current previous sibling
236+ if ( target !== currentPreviousSibling ) {
237+ parent . insertBefore ( draggedItem , target . nextSibling ) ;
238+ }
214239 }
240+ // --- END of DOM change ---
241+
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' , ( ) => {
272+ child . style . transition = '' ;
273+ } , { once : true } ) ; // Executes once and removes itself
274+
275+ } else {
276+ // If the element didn't move, ensure it has no animation styles
277+ child . style . transition = '' ;
278+ child . style . transform = '' ;
279+ }
280+ }
281+ } ) ;
215282}
216283
217284function handleDrop ( e ) {
0 commit comments