drag_source: null,
drag_over_cell: null,
rack_drag_over: false,
- selected: null
+ selected: null,
+ touch_pos: null
};
}
this._onTouchEnd = (e) => this.on_touch_end(e);
document.addEventListener("touchmove", this._onTouchMove, { passive: false });
document.addEventListener("touchend", this._onTouchEnd);
+ document.addEventListener("touchcancel", this._onTouchEnd);
}
componentDidUpdate(prevProps, prevState) {
componentWillUnmount() {
document.removeEventListener("touchmove", this._onTouchMove);
document.removeEventListener("touchend", this._onTouchEnd);
+ document.removeEventListener("touchcancel", this._onTouchEnd);
}
/* Send tile placement changes to the server. */
if (e.touches.length !== 1) return;
const touch = e.touches[0];
- const target = e.currentTarget;
- const rect = target.getBoundingClientRect();
-
- /* Create a floating clone to follow the finger. */
- const clone = target.cloneNode(true);
- clone.className = "tile touch-dragging";
- clone.style.position = "fixed";
- clone.style.zIndex = "200";
- clone.style.pointerEvents = "none";
- clone.style.width = rect.width + "px";
- clone.style.height = rect.height + "px";
- clone.style.left = (touch.clientX - rect.width / 2) + "px";
- clone.style.top = (touch.clientY - rect.height / 2) + "px";
- document.body.appendChild(clone);
-
- this._touch = { source, clone, startX: touch.clientX, startY: touch.clientY, moved: false };
- this.setState({ drag_source: source, selected: null });
+ this._touch_moved = false;
+ this.setState({
+ drag_source: source,
+ selected: null,
+ touch_pos: { x: touch.clientX, y: touch.clientY }
+ });
e.preventDefault();
}
on_touch_move(e) {
- if (!this._touch) return;
+ if (!this.state.touch_pos) return;
const touch = e.touches[0];
- const t = this._touch;
+ this._touch_moved = true;
- t.clone.style.left = (touch.clientX - 22) + "px";
- t.clone.style.top = (touch.clientY - 22) + "px";
- t.moved = true;
-
- /* Find the element under the finger (hide clone so elementFromPoint
- * sees through it). */
- t.clone.style.display = "none";
+ /* The floating tile has pointer-events: none so elementFromPoint
+ * sees through it without needing to hide/show anything. */
const el = document.elementFromPoint(touch.clientX, touch.clientY);
- t.clone.style.display = "";
+
+ const updates = { touch_pos: { x: touch.clientX, y: touch.clientY } };
if (el) {
const cell = el.closest("[data-key]");
const rack = el.closest("[data-rack]");
if (cell) {
const key = cell.getAttribute("data-key");
- if (this.state.drag_over_cell !== key) {
- this.setState({ drag_over_cell: key, rack_drag_over: false });
- }
+ updates.drag_over_cell = key;
+ updates.rack_drag_over = false;
} else if (rack) {
- if (!this.state.rack_drag_over) {
- this.setState({ drag_over_cell: null, rack_drag_over: true });
- }
+ updates.drag_over_cell = null;
+ updates.rack_drag_over = true;
} else {
- if (this.state.drag_over_cell || this.state.rack_drag_over) {
- this.setState({ drag_over_cell: null, rack_drag_over: false });
- }
+ updates.drag_over_cell = null;
+ updates.rack_drag_over = false;
}
}
+ this.setState(updates);
e.preventDefault();
}
on_touch_end(e) {
- if (!this._touch) return;
- const t = this._touch;
-
- /* Clean up the floating clone. */
- if (t.clone.parentNode) t.clone.parentNode.removeChild(t.clone);
+ if (!this.state.touch_pos) return;
+ const source = this.state.drag_source;
/* If the finger barely moved, treat as a tap (let onClick handle it). */
- if (!t.moved) {
- this._touch = null;
- this.setState({ drag_source: null, drag_over_cell: null, rack_drag_over: false });
+ if (!this._touch_moved) {
+ this.setState({ drag_source: null, drag_over_cell: null,
+ rack_drag_over: false, touch_pos: null });
return;
}
/* Find drop target. */
- const touch = e.changedTouches[0];
- const el = document.elementFromPoint(touch.clientX, touch.clientY);
+ const touch = e.changedTouches ? e.changedTouches[0] : null;
+ const el = touch ? document.elementFromPoint(touch.clientX, touch.clientY) : null;
- if (el) {
+ if (el && source) {
const cell = el.closest("[data-key]");
const rack = el.closest("[data-rack]");
if (cell) {
const key = cell.getAttribute("data-key");
const [r, c] = key.split(",").map(Number);
- /* Reuse existing drop logic. */
- this._drop_touch(r, c, t.source);
- } else if (rack && t.source.from === "grid") {
+ this._drop_touch(r, c, source);
+ this.setState({ touch_pos: null });
+ return;
+ } else if (rack && source.from === "grid") {
/* Drop grid tile back to rack. */
const new_grid = {...this.state.grid};
- delete new_grid[t.source.grid_key];
- const new_rack = [...this.state.rack, t.source.tile_index];
+ delete new_grid[source.grid_key];
+ const new_rack = [...this.state.rack, source.tile_index];
this.setState({ grid: new_grid, rack: new_rack,
- drag_source: null, drag_over_cell: null, rack_drag_over: false });
- this._touch = null;
+ drag_source: null, drag_over_cell: null,
+ rack_drag_over: false, touch_pos: null });
return;
}
}
- this._touch = null;
- this.setState({ drag_source: null, drag_over_cell: null, rack_drag_over: false });
+ this.setState({ drag_source: null, drag_over_cell: null,
+ rack_drag_over: false, touch_pos: null });
}
_drop_touch(r, c, source) {
if (is_blank && (!this.state.grid[source.grid_key] || !this.state.grid[source.grid_key].letter)) {
this.setState({ grid: new_grid, rack: new_rack,
drag_source: null, drag_over_cell: null, rack_drag_over: false,
- blank_pending: { r, c, tileIndex: tile_index } });
+ touch_pos: null, blank_pending: { r, c, tileIndex: tile_index } });
return;
}
new_grid[grid_key] = { letter, tileIndex: tile_index, isBlank };
this.setState({ grid: new_grid, rack: new_rack,
- drag_source: null, drag_over_cell: null, rack_drag_over: false });
+ drag_source: null, drag_over_cell: null, rack_drag_over: false,
+ touch_pos: null });
}
/*****************************************************
</div>
) : null}
</div>
+ ) : null,
+
+ state.touch_pos && state.drag_source ? (
+ this.render_touch_tile()
) : null
];
}
+ render_touch_tile() {
+ const { drag_source, touch_pos, tiles } = this.state;
+ const tile_index = drag_source.tile_index;
+ const tile_char = tiles[tile_index];
+ const is_blank = (tile_char === "_" || tile_char === undefined);
+
+ /* For blanks already on the grid, show their assigned letter. */
+ let letter;
+ if (drag_source.from === "grid") {
+ const cell = this.state.grid[drag_source.grid_key];
+ letter = cell ? cell.letter : (is_blank ? "\u00A0" : tile_char);
+ } else {
+ letter = is_blank ? "\u00A0" : tile_char;
+ }
+
+ return (
+ <div key="touch-tile"
+ className="tile touch-dragging"
+ style={{
+ position: "fixed",
+ left: touch_pos.x - 22,
+ top: touch_pos.y - 22,
+ zIndex: 200,
+ pointerEvents: "none"
+ }}>
+ {letter}
+ </div>
+ );
+ }
+
render_board(analysis, invalid_cells, unconnected_cells) {
const { grid, drag_over_cell, drag_source, selected } = this.state;
const bounds = grid_bounds(grid);