To make sure only a single character can be inside each input at a time you can use a keydown event on the input and preventDefault on the handler so that it doesn't double enter into the field. Then pass on some information about the key event to your function and set the value of the input to be the value of the current keyCode passed through by the event inside the function.
I see you're looking to limit it to a specific set of keycode's so you can take care of that in this function as well.
<script>
let boxes = [
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""]
];
function isValid(val, i, j) {
if ((val.keyCode >= 65 && val.keyCode <= 90) || (val.keyCode >= 97 && val.keyCode <= 122)) {
boxes[i][j] = val.key;
return true;
}
return false;
}
</script>
<main>
<div class="root">
{#each boxes as row, i}
<div class="row">
{#each row as column, j}
<span class="column">
<input
class="col-input"
type="text"
bind:value="{boxes[i][j]}"
on:keydown|preventDefault={e => isValid(e, i, j)}
/>
</span>
{/each}
</div>
{/each}
</div>
</main>
Here's an example in the REPL
EDIT:
If tab functionality needs to be preserved (assuming this is not a point and click game, then some extra functionality will need to be added since preventDefault kills the tab key in this instance.
This can be done by changing bind:value to bind:this and all references that would change the boxes' value will need to be changed to boxes[i][j].value.
I added some additional functionality to handle the arrow keys and preserve tab key function while keeping focus inside the grid (usually tab would want to jump out of window)
<script>
let boxes = [
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""],
["", "", "", "", "", "", "", ""]
];
function isValid(val, i, j) {
const width = boxes[i].length, height = boxes.length, prevCol = j - 1, nextCol = j + 1, prevRow = i - 1, nextRow = i + 1, left = 37, up = 38, right = 39, down = 40, tab = 9;
// Preserve tab functionality,
// loop to first input of next row
// when end of row is reached or to
// first row and column when end of
// matrix is reached
if (val.keyCode == tab) {
nextCol != width? boxes[i][nextCol].focus() : i != height - 1? boxes[nextRow][0].focus() : boxes[0][0].focus();
return;
}
// Loop around single row with right and left arrows
if (val.keyCode == right) {
nextCol != width? boxes[i][nextCol].focus() : boxes[i][0].focus();
return;
}
if (val.keyCode == left) {
j != 0? boxes[i][prevCol].focus() : boxes[i][width - 1].focus();
return;
}
// loop around single column with up and down arrows
if (val.keyCode == up) {
i != 0? boxes[prevRow][j].focus() : boxes[height - 1][j].focus();
return;
}
if (val.keyCode == down) {
i != height - 1? boxes[nextRow][j].focus() : boxes[0][j].focus();
return;
}
if ((val.keyCode >= 65 && val.keyCode <= 90) || (val.keyCode >= 97 && val.keyCode <= 122)) {
boxes[i][j].value = val.key;
return true;
}
return false;
}
</script>
<main>
<div class="root">
{#each boxes as row, i}
<div class="row">
{#each row as column, j}
<span class="column">
<input
class="col-input"
type="text"
bind:this="{boxes[i][j]}"
on:keydown|preventDefault={e => isValid(e, i, j)}
/>
</span>
{/each}
</div>
{/each}
</div>
</main>
Here is the link to that REPL example