From 6125475ff20abb3751b51b2960f31627463002d6 Mon Sep 17 00:00:00 2001 From: Carl Worth Date: Sun, 8 Mar 2026 09:20:25 -0400 Subject: [PATCH] Add support for dragging tiles while claiming This can be either to drag tiles to the rack (to claim them) or to just rearrange the tiles (as can already be done outside of claiming). A 5-pixel threshold of movement will trigger a drag operation. Movement less than that will be considered a click and will claim the letter. --- anagrams/anagrams.jsx | 83 ++++++++++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 21 deletions(-) diff --git a/anagrams/anagrams.jsx b/anagrams/anagrams.jsx index 0d2397d..14b4d05 100644 --- a/anagrams/anagrams.jsx +++ b/anagrams/anagrams.jsx @@ -596,23 +596,43 @@ class Game extends React.Component { * Center tile dragging (client-side rearrangement) * *****************************************************/ + _drop_on_rack(clientX, clientY, tile) { + if (!this.state.claim_active) return false; + const rack = document.querySelector(".claim-rack"); + if (!rack) return false; + const r = rack.getBoundingClientRect(); + if (clientX >= r.left && clientX <= r.right + && clientY >= r.top && clientY <= r.bottom) { + this.take_letter(tile); + return true; + } + return false; + } + on_center_mouse_down(e, tile) { - /* Only allow rearranging when not in claim mode. */ - if (this.state.claim_active) return; if (e.button !== 0) return; const pool = e.target.closest(".center-pool"); if (!pool) return; const rect = pool.getBoundingClientRect(); + const startX = e.clientX; + const startY = e.clientY; this._drag_offset = { x: e.clientX - (this.state.tile_positions[tile.id].x / 100 * rect.width), y: e.clientY - (this.state.tile_positions[tile.id].y / 100 * rect.height) }; this._drag_pool_rect = rect; - this.setState({ dragging_center_tile: tile.id }); + let dragging = false; const onMove = (me) => { + const dx = me.clientX - startX; + const dy = me.clientY - startY; + if (!dragging) { + if (dx * dx + dy * dy < 25) return; + dragging = true; + this.setState({ dragging_center_tile: tile.id }); + } const x = ((me.clientX - this._drag_offset.x) / rect.width) * 100; const y = ((me.clientY - this._drag_offset.y) / rect.height) * 100; this.setState(prev => ({ @@ -626,10 +646,15 @@ class Game extends React.Component { })); }; - const onUp = () => { + const onUp = (me) => { document.removeEventListener("mousemove", onMove); document.removeEventListener("mouseup", onUp); - this.setState({ dragging_center_tile: null }); + if (dragging) { + this.setState({ dragging_center_tile: null }); + this._drop_on_rack(me.clientX, me.clientY, tile); + } else if (this.state.claim_active && !this.state.revealing[tile.id]) { + this.take_letter(tile); + } }; document.addEventListener("mousemove", onMove); @@ -638,7 +663,6 @@ class Game extends React.Component { } on_center_touch_start(e, tile) { - if (this.state.claim_active) return; if (e.touches.length !== 1) return; const pool = e.target.closest(".center-pool"); @@ -646,29 +670,38 @@ class Game extends React.Component { const touch = e.touches[0]; const rect = pool.getBoundingClientRect(); + this._drag_start = { x: touch.clientX, y: touch.clientY }; this._drag_offset = { x: touch.clientX - (this.state.tile_positions[tile.id].x / 100 * rect.width), y: touch.clientY - (this.state.tile_positions[tile.id].y / 100 * rect.height) }; this._drag_pool_rect = rect; - this._touch_moved = false; - this.setState({ dragging_center_tile: tile.id }); + this._drag_dragging = false; + this._drag_tile = tile; e.preventDefault(); } on_center_touch_move(e) { - if (this.state.dragging_center_tile === null) return; + if (!this._drag_tile) return; const touch = e.touches[0]; + this._drag_last_touch = { x: touch.clientX, y: touch.clientY }; const rect = this._drag_pool_rect; - this._touch_moved = true; + + if (!this._drag_dragging) { + const dx = touch.clientX - this._drag_start.x; + const dy = touch.clientY - this._drag_start.y; + if (dx * dx + dy * dy < 25) return; + this._drag_dragging = true; + this.setState({ dragging_center_tile: this._drag_tile.id }); + } const x = ((touch.clientX - this._drag_offset.x) / rect.width) * 100; const y = ((touch.clientY - this._drag_offset.y) / rect.height) * 100; this.setState(prev => ({ tile_positions: { ...prev.tile_positions, - [this.state.dragging_center_tile]: { + [this._drag_tile.id]: { x: Math.max(0, Math.min(90, x)), y: Math.max(0, Math.min(85, y)) } @@ -678,8 +711,21 @@ class Game extends React.Component { } on_center_touch_end(e) { - if (this.state.dragging_center_tile === null) return; - this.setState({ dragging_center_tile: null }); + const tile = this._drag_tile; + if (!tile) return; + + if (this._drag_dragging) { + this.setState({ dragging_center_tile: null }); + /* Use last known touch position for drop detection. */ + if (this._drag_last_touch) { + this._drop_on_rack(this._drag_last_touch.x, this._drag_last_touch.y, tile); + } + } else if (this.state.claim_active && !this.state.revealing[tile.id]) { + this.take_letter(tile); + } + + this._drag_tile = null; + this._drag_dragging = false; } /***************************************************** @@ -791,8 +837,7 @@ class Game extends React.Component { left: pos.x + "%", top: pos.y + "%", zIndex: is_dragging ? 10 : 1, - cursor: claim_active && !is_revealing ? "pointer" : - !claim_active && !is_revealing ? "grab" : "default" + cursor: claim_active ? "pointer" : "grab" }; let className = ""; @@ -804,12 +849,8 @@ class Game extends React.Component {
this.on_center_mouse_down(e, tile) : null} - onTouchStart={!claim_active && !is_revealing - ? (e) => this.on_center_touch_start(e, tile) : null} - onClick={claim_active && !is_revealing - ? () => this.take_letter(tile) : null}> + onMouseDown={(e) => this.on_center_mouse_down(e, tile)} + onTouchStart={(e) => this.on_center_touch_start(e, tile)}> {is_revealing ? {rev.seconds} : tile.letter} -- 2.45.2