Drag-reorder works on touch via svelte-dnd-action

HTML5 native drag-and-drop doesn't fire on touchscreens — mobile
users couldn't reorder the list at all. Swapped the manual DnD
wiring (dragstart/dragover/drop) for svelte-dnd-action, which uses
Pointer Events and handles mouse, touch, AND keyboard reorder
uniformly. Linear-quality reorder UX for ~11 KB gzipped.

Replacement details:
  - bun add svelte-dnd-action (0.9.69)
  - Home.svelte: ~70 lines of manual handler code deleted, replaced
    with ~30 lines wiring up `use:dndzone` + `onconsider` +
    `onfinalize`. The midpoint-position math for sort_position is
    unchanged (finalize gives us the new neighbour list directly).
  - ActivityRow.svelte: the drag handle's `onpointerdown` flips a
    parent-owned dragDisabled flag to false — the library then
    takes over. Standard "handle-only drag" recipe; clicks on the
    title/buttons inside the card don't initiate drag because
    dragDisabled stays true everywhere else.
  - dndItems is a buffer copy of `filtered` that the library
    mutates during a drag. An $effect re-syncs it from `filtered`
    between drags (so new activities still float to the top, etc).
  - Shadow item (the library's placeholder while dragging) is
    rendered at 30% opacity so the drop target is visible without
    flashing.

Accessibility wins for free:
  - Keyboard reorder: focus an item, press Space to "pick up",
    arrow keys to move, Space to drop. Screen readers get
    polite-live announcements of each move from the library.
  - Touch reorder works on iOS Safari and Android Chrome.

96 tests still pass; typecheck clean; build ok.
Bundle: 122 KB → 154 KB (gzipped 42 → 53 KB, ~+11 KB).
This commit is contained in:
Ole-Morten Duesund 2026-05-25 16:59:43 +02:00
commit 59e2f95767
4 changed files with 89 additions and 92 deletions

View file

@ -7,6 +7,7 @@
"dependencies": {
"hono": "^4.6.0",
"libsodium-wrappers-sumo": "^0.7.15",
"svelte-dnd-action": "^0.9.69",
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^5.0.0",
@ -219,6 +220,8 @@
"svelte-check": ["svelte-check@4.4.8", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-67adfgBox5eNSNIvIIwgFizKGdcRrGpiMoNO2obHcYuLz7iTa8Xgm/NGU3ntMFnNm8K1grFOIG6HhMLX/vcN8w=="],
"svelte-dnd-action": ["svelte-dnd-action@0.9.69", "", { "peerDependencies": { "svelte": ">=3.23.0 || ^5.0.0-next.0" } }, "sha512-NAmSOH7htJoYraTQvr+q5whlIuVoq88vEuHr4NcFgscDRUxfWPPxgie2OoxepBCQCikrXZV4pqV86aun60wVyw=="],
"tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],