fix(ui): recentre the board on a hint taken while already zoomed in
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 48s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 57s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 48s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 57s
The board only scrolled to the hint's word when the hint also toggled zoom (the zoom-out case): the recenter effect tracked `zoomed` and read `focus` untracked, so a hint taken while ALREADY zoomed in — no zoom change — never recentred and the board stayed parked where the player was looking. Add a `recenter` nonce the hint bumps; the board's scroll effect tracks it and, when it fires without a zoom toggle, pans straight to `focus` (the board width is stable, so the width-driven zoom tween has nothing to ride). Placing a 2nd+ tile or hovering a dragged tile still set `focus` without the nonce, so the board never jumps on those — the original no-jump intent is preserved. e2e: zoom into the corner, then hint (the mock plays at the centre) — the board pans toward the centre. Verified RED without the fix (both engines), GREEN with.
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
lines,
|
||||
locale,
|
||||
focus,
|
||||
recenter,
|
||||
dropTarget,
|
||||
oncell,
|
||||
ontogglezoom,
|
||||
@@ -39,6 +40,9 @@
|
||||
lines: boolean;
|
||||
locale: Locale;
|
||||
focus: { row: number; col: number } | null;
|
||||
/** A monotonic nonce the parent bumps to request a scroll to `focus` even when the zoom state
|
||||
* is unchanged — the hint recentring an already-zoomed board on the word it played. */
|
||||
recenter: number;
|
||||
/** The cell a dragged tile is currently aimed at, highlighted as a drop target. */
|
||||
dropTarget: { row: number; col: number } | null;
|
||||
oncell: (row: number, col: number) => void;
|
||||
@@ -60,10 +64,17 @@
|
||||
// the board's real width grows (zoom-in) or shrinks (zoom-out), so it magnifies evenly
|
||||
// from A to B in one motion instead of chasing a per-frame target that the scroll bounds
|
||||
// clamp — which made the board lurch one way and then snap back near the edges/centre.
|
||||
// It runs only on a zoom toggle (`zoomed`); changing `focus` while already zoomed does
|
||||
// not recentre, so placing a 2nd+ tile or hovering a dragged tile never jumps the board.
|
||||
// It tracks the zoom toggle (`zoomed`) and the `recenter` nonce; `focus` stays untracked,
|
||||
// so placing a 2nd+ tile or hovering a dragged tile never jumps the board — only a zoom
|
||||
// toggle or an explicit recenter request (the hint) moves it. A recenter with no zoom change
|
||||
// pans straight to `focus`, since the board width is stable and the width-driven tween below
|
||||
// has nothing to ride (the case the owner hit: a hint taken while already zoomed in).
|
||||
// The previous zoom state, to tell a zoom toggle from a recenter request. The board always mounts
|
||||
// zoomed-out, so it starts false and is updated to the live zoom on each effect run.
|
||||
let prevZoom = false;
|
||||
$effect(() => {
|
||||
const on = zoomed;
|
||||
void recenter; // re-run on an explicit recenter request even when the zoom state is unchanged
|
||||
const vp = viewport;
|
||||
if (!vp) return;
|
||||
const f = untrack(() => focus);
|
||||
@@ -72,8 +83,6 @@
|
||||
if (clientW === 0) return;
|
||||
const fitW = clientW; // board width at scale 1 (fills the viewport)
|
||||
const fullW = clientW * Z; // board width at full zoom
|
||||
const startSL = vp.scrollLeft;
|
||||
const startST = vp.scrollTop;
|
||||
let finalSL = 0;
|
||||
let finalST = 0;
|
||||
if (on && f) {
|
||||
@@ -81,6 +90,19 @@
|
||||
finalSL = Math.max(0, Math.min((f.col + 0.5) * cell - clientW / 2, fullW - clientW));
|
||||
finalST = Math.max(0, Math.min((f.row + 0.5) * cell - clientH / 2, fullW - clientH));
|
||||
}
|
||||
const toggled = on !== prevZoom;
|
||||
prevZoom = on;
|
||||
if (!toggled) {
|
||||
// No zoom change: an explicit recenter (the hint) pans straight to the focused word; a stale
|
||||
// run with nothing focused leaves the scroll alone.
|
||||
if (on && f) {
|
||||
vp.scrollLeft = finalSL;
|
||||
vp.scrollTop = finalST;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const startSL = vp.scrollLeft;
|
||||
const startST = vp.scrollTop;
|
||||
const fromW = on ? fitW : fullW; // board width when this transition begins
|
||||
const toW = on ? fullW : fitW;
|
||||
let raf = requestAnimationFrame(function tick() {
|
||||
|
||||
@@ -47,6 +47,10 @@
|
||||
let zoomed = $state(false);
|
||||
let selected = $state<number | null>(null);
|
||||
let focus = $state<{ row: number; col: number } | null>(null);
|
||||
// Bumped to ask the board to scroll to `focus` even when the zoom state does not change — the
|
||||
// hint recentring an already-zoomed board on the word it just played (a plain focus change is
|
||||
// deliberately ignored by the board so placing/dragging tiles never jumps it).
|
||||
let recenter = $state(0);
|
||||
// A stable id per rack slot, permuted together with the letters on shuffle, so the rack
|
||||
// tiles fly to their new positions (Rack's hop animation) instead of relabelling in place.
|
||||
let rackIds = $state<number[]>([]);
|
||||
@@ -629,6 +633,10 @@
|
||||
row: Math.round((Math.min(...rows) + Math.max(...rows)) / 2),
|
||||
col: Math.round((Math.min(...cols) + Math.max(...cols)) / 2),
|
||||
};
|
||||
// Ask the board to scroll to the hint word. A zoom-out board zooms in (and centres) on
|
||||
// the next line; this nonce also recentres a board that is already zoomed in, where the
|
||||
// unchanged zoom state would otherwise leave it parked where the player was looking.
|
||||
recenter++;
|
||||
}
|
||||
if (isCoarse()) zoomed = true;
|
||||
view = { ...view, hintsRemaining: h.hintsRemaining };
|
||||
@@ -929,6 +937,7 @@
|
||||
lines={app.boardLines}
|
||||
locale={app.locale}
|
||||
{focus}
|
||||
{recenter}
|
||||
{dropTarget}
|
||||
oncell={onCell}
|
||||
ontogglezoom={(r, c) => { focus = { row: r, col: c }; if (!gameOver) zoomed = !zoomed; }}
|
||||
|
||||
Reference in New Issue
Block a user