docs: amend plan to reflect code-review fixes
Tasks 8/10/11 received review fixes during execution; the plan's code blocks are updated to match what shipped: - Task 8: drag controller handles pointercancel + idempotent start. - Task 10: palette pieces are plain spans + svelte-ignore (no focusable-but-not-operable role/tabindex). - Task 11: phantom-load effect keyed on gameId; drag ghost gated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -873,6 +873,12 @@ function makeDrag() {
|
|||||||
let suppressClickOn: Square | null = null;
|
let suppressClickOn: Square | null = null;
|
||||||
const THRESHOLD = 6;
|
const THRESHOLD = 6;
|
||||||
|
|
||||||
|
function detach() {
|
||||||
|
window.removeEventListener('pointermove', onMove);
|
||||||
|
window.removeEventListener('pointerup', onUp);
|
||||||
|
window.removeEventListener('pointercancel', onCancel);
|
||||||
|
}
|
||||||
|
|
||||||
function onMove(e: PointerEvent) {
|
function onMove(e: PointerEvent) {
|
||||||
if (!state.active) return;
|
if (!state.active) return;
|
||||||
state.x = e.clientX;
|
state.x = e.clientX;
|
||||||
@@ -882,15 +888,23 @@ function makeDrag() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pointercancel fires instead of pointerup when the browser/OS takes over
|
||||||
|
// the gesture (common on touch). Abort the drag: clean up, drop nothing.
|
||||||
|
function onCancel() {
|
||||||
|
detach();
|
||||||
|
state.active = null;
|
||||||
|
state.moved = false;
|
||||||
|
}
|
||||||
|
|
||||||
function onUp(e: PointerEvent) {
|
function onUp(e: PointerEvent) {
|
||||||
window.removeEventListener('pointermove', onMove);
|
detach();
|
||||||
window.removeEventListener('pointerup', onUp);
|
|
||||||
const src = state.active;
|
const src = state.active;
|
||||||
const wasDrag = state.moved;
|
const wasDrag = state.moved;
|
||||||
state.active = null;
|
state.active = null;
|
||||||
state.moved = false;
|
state.moved = false;
|
||||||
if (!src || !wasDrag) return; // a tap — the board click handler deals with it
|
if (!src || !wasDrag) return; // a tap — the board click handler deals with it
|
||||||
|
|
||||||
|
// elementFromPoint returns null off-viewport — treated as an off-board drop.
|
||||||
const el = document.elementFromPoint(e.clientX, e.clientY);
|
const el = document.elementFromPoint(e.clientX, e.clientY);
|
||||||
const sqEl = el?.closest('[data-square]') as HTMLElement | null;
|
const sqEl = el?.closest('[data-square]') as HTMLElement | null;
|
||||||
const target = sqEl?.dataset.square as Square | undefined;
|
const target = sqEl?.dataset.square as Square | undefined;
|
||||||
@@ -907,6 +921,7 @@ function makeDrag() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function start(src: DragSource, e: PointerEvent) {
|
function start(src: DragSource, e: PointerEvent) {
|
||||||
|
detach(); // idempotency — drop any listeners from an unfinished prior drag
|
||||||
suppressClickOn = null;
|
suppressClickOn = null;
|
||||||
state.active = src;
|
state.active = src;
|
||||||
state.x = startX = e.clientX;
|
state.x = startX = e.clientX;
|
||||||
@@ -914,6 +929,7 @@ function makeDrag() {
|
|||||||
state.moved = false;
|
state.moved = false;
|
||||||
window.addEventListener('pointermove', onMove);
|
window.addEventListener('pointermove', onMove);
|
||||||
window.addEventListener('pointerup', onUp);
|
window.addEventListener('pointerup', onUp);
|
||||||
|
window.addEventListener('pointercancel', onCancel);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The board calls this first in its square-click handler. */
|
/** The board calls this first in its square-click handler. */
|
||||||
@@ -1095,11 +1111,11 @@ Create `packages/client/src/lib/PhantomPalette.svelte`:
|
|||||||
<span class="hint muted">Drag onto your board — your guess of where the opponent is.</span>
|
<span class="hint muted">Drag onto your board — your guess of where the opponent is.</span>
|
||||||
<div class="pieces">
|
<div class="pieces">
|
||||||
{#each TYPES as t (t)}
|
{#each TYPES as t (t)}
|
||||||
|
<!-- Pointer-only drag source — same deliberate a11y trade-off as the
|
||||||
|
phantom spans in Board.svelte (no keyboard drag interaction). -->
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<span
|
<span
|
||||||
class="pp pp-{oppColor}"
|
class="pp pp-{oppColor}"
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
aria-label={`place ${t}`}
|
|
||||||
onpointerdown={(e) => { e.preventDefault(); phantomDrag.start({ kind: 'palette', type: t }, e); }}
|
onpointerdown={(e) => { e.preventDefault(); phantomDrag.start({ kind: 'palette', type: t }, e); }}
|
||||||
>{pieceGlyph({ color: oppColor, type: t })}</span>
|
>{pieceGlyph({ color: oppColor, type: t })}</span>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -1183,14 +1199,17 @@ After the existing `const turnLabel = ...` derived block, add:
|
|||||||
return a && phantomDrag.state.moved ? a.type : null;
|
return a && phantomDrag.state.moved ? a.type : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load the phantom layer once `you` is known (blind games only).
|
// Load the phantom layer when `you` is known (blind games only). Keyed on
|
||||||
let phantomsLoaded = $state(false);
|
// gameId — like the connection effect — so it reloads if this <Game>
|
||||||
|
// instance is reused for a different game without a remount.
|
||||||
|
let loadedFor: string | null = $state(null);
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (phantomsLoaded) return;
|
const id = gameId;
|
||||||
const you = game.state.you;
|
const you = game.state.you;
|
||||||
|
if (loadedFor === id) return;
|
||||||
if (you && game.state.mode === 'blind') {
|
if (you && game.state.mode === 'blind') {
|
||||||
untrack(() => phantoms.loadForGame(gameId, you));
|
untrack(() => phantoms.loadForGame(id, you));
|
||||||
phantomsLoaded = true;
|
loadedFor = id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1234,7 +1253,7 @@ Replace the `<div class="board-area">...</div>` block with:
|
|||||||
Immediately after the closing `</div>` of `<div class="game-layout" ...>` (and before the `{#if pendingPromotion ...}` block), add:
|
Immediately after the closing `</div>` of `<div class="game-layout" ...>` (and before the `{#if pendingPromotion ...}` block), add:
|
||||||
|
|
||||||
```svelte
|
```svelte
|
||||||
{#if dragGhost}
|
{#if phantomLayerEnabled && dragGhost}
|
||||||
<div
|
<div
|
||||||
class="drag-ghost piece-{oppColor}"
|
class="drag-ghost piece-{oppColor}"
|
||||||
style="left: {phantomDrag.state.x}px; top: {phantomDrag.state.y}px;"
|
style="left: {phantomDrag.state.x}px; top: {phantomDrag.state.y}px;"
|
||||||
|
|||||||
Reference in New Issue
Block a user