import { useRef, type CSSProperties, type KeyboardEvent } from "react"; import { aidMemoireCellDescription, clearAidMemoireEntries, compactAidMemoireColumns, configureAidMemoire, enterAidMemoireCell, eraseAidMemoireCell, labelAidMemoireCell, MAX_AID_MEMOIRE_CELLS, MAX_AID_MEMOIRE_COLUMNS, resetAidMemoire, setAidMemoireEnabled, type AidMemoireState, } from "../state/aidMemoire"; import { maskValues, symbolFor, valueForKey, type EntryMode, } from "../state/session"; interface AidMemoireProps { readonly size: number; readonly state: AidMemoireState; readonly selectedCell: number; readonly active: boolean; readonly readOnly?: boolean; readonly mode: EntryMode; readonly onStateChange: (state: AidMemoireState) => void; readonly onSelect: (cell: number) => void; readonly onMode: (mode: EntryMode) => void; } const modes: ReadonlyArray<{ mode: EntryMode; label: string; key: string }> = [ { mode: "value", label: "Value", key: "Z" }, { mode: "corner", label: "Corner", key: "X" }, { mode: "center", label: "Centre", key: "C" }, { mode: "color", label: "Colour", key: "V" }, ]; export function AidMemoire({ size, state, selectedCell, active, readOnly = false, mode, onStateChange, onSelect, onMode, }: AidMemoireProps) { const cellRefs = useRef>([]); const selected = Math.max(0, Math.min(state.cells.length - 1, selectedCell)); const selectAndFocus = (cell: number) => { const next = Math.max(0, Math.min(state.cells.length - 1, cell)); onSelect(next); cellRefs.current[next]?.focus(); }; const handleKeyDown = ( cell: number, event: KeyboardEvent, ) => { const row = Math.floor(cell / state.columns); const column = cell % state.columns; const rowStart = row * state.columns; const rowEnd = Math.min( state.cells.length - 1, rowStart + state.columns - 1, ); let destination: number | undefined; if (event.key === "ArrowLeft") destination = Math.max(rowStart, cell - 1); if (event.key === "ArrowRight") destination = Math.min(rowEnd, cell + 1); if (event.key === "ArrowUp") { destination = cell - state.columns >= 0 ? cell - state.columns : cell; } if (event.key === "ArrowDown") { destination = cell + state.columns < state.cells.length ? cell + state.columns : cell; } if (event.key === "Home") { destination = event.ctrlKey || event.metaKey ? 0 : rowStart; } if (event.key === "End") { destination = event.ctrlKey || event.metaKey ? state.cells.length - 1 : rowEnd; } if (event.key === "PageUp") destination = column; if (event.key === "PageDown") { const lastRowStart = Math.floor((state.cells.length - 1) / state.columns) * state.columns; destination = Math.min(state.cells.length - 1, lastRowStart + column); } if (destination !== undefined) { event.preventDefault(); selectAndFocus(destination); return; } if (readOnly) return; if ( event.key === "Backspace" || event.key === "Delete" || event.key === "0" ) { event.preventDefault(); onStateChange(eraseAidMemoireCell(state, cell, mode)); return; } if (!event.ctrlKey && !event.metaKey && !event.altKey) { const modeKey: Record = { z: "value", x: "corner", c: "center", v: "color", }; const nextMode = modeKey[event.key.toLowerCase()]; if (nextMode !== undefined) { event.preventDefault(); onMode(nextMode); return; } const value = valueForKey(event.key, mode === "color" ? 8 : size); if (value !== null) { event.preventDefault(); onStateChange(enterAidMemoireCell(state, cell, mode, value, size)); } } }; const configure = (cellCount: number, columns: number) => { const next = configureAidMemoire(state, size, cellCount, columns); onStateChange(next); if (selected >= next.cells.length) onSelect(next.cells.length - 1); }; return (

Optional scratch cells

Aid-mémoire

Track contextual sets, pseudo-digits or mappings. These cells do not constrain the Sudoku.

{Array.from( { length: Math.ceil(state.cells.length / state.columns) }, (_, rowIndex) => (
{state.cells .slice( rowIndex * state.columns, (rowIndex + 1) * state.columns, ) .map((cell, columnIndex) => { const index = rowIndex * state.columns + columnIndex; const cornerMarks = maskValues(cell.cornerMarks, size); const centerMarks = maskValues(cell.centerMarks, size); return ( ); })}
), )}
{modes.map((item) => ( ))}

The regular keypad edits this cell in {mode} mode. Digits, Z/X/C/V, Delete and arrow keys also work directly in the scratch grid.

Configure scratch layout
); }