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
|
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() {
|
export default function Grid() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const svgRef = useRef<SVGSVGElement>(null)
|
const svgRef = useRef<SVGSVGElement>(null)
|
||||||
@@ -42,6 +51,22 @@ export default function Grid() {
|
|||||||
cellRefs.current[idx]?.focus()
|
cellRefs.current[idx]?.focus()
|
||||||
}, [selectedCell, width])
|
}, [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
|
if (!puzzle) return null
|
||||||
|
|
||||||
const CELL = getCellSize(Math.max(width, height))
|
const CELL = getCellSize(Math.max(width, height))
|
||||||
@@ -110,25 +135,23 @@ export default function Grid() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const focusTypingField = (r: number, c: number) => {
|
const focusTypingField = (r: number, c: number) => {
|
||||||
if (!window.matchMedia('(pointer: coarse)').matches) return
|
if (window.matchMedia('(pointer: coarse)').matches) {
|
||||||
const field = typingRef.current
|
const field = typingRef.current
|
||||||
if (field) {
|
if (field) {
|
||||||
field.setAttribute('aria-label', buildCellLabel(r, c))
|
field.setAttribute('aria-label', buildCellLabel(r, c))
|
||||||
field.focus({ preventScroll: true })
|
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
|
// Exactly one cell is a Tab stop so the grid can receive keyboard focus even
|
||||||
// before a cell has been selected.
|
// before a cell has been selected.
|
||||||
const focusableIdx = (() => {
|
const focusableIdx = selectedCell
|
||||||
if (selectedCell) return selectedCell.row * width + selectedCell.col
|
? selectedCell.row * width + selectedCell.col
|
||||||
for (let r = 0; r < height; r++) {
|
: firstWhiteIdx(puzzle.cells, width)
|
||||||
for (let c = 0; c < width; c++) {
|
|
||||||
if (puzzle.cells[r][c] !== 'black') return r * width + c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
})()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -178,7 +201,13 @@ export default function Grid() {
|
|||||||
id={`cell-${r}-${c}`}
|
id={`cell-${r}-${c}`}
|
||||||
data-cell={`${r},${c}`}
|
data-cell={`${r},${c}`}
|
||||||
onClick={() => {
|
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)
|
selectCell(r, c)
|
||||||
focusTypingField(r, c)
|
focusTypingField(r, c)
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user