fix: fluid drop instead of the whole list lurching

Letting go of a card was abrupt and pulled its neighbours with it. Frame
capture showed non-dragged rows moving about 68px after release, still
going 13 frames later.

Two things owned transform on the same elements: manual shift transforms
for the gap, and animate:flip for the reorder. On drop, flip measured a
"before" rect that already had a manual offset in it, then the order
changed and the offsets cleared in the same update, so it animated toward
a position that never existed.

One owner each now. The list reorders as the pointer crosses each
boundary rather than on release, so flip moves the displaced cards on its
own, one swap at a time, and the swaps look right while you're dragging.
Only the lifted card is positioned by hand, with flip off for it so it
stays under the pointer. By release the order is already final, and
nothing moves but that card settling.

After the change, every non-dragged row moves 0px on release and the
dropped card eases 4px into place.

Slot geometry is replaced with offsetTop and offsetHeight, layout values
that ignore transforms, so the target can't feed back into itself and
can't go stale.

Removes src/lib/drag.js and its tests. The rule they covered doesn't exist
in this design any more.
This commit is contained in:
Aculix Technologies
2026-08-16 01:33:51 +05:30
parent 00d1e211fe
commit 19e8ac2f61
4 changed files with 84 additions and 152 deletions
-20
View File
@@ -1,20 +0,0 @@
/**
* How far a row should slide, in pixels, while another row is being dragged
* over the list.
*
* A lifted card vacates exactly its own height plus one gap, so every row it
* passes travels by that same amount — `shift` — and everything else stays
* put. The result is a real gap at the destination, which doubles as the drop
* indicator and is therefore always accurate.
*
* Negative moves a row up the list, positive moves it down.
*/
export function displacement(index, fromIndex, toIndex, shift) {
if (fromIndex === null || toIndex === null) return 0
if (index === fromIndex) return 0
if (fromIndex < toIndex && index > fromIndex && index <= toIndex) return -shift
if (fromIndex > toIndex && index >= toIndex && index < fromIndex) return shift
return 0
}
-69
View File
@@ -1,69 +0,0 @@
import { describe, it, expect } from 'vitest'
import { displacement } from './drag.js'
const SHIFT = 70
describe('displacement', () => {
it('is zero when nothing is being dragged', () => {
expect(displacement(2, null, null, SHIFT)).toBe(0)
})
it('is zero for the dragged row itself', () => {
expect(displacement(1, 1, 3, SHIFT)).toBe(0)
})
it('is zero when the row would not move', () => {
expect(displacement(2, 2, 2, SHIFT)).toBe(0)
})
describe('dragging downward', () => {
// Row 0 heading to slot 2: rows 1 and 2 slide up into the vacated space,
// row 3 stays put.
it('slides passed rows up', () => {
expect(displacement(1, 0, 2, SHIFT)).toBe(-SHIFT)
expect(displacement(2, 0, 2, SHIFT)).toBe(-SHIFT)
})
it('leaves rows beyond the destination alone', () => {
expect(displacement(3, 0, 2, SHIFT)).toBe(0)
})
it('leaves rows above the origin alone', () => {
expect(displacement(0, 1, 3, SHIFT)).toBe(0)
})
it('includes the destination row itself', () => {
expect(displacement(3, 0, 3, SHIFT)).toBe(-SHIFT)
})
})
describe('dragging upward', () => {
// Row 3 heading to slot 1: rows 1 and 2 slide down.
it('slides passed rows down', () => {
expect(displacement(1, 3, 1, SHIFT)).toBe(SHIFT)
expect(displacement(2, 3, 1, SHIFT)).toBe(SHIFT)
})
it('leaves rows above the destination alone', () => {
expect(displacement(0, 3, 1, SHIFT)).toBe(0)
})
it('leaves rows below the origin alone', () => {
expect(displacement(4, 3, 1, SHIFT)).toBe(0)
})
it('includes the destination row itself', () => {
expect(displacement(0, 2, 0, SHIFT)).toBe(SHIFT)
})
})
it('moves exactly one row for a single-step drag', () => {
expect(displacement(1, 0, 1, SHIFT)).toBe(-SHIFT)
expect(displacement(2, 0, 1, SHIFT)).toBe(0)
})
it('displaces every row between the ends of a full-list drag', () => {
const shifts = [0, 1, 2, 3, 4].map(i => displacement(i, 0, 4, SHIFT))
expect(shifts).toEqual([0, -SHIFT, -SHIFT, -SHIFT, -SHIFT])
})
})