Ember Example: UseHotkeySequence

import { array } from '@ember/helper'
import Component from '@glimmer/component'
import { tracked } from '@glimmer/tracking'

import { on } from '@ember/modifier'

import { useHotkeySequence, useHotkey } from '@tanstack/ember-hotkeys'
import type { HotkeySequence, HotkeyCallback } from '@tanstack/ember-hotkeys'
const gt = (a: number, b: number) => a > b

class App extends Component {
  usage0 =
    '{{useHotkeySequence (array "G" "G") this.scrollToTop}}\n{{useHotkeySequence\n  (array "ArrowUp" "ArrowUp" "ArrowDown" "ArrowDown")\n  this.activateCheatMode timeout=1500}}\n{{useHotkeySequence (array "C" "I" "W") this.changeInnerWord}}\n{{useHotkeySequence (array "Shift+[KeyR]" "Shift+[KeyT]") this.runChain}}'
  @tracked lastSequence: string | null = null
  @tracked history: Array<string> = []
  @tracked helloSequenceEnabled: boolean = true
  toggleHello = () => (this.helloSequenceEnabled = !this.helloSequenceEnabled)
  clearHistory = () => (this.history = [])
  addToHistory = (action: string) => {
    this.lastSequence = action
    this.history = [...this.history.slice(-9), action]
  }
  goToTop: HotkeyCallback = () => this.addToHistory('gg → Go to top')
  goToBottom: HotkeyCallback = () => this.addToHistory('G → Go to bottom')
  deleteLine: HotkeyCallback = () => this.addToHistory('dd → Delete line')
  copyLine: HotkeyCallback = () => this.addToHistory('yy → Yank (copy) line')
  deleteWord: HotkeyCallback = () => this.addToHistory('dw → Delete word')
  changeInnerWord: HotkeyCallback = () =>
    this.addToHistory('ciw → Change inner word')
  konamiCode: HotkeyCallback = () =>
    this.addToHistory('↑↑↓↓ → Konami code (partial)')
  sideToSide: HotkeyCallback = () => this.addToHistory('←→←→ → Side to side!')
  sayHello: HotkeyCallback = () => this.addToHistory('hello → Hello World!')
  get shiftedSequence(): HotkeySequence {
    return ['Shift+[KeyR]', 'Shift+[KeyT]']
  }
  shiftedChain: HotkeyCallback = () =>
    this.addToHistory('⇧R ⇧T → Chained Shift+letter (2 steps)')
  onEscape: HotkeyCallback = () => {
    this.lastSequence = null
    this.history = []
  }

  <template>
    {{useHotkeySequence (array 'G' 'G') this.goToTop}}
    {{useHotkeySequence (array 'Shift+G') this.goToBottom}}
    {{useHotkeySequence (array 'D' 'D') this.deleteLine}}
    {{useHotkeySequence (array 'Y' 'Y') this.copyLine}}
    {{useHotkeySequence (array 'D' 'W') this.deleteWord}}
    {{useHotkeySequence (array 'C' 'I' 'W') this.changeInnerWord}}
    {{useHotkeySequence
      (array 'ArrowUp' 'ArrowUp' 'ArrowDown' 'ArrowDown')
      this.konamiCode
      timeout=1500
    }}
    {{useHotkeySequence
      (array 'ArrowLeft' 'ArrowRight' 'ArrowLeft' 'ArrowRight')
      this.sideToSide
      timeout=1500
    }}
    {{useHotkeySequence
      (array 'H' 'E' 'L' 'L' 'O')
      this.sayHello
      enabled=this.helloSequenceEnabled
    }}
    {{useHotkeySequence this.shiftedSequence this.shiftedChain}}
    {{useHotkey 'Escape' this.onEscape}}
    <div class='app'>
      <header>
        <h1>useHotkeySequence</h1>
        <p>
          Register multi-key sequences (like Vim commands). Keys must be pressed
          within the timeout window (default: 1000ms).
        </p>
      </header>

