Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions examples/lit/hotkey-sequence/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
12 changes: 12 additions & 0 deletions examples/lit/hotkey-sequence/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>hotkey-sequence - TanStack Hotkey Lit example</title>
<script type="module" src="/src/app.ts"></script>
</head>
<body>
<my-app></my-app>
</body>
</html>
21 changes: 21 additions & 0 deletions examples/lit/hotkey-sequence/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@tanstack/hotkeys-example-lit-hotkey-sequence",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --port=3069",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"test:types": "tsc"
},
"dependencies": {
"@tanstack/lit-hotkeys": "^0.4.0",
"lit": "^3.3.1"
},
"devDependencies": {
"typescript": "~5.9.3",
"vite": "^7.3.1"
}
}
255 changes: 255 additions & 0 deletions examples/lit/hotkey-sequence/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import { LitElement, css, html, unsafeCSS } from 'lit'
import { customElement, state } from 'lit/decorators.js'

import { hotkey, hotkeySequence } from '@tanstack/lit-hotkeys'

import appStyles from './index.css?raw'
import type { HotkeyCallbackContext } from '@tanstack/lit-hotkeys'

@customElement('my-app')
export class MyApp extends LitElement {
static styles = [
css`
:host {
display: block;
margin: 0;
font-family:
system-ui,
-apple-system,
sans-serif;
background: #f5f5f5;
color: #333;
box-sizing: border-box;
}
:host *,
:host *::before,
:host *::after {
box-sizing: border-box;
}
`,
unsafeCSS(appStyles),
]

@state() private lastSequence: string | null = null
@state() private history: Array<string> = []

private addToHistory(action: string) {
this.lastSequence = action
this.history = [...this.history.slice(-9), action]
}

render() {
return html`
<div class="app">
<header>
<h1>@hotkeySequence</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>
</div>
</div>
</section>

${this.lastSequence
? html`
<div class="info-box success">
<strong>Triggered:</strong> ${this.lastSequence}
</div>
`
: null}

<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>Usage</h2>
<pre class="code-block">
${`import { hotkeySequence } from '@tanstack/lit-hotkeys'

class VimEditor extends LitElement {
// Basic sequence
@hotkeySequence(['G', 'G'])
goToTop() {
window.scrollTo(0, 0)
}

// With custom timeout (1.5 seconds)
@hotkeySequence(
['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown'],
{ timeout: 1500 }
)
activateCheatMode() {
// ...
}

// Three-key sequence
@hotkeySequence(['C', 'I', 'W'])
changeInnerWord() {
// ...
}
}`}</pre
>
</section>

${this.history.length > 0
? html`
<section class="demo-section">
<h2>History</h2>
<ul class="history-list">
${this.history.map((item) => html`<li>${item}</li>`)}
</ul>
<button @click=${() => (this.history = [])}>
Clear History
</button>
</section>
`
: null}

<p class="hint">Press <kbd>Escape</kbd> to clear history</p>
</main>
</div>
`
}

// ============================================================================
// Sequences
// ============================================================================

@hotkeySequence(['G', 'G'])
private _gg() {
this.addToHistory('gg β†’ Go to top')
}

@hotkeySequence(['Shift+G'])
private _shiftG() {
this.addToHistory('G β†’ Go to bottom')
}

@hotkeySequence(['D', 'D'])
private _dd() {
this.addToHistory('dd β†’ Delete line')
}

@hotkeySequence(['Y', 'Y'])
private _yy() {
this.addToHistory('yy β†’ Yank (copy) line')
}

@hotkeySequence(['D', 'W'])
private _dw() {
this.addToHistory('dw β†’ Delete word')
}

@hotkeySequence(['C', 'I', 'W'])
private _ciw() {
this.addToHistory('ciw β†’ Change inner word')
}

@hotkeySequence(['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown'], {
timeout: 1500,
})
private _konami() {
this.addToHistory('↑↑↓↓ β†’ Konami code (partial)')
}

@hotkeySequence(['ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight'], {
timeout: 1500,
})
private _sideToSide() {
this.addToHistory('←→←→ β†’ Side to side!')
}

@hotkeySequence(['H', 'E', 'L', 'L', 'O'])
private _hello() {
this.addToHistory('hello β†’ Hello World!')
}

@hotkey('Escape')
private _escape(_event: KeyboardEvent, _ctx: HotkeyCallbackContext) {
this.lastSequence = null
this.history = []
}
}

declare global {
interface HTMLElementTagNameMap {
'my-app': MyApp
}
}
Loading