!isBlack && selectCell(r, c)}
+ onClick={() => {
+ if (isBlack) return
+ selectCell(r, c)
+ focusTypingField(r, c)
+ }}
style={{ cursor: isBlack ? 'default' : 'pointer' }}
>
- {/* screen reader layer: one focusable cell per grid cell, invisible to the eye */}
+ {/* screen reader layer: a real ARIA grid mirroring the visual one */}
- {Array.from({ length: height }, (_, r) =>
- Array.from({ length: width }, (_, c) => {
- const isBlack = puzzle.cells[r][c] === 'black'
- const isSelected = selectedCell?.row === r && selectedCell?.col === c
- const num = puzzle.cell_numbers[`${r},${c}`]
- const letter = entries[r]?.[c] || ''
- const pencilLetter = !letter && pencilEntries[r]?.[c] ? pencilEntries[r][c] : ''
- const status: CellStatus = cellStatus[r]?.[c] || 'default'
- const label = isBlack ? t('grid.blackCell') :
- t('grid.cellLabel', { row: r + 1, col: c + 1 }) + (num ? t('grid.cellNumber', { num }) : '') + (letter ? t('grid.cellLetter', { letter }) : t('grid.cellEmpty')) +
- (status === 'correct' ? t('grid.cellCorrect') : status === 'incorrect' ? t('grid.cellIncorrect') : status === 'revealed' ? t('grid.cellRevealed') : pencilLetter ? t('grid.cellPencil', { letter: pencilLetter }) : '')
- return (
-
+
{
+ const value = e.target.value
+ e.target.value = ''
+ for (const ch of value.toLowerCase()) {
+ if (ch >= 'a' && ch <= 'z') useStore.getState().typeLetter(ch)
+ else if (ch === ' ') useStore.getState().toggleDirection()
+ }
+ }}
+ onKeyDown={e => {
+ if (e.key === 'Backspace' && !e.currentTarget.value) {
+ e.preventDefault()
+ useStore.getState().deleteLetter()
+ }
+ }}
+ />
diff --git a/frontend/src/index.css b/frontend/src/index.css
index cdc1cb4..93d6141 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -86,6 +86,27 @@
cursor: default;
}
+/* hidden typing field so touch devices can summon the on-screen keyboard */
+.grid-type-input {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ width: 2px;
+ height: 2px;
+ padding: 0;
+ border: 0;
+ opacity: 0.01;
+ background: transparent;
+ color: transparent;
+ caret-color: transparent;
+}
+
+.game-input-mode {
+ display: flex;
+ gap: 0.375rem;
+ margin-top: 0.5rem;
+}
+
.visually-hidden {
position: absolute; width: 1px; height: 1px;
padding: 0; margin: -1px; overflow: hidden;