      <main>
        <section class='demo-section'>
          <h2>Vim-Style Commands</h2>
          <table class='sequence-table'>
            <thead>
              <tr>
                <th>Sequence</th>
                <th>Action</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>
                  <kbd>g</kbd>
                  <kbd>g</kbd>
                </td>
                <td>Go to top</td>
              </tr>
              <tr>
                <td>
                  <kbd>G</kbd>
                  (Shift+G)
                </td>
                <td>Go to bottom</td>
              </tr>
              <tr>
                <td>
                  <kbd>d</kbd>
                  <kbd>d</kbd>
                </td>
                <td>Delete line</td>
              </tr>
              <tr>
                <td>
                  <kbd>y</kbd>
                  <kbd>y</kbd>
                </td>
                <td>Yank (copy) line</td>
              </tr>
              <tr>
                <td>
                  <kbd>d</kbd>
                  <kbd>w</kbd>
                </td>
                <td>Delete word</td>
              </tr>
              <tr>
                <td>
                  <kbd>c</kbd>
                  <kbd>i</kbd>
                  <kbd>w</kbd>
                </td>
                <td>Change inner word</td>
              </tr>
            </tbody>
          </table>
        </section>

        <section class='demo-section'>
          <h2>Fun Sequences</h2>
          <div class='fun-sequences'>
            <div class='sequence-card'>
              <h3>Konami Code (Partial)</h3>
              <p>
                <kbd>↑</kbd>
                <kbd>↑</kbd>
                <kbd>↓</kbd>
                <kbd>↓</kbd>
              </p>
              <span class='hint'>Use arrow keys within 1.5 seconds</span>
            </div>
            <div class='sequence-card'>
              <h3>Side to Side</h3>
              <p>
                <kbd>←</kbd>
                <kbd>→</kbd>
                <kbd>←</kbd>
                <kbd>→</kbd>
              </p>
              <span class='hint'>Arrow keys within 1.5 seconds</span>
            </div>
            <div class='sequence-card'>
              <h3>Spell It Out</h3>
              <p>
                <kbd>h</kbd>
                <kbd>e</kbd>
                <kbd>l</kbd>
                <kbd>l</kbd>
                <kbd>o</kbd>
              </p>
              <span class='hint'>Type "hello" quickly</span>
              <p class='sequence-toggle-status'>
                This sequence is
                <strong>{{if
                    this.helloSequenceEnabled
                    'enabled'
                    'disabled'
                  }}</strong>
                .
              </p>
              <button type='button' {{on 'click' this.toggleHello}}>
                {{if this.helloSequenceEnabled 'Disable' 'Enable'}}
                sequence
              </button>
            </div>
          </div>
        </section>

        {{#if this.lastSequence}}<div class='info-box success'>
            <strong>Triggered:</strong>
            {{this.lastSequence}}
          </div>{{/if}}

        <section class='demo-section'>
          <h2>Input handling</h2>
          <p>
            Sequences are not detected when typing in text inputs, textareas,
            selects, or contenteditable elements. Button-type inputs (
            <code>type="button"</code>,
            <code>submit</code>,
            <code>reset</code>) still receive sequences. Focus the input below and
            try
            <kbd>g</kbd>
            <kbd>g</kbd>
            or
            <kbd>h</kbd>
            <kbd>e</kbd>
            <kbd>l</kbd>
            <kbd>l</kbd>
            <kbd>o</kbd>
            — nothing will trigger. Click outside to try again.
          </p>
          <input
            type='text'
            class='demo-input'
            placeholder="Focus here – sequences won't trigger while typing..."
          />
        </section>

        <section class='demo-section'>
          <h2>Chained Shift+letter sequences</h2>
          <p>
            These steps use physical R and T positions (<code>[KeyR]</code>
            and
            <code>[KeyT]</code>), independent of the characters they produce. Hold
            <kbd>Shift</kbd>
            and press each position. You can press
            <kbd>Shift</kbd>
            alone between steps—those modifier-only presses do not reset progress,
            so the next chord still counts. Holding a key does not count as
            additional steps.
          </p>
          <table class='sequence-table'>
            <thead>
              <tr>
                <th>Sequence</th>
                <th>Action</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>
                  <kbd>Shift</kbd>+<kbd>r</kbd>
                  then
                  <kbd>Shift</kbd>+
                  <kbd>t</kbd>
                </td>
                <td>Chained Shift+letter (2 steps)</td>
              </tr>
            </tbody>
          </table>
        </section>

        <section class='demo-section'>
          <h2>Usage</h2>
          <pre class='code-block'>{{this.usage0}}</pre>
        </section>

        {{#if (gt this.history.length 0)}}<section class='demo-section'>
            <h2>History</h2>
            <ul class='history-list'>
              {{#each this.history as |item i|}}<li>{{item}}</li>{{/each}}
            </ul>
            <button {{on 'click' this.clearHistory}}>Clear History</button>
          </section>{{/if}}

        <p class='hint'>
          Press
          <kbd>Escape</kbd>
          to clear history
        </p>
      </main>

    </div>
  </template>
}
export default App