Stage 17 (#3,#5,#10): hover-hold drag zoom, always-editable profile, drag-back + double-tap recall
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 27s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 56s

- Board drag now auto-zooms toward a cell after holding the tile over it ~1s (#3).
- Profile is inline-editable: drop the Edit/Cancel toggle, form is always shown
  for durable accounts; hint balance stays read-only; re-populate after link/merge (#5).
- A pending tile recalls by double-tap (same cell) or by dragging it back onto the
  rack (unzoomed board); a single tap no longer recalls (#10).
- e2e: lock double-tap recall + single-tap no-op; drop the removed Edit-profile click.
This commit is contained in:
Ilia Denisov
2026-06-06 14:42:09 +02:00
parent 4fd82335db
commit 1bbf0bc654
6 changed files with 171 additions and 90 deletions
+20 -3
View File
@@ -20,6 +20,8 @@
focus,
oncell,
ontogglezoom,
onrecall,
onpenddown,
}: {
board: (BoardCell | null)[][];
premium: Premium[][];
@@ -34,6 +36,10 @@
focus: { row: number; col: number } | null;
oncell: (row: number, col: number) => void;
ontogglezoom: (row: number, col: number) => void;
/** Recall the pending tile at (row, col) — fired on a double-tap of a pending cell. */
onrecall: (row: number, col: number) => void;
/** Pointer-down on a pending cell, to start dragging that tile back to the rack. */
onpenddown: (e: PointerEvent, row: number, col: number) => void;
} = $props();
const Z = 1.85;
@@ -60,16 +66,26 @@
return () => cancelAnimationFrame(raf);
});
// Double-tap toggles zoom (pinch was dropped — it conflicts with native scroll).
// Double-tap a pending tile recalls it; double-tap any other cell toggles zoom toward
// it. A single tap places a selected rack tile (handled by oncell). Drag also auto-zooms
// toward a cell the held tile hovers over (handled in Game), so the one-finger native
// scroll of the zoomed board is never hijacked by a pinch gesture.
let lastTap = 0;
let lastCell = '';
function onTap(row: number, col: number) {
const now = Date.now();
if (now - lastTap < 300) {
ontogglezoom(row, col); // zoom toward the double-tapped cell, not the top-left
const k = key(row, col);
// A double-tap counts only when it lands twice on the same cell, so quick taps across
// different cells don't coalesce into a stray recall/zoom.
if (k === lastCell && now - lastTap < 300) {
lastTap = 0;
lastCell = '';
if (pending.has(k)) onrecall(row, col);
else ontogglezoom(row, col);
return;
}
lastTap = now;
lastCell = k;
oncell(row, col);
}
@@ -95,6 +111,7 @@
data-row={r}
data-col={c}
onclick={() => onTap(r, c)}
onpointerdown={(e) => { if (!!p && !cell) onpenddown(e, r, c); }}
>
{#if letter}
<span class="letter">{letter}</span>