Controller+BETADownload

keyboards reference

Build and edit a drawn keyboard without HTML inputs or a system keyboard. Import from @controller-plus/sdk/keyboards or /sdk/src/keyboards.mjs. Most games should use the keyboard surface part; these functions support custom renderers.

Exports

Full signature Parameters, defaults and return
LAYOUTS Frozen array of 65 frozen metadata records { id, name, nativeName, script, direction, needsCandidates? }. direction is ltr or rtl; Chinese sets needsCandidates: true. No row data here.
layoutFor(language): string Resolve a BCP 47-style tag to a layout ID; case-insensitive, underscores accepted. Non-string, malformed or unsupported scripts fall back to en.
keyRows(layoutId = 'en', { charset = 'text', shift = false, page = 'letters' } = {}) Return { id, direction, rows: Key[][], pages: string[] }. charset: letters, text, digits. Page names remain letters/symbols, even for vowel/kana pages.
applyKey(state, key, { maxLength = 48 } = {}): TextState Return a copied serializable editor state. Enforce a grapheme limit, compose letters and update shift state. Does not mutate input state.
textOf(state): string Committed text plus any active Hangul syllable. Missing state yields ''.

Key is { kind: 'char', text, alt?: string[] } or { kind: 'shift' | 'delete' | 'enter' | 'page' | 'language' | 'space', width?: number }. alt contains alternate characters for a renderer to expose. width is a relative key-width hint. The functions do not draw UI or emit events. They have no deliberate error class; malformed argument shapes can produce normal JavaScript errors.

TextState is { text: string, composing?: { shift?: true, hangul?: { initial?: string, medial?: string, final?: string } } }. Store and restore it as JSON. Read textOf(state) for the visible string rather than only state.text.

Language selection

Exact IDs win, then script-aware language matching. Examples: tr-TR → tr, sr-Latn → sr-Latn, sr-Cyrl → sr, uz → uz-Latn, zh-Hant → zh. Unsupported requested scripts use English. Aliases: nb/nn → no, fil → tl, iw → he, in → id, sh → sr-Latn without a script override. Extensions/private-use subtags do not override a script.

Script/group Layout IDs and languages
Latin en English; tr Turkish; de German; fr French; es Spanish; pt Portuguese; it Italian; nl Dutch; sv Swedish; no Norwegian; da Danish; fi Finnish; pl Polish; cs Czech; sk Slovak; hu Hungarian; ro Romanian; hr Croatian; sl Slovenian; sr-Latn Serbian Latin; lt Lithuanian; lv Latvian; et Estonian; is Icelandic; ga Irish; cy Welsh; mt Maltese; vi Vietnamese; id Indonesian; ms Malay; tl Filipino; sw Swahili; ha Hausa; yo Yoruba; az Azerbaijani; uz-Latn Uzbek Latin.
Cyrillic ru Russian; uk Ukrainian; be Belarusian; bg Bulgarian; sr Serbian; mk Macedonian; kk Kazakh; ky Kyrgyz; mn Mongolian.
Greek, Armenian, Georgian el Greek; hy Armenian; ka Georgian.
Right to left he Hebrew; ar Arabic; fa Persian; ur Urdu.
Indic hi Hindi; mr Marathi; bn Bengali; ta Tamil; te Telugu; gu Gujarati; pa Punjabi Gurmukhi; kn Kannada; ml Malayalam.
Other th Thai; ko Korean; ja Japanese kana; zh Chinese pinyin letters.

Pages, case and composition

Digits are a 3×4 phone-style pad: 1–9, delete, 0, enter. Text mode adds space, punctuation and a symbols page. Letters mode omits general punctuation, but retains Indic vowel pages and Japanese katakana. Page and language changes are caller-owned: applyKey commits composition but does not choose a new page/layout.

Shift lasts until a letter is accepted. keyRows(..., { shift: true }) supplies locale-aware casing and alternatives, including Turkish dotted/dotless I and German ẞ. Read state.composing?.shift when redrawing.

Hangul composes syllables and compound vowels/finals. Delete removes one component while a syllable is active, then whole graphemes from committed text. Kana supports hiragana, katakana, voicing and small kana, without kanji conversion. Indic dependent signs attach only to an appropriate base and script; orphan marks are ignored. Chinese provides pinyin letters and tone alternatives, not a Hanzi candidate engine. Do not promise general system-IME support.

enter, page and language commit composition and clear shift. Enter adds no newline. Space adds a space. applyKey defaults to 48 graphemes; invalid nonnumeric limits fall back to 48, negative limits become zero. Surface keyboards separately clamp their own limits to 64 or 24 slots. Without Intl.Segmenter, editing counts/removes Unicode code points rather than complete graphemes.

The browser surface renderer chooses among the game's language, the player's language and English. Its current touch UI does not expose every alternate returned by keyRows().rows[].alt; availability in the data API is not a guarantee of a long-press menu on every client.

Editing example

import { LAYOUTS, layoutFor, keyRows, applyKey, textOf } from '@controller-plus/sdk/keyboards';
let state = { text: '' };
for (const text of ['ㅎ', 'ㅏ', 'ㄴ']) state = applyKey(state, { kind: 'char', text });
console.log(textOf(state)); // 한
state = applyKey(state, { kind: 'enter' });
console.log(JSON.stringify(state)); // {"text":"한"}
const layout = layoutFor('tr-TR');
const board = keyRows(layout, { charset: 'text', shift: true });
console.log(board.id, board.pages, board.rows.length, LAYOUTS.length);
state = applyKey(state, { kind: 'delete' }, { maxLength: 24 });
console.log(textOf(state)); // empty