Description
KeyPiano is a generic clickable "piano" of buttons that composes a
technical sequence -- made of codes and separators that are hard to type by hand --
into a target text field. Only the content of the keys (images, text, values) is
domain-specific and stays in the HTML written by the consuming page; KeyPiano
only knows about groups of keys, two separators, and the target field.
JavaScript
The KeyPiano class is activated on an existing container element. It reads
its configuration from the container's data-* attributes and from its
.wexa-key-piano-group children, found in document order.
Usage example:
<script type="module">
import { KeyPiano } from '../wexa_statics/js/extras/keypiano/keypiano.js';
const piano = new KeyPiano(document.getElementById('my-piano'));
</script>
HTML
The container carries the following data-* attributes:
data-target(required): id of the target text field.data-group-sep(optional): separator between the parts of one composed entry. Default:"-".data-key-sep(optional): separator between successive entries. Default:".".data-backspace-label(optional): accessible label of the "delete last key" button. Default:"Delete last key".data-clear-label(optional): accessible label of the "clear all" button. Default:"Clear all".
Each .wexa-key-piano-group child is either:
data-mode="radio": native<input type="radio">keys, wrapped in a.wexa-key-piano-keylabel. Groups take turns: only the first group is enabled initially, the composed entry is appended only once every group of the cycle has a selection.data-mode="free"(or nodata-mode): plain<button type="button" class="wexa-key-piano-key" value="...">keys. Every click appends its value immediately, with no turn and no exclusivity.
<div class="wexa-key-piano" id="my-piano"
data-target="my-target-field" data-group-sep="-" data-key-sep=".">
<div class="wexa-key-piano-group" data-mode="radio" aria-label="Group 1">
<label class="wexa-key-piano-key">
<input type="radio" value="1">
1
</label>
...
</div>
<div class="wexa-key-piano-group" data-mode="radio" aria-label="Group 2">
...
</div>
</div>
Radio and free groups can be mixed in a single container. Free groups always respond to clicks; radio groups only respond when it is their turn in the cycle.
KeyPiano itself injects, at the end of the container: a "delete last key"
button, a "clear all" button (both using the shared Whakerexa mono-svg icons), and a
visually-hidden aria-live="polite" status region announcing every action to
screen reader users.
"Delete last key" has two behaviours depending on context:
- While a radio cycle is in progress: undoes the last radio selection and re-enables that group.
- Otherwise: restores the target field to its value before the last append.
Every change to the target field dispatches a real input event
(with bubbles: true), so consuming pages can react to piano-driven
changes the same way they react to keyboard input.
Real native <input type="radio"> and <button>
elements are used throughout, so keyboard navigation and focus styling are already
covered by Whakerexa's global rules -- nothing to add.
Retargeting
piano.setTarget(fieldId) points the piano at a different target field,
discarding any in-progress entry and undo history. Useful when a single shared piano
serves several fields in turn (e.g. opened from different rows of a table).
const piano = new KeyPiano(document.getElementById('my-piano'));
// later, when the context changes:
piano.setTarget('other-field-id');
Demo
Example 1: two "radio" groups (e.g. a two-part code)
Click a key in "Group A", then a key in "Group B": one entry is appended only once both groups have a choice. "Delete last key" undoes the last selection while a choice is in progress, or removes the last full entry otherwise.
Example 2: one "free" group (e.g. a phoneme sequence)
Every click appends its value immediately, separated by the outer separator.
Example 3: mixed "radio" + "free" groups
Radio groups take turns (Group A first, then Group B); the free group appends immediately at any time. The entry is composed and appended only once both radio groups have a selection.