restore grid keyboard focus on load and cell click
This commit is contained in:
@@ -14,6 +14,15 @@ function getCellSize(gridSize: number): number {
|
||||
return 36
|
||||
}
|
||||
|
||||
function firstWhiteIdx(cells: string[][], width: number): number {
|
||||
for (let r = 0; r < cells.length; r++) {
|
||||
for (let c = 0; c < cells[r].length; c++) {
|
||||
if (cells[r][c] !== 'black') return r * width + c
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
export default function Grid() {
|
||||
const { t } = useTranslation()
|
||||
const svgRef = useRef<SVGSVGElement>(null)
|
||||
@@ -42,6 +51,22 @@ export default function Grid() {
|
||||
cellRefs.current[idx]?.focus()
|
||||
}, [selectedCell, width])
|
||||
|
||||
// land focus on the starting cell when a puzzle loads and nothing else has
|
||||
// it, so typing works without clicking into the grid first
|
||||
useEffect(() => {
|
||||
if (!puzzle) return
|
||||
const t = setTimeout(() => {
|
||||
const active = document.activeElement
|
||||
if (active !== document.body && active !== null) return
|
||||
const st = useStore.getState()
|
||||
const idx = st.selectedCell
|
||||
? st.selectedCell.row * width + st.selectedCell.col
|
||||
: firstWhiteIdx(puzzle.cells, width)
|
||||
cellRefs.current[idx]?.focus({ preventScroll: true })
|
||||
}, 0)
|
||||
return () => clearTimeout(t)
|
||||
}, [puzzle, width])
|
||||
|
||||
if (!puzzle) return null
|
||||
|
||||
const CELL = getCellSize(Math.max(width, height))
|
||||
@@ -110,25 +135,23 @@ export default function Grid() {
|
||||
}
|
||||
|
||||
const focusTypingField = (r: number, c: number) => {
|
||||
if (!window.matchMedia('(pointer: coarse)').matches) return
|
||||
if (window.matchMedia('(pointer: coarse)').matches) {
|
||||
const field = typingRef.current
|
||||
if (field) {
|
||||
field.setAttribute('aria-label', buildCellLabel(r, c))
|
||||
field.focus({ preventScroll: true })
|
||||
}
|
||||
} else {
|
||||
// desktop: park focus on the matching grid cell so keystrokes reach it
|
||||
cellRefs.current[r * width + c]?.focus()
|
||||
}
|
||||
}
|
||||
|
||||
// Exactly one cell is a Tab stop so the grid can receive keyboard focus even
|
||||
// before a cell has been selected.
|
||||
const focusableIdx = (() => {
|
||||
if (selectedCell) return selectedCell.row * width + selectedCell.col
|
||||
for (let r = 0; r < height; r++) {
|
||||
for (let c = 0; c < width; c++) {
|
||||
if (puzzle.cells[r][c] !== 'black') return r * width + c
|
||||
}
|
||||
}
|
||||
return 0
|
||||
})()
|
||||
const focusableIdx = selectedCell
|
||||
? selectedCell.row * width + selectedCell.col
|
||||
: firstWhiteIdx(puzzle.cells, width)
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -178,7 +201,13 @@ export default function Grid() {
|
||||
id={`cell-${r}-${c}`}
|
||||
data-cell={`${r},${c}`}
|
||||
onClick={() => {
|
||||
if (isBlack) return
|
||||
if (isBlack) {
|
||||
// keep keyboard focus inside the grid after tapping a black square
|
||||
if (!window.matchMedia('(pointer: coarse)').matches) {
|
||||
cellRefs.current[focusableIdx]?.focus()
|
||||
}
|
||||
return
|
||||
}
|
||||
selectCell(r, c)
|
||||
focusTypingField(r, c)
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user