/ ink / events / keyboard-event.ts
keyboard-event.ts
 1  import type { ParsedKey } from '../parse-keypress.js'
 2  import { TerminalEvent } from './terminal-event.js'
 3  
 4  /**
 5   * Keyboard event dispatched through the DOM tree via capture/bubble.
 6   *
 7   * Follows browser KeyboardEvent semantics: `key` is the literal character
 8   * for printable keys ('a', '3', ' ', '/') and a multi-char name for
 9   * special keys ('down', 'return', 'escape', 'f1'). The idiomatic
10   * printable-char check is `e.key.length === 1`.
11   */
12  export class KeyboardEvent extends TerminalEvent {
13    readonly key: string
14    readonly ctrl: boolean
15    readonly shift: boolean
16    readonly meta: boolean
17    readonly superKey: boolean
18    readonly fn: boolean
19  
20    constructor(parsedKey: ParsedKey) {
21      super('keydown', { bubbles: true, cancelable: true })
22  
23      this.key = keyFromParsed(parsedKey)
24      this.ctrl = parsedKey.ctrl
25      this.shift = parsedKey.shift
26      this.meta = parsedKey.meta || parsedKey.option
27      this.superKey = parsedKey.super
28      this.fn = parsedKey.fn
29    }
30  }
31  
32  function keyFromParsed(parsed: ParsedKey): string {
33    const seq = parsed.sequence ?? ''
34    const name = parsed.name ?? ''
35  
36    // Ctrl combos: sequence is a control byte (\x03 for ctrl+c), name is the
37    // letter. Browsers report e.key === 'c' with e.ctrlKey === true.
38    if (parsed.ctrl) return name
39  
40    // Single printable char (space through ~, plus anything above ASCII):
41    // use the literal char. Browsers report e.key === '3', not 'Digit3'.
42    if (seq.length === 1) {
43      const code = seq.charCodeAt(0)
44      if (code >= 0x20 && code !== 0x7f) return seq
45    }
46  
47    // Special keys (arrows, F-keys, return, tab, escape, etc.): sequence is
48    // either an escape sequence (\x1b[B) or a control byte (\r, \t), so use
49    // the parsed name. Browsers report e.key === 'ArrowDown'.
50    return name || seq
51  }