@dealdeploy/skl 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/opentui/SKILL.md +198 -0
- package/.agents/skills/opentui/references/animation/REFERENCE.md +431 -0
- package/.agents/skills/opentui/references/components/REFERENCE.md +143 -0
- package/.agents/skills/opentui/references/components/code-diff.md +496 -0
- package/.agents/skills/opentui/references/components/containers.md +412 -0
- package/.agents/skills/opentui/references/components/inputs.md +531 -0
- package/.agents/skills/opentui/references/components/text-display.md +384 -0
- package/.agents/skills/opentui/references/core/REFERENCE.md +145 -0
- package/.agents/skills/opentui/references/core/api.md +506 -0
- package/.agents/skills/opentui/references/core/configuration.md +166 -0
- package/.agents/skills/opentui/references/core/gotchas.md +393 -0
- package/.agents/skills/opentui/references/core/patterns.md +448 -0
- package/.agents/skills/opentui/references/keyboard/REFERENCE.md +511 -0
- package/.agents/skills/opentui/references/layout/REFERENCE.md +337 -0
- package/.agents/skills/opentui/references/layout/patterns.md +444 -0
- package/.agents/skills/opentui/references/react/REFERENCE.md +174 -0
- package/.agents/skills/opentui/references/react/api.md +435 -0
- package/.agents/skills/opentui/references/react/configuration.md +301 -0
- package/.agents/skills/opentui/references/react/gotchas.md +443 -0
- package/.agents/skills/opentui/references/react/patterns.md +501 -0
- package/.agents/skills/opentui/references/solid/REFERENCE.md +201 -0
- package/.agents/skills/opentui/references/solid/api.md +543 -0
- package/.agents/skills/opentui/references/solid/configuration.md +315 -0
- package/.agents/skills/opentui/references/solid/gotchas.md +415 -0
- package/.agents/skills/opentui/references/solid/patterns.md +558 -0
- package/.agents/skills/opentui/references/testing/REFERENCE.md +614 -0
- package/.claude/skills/opentui/SKILL.md +198 -0
- package/.claude/skills/opentui/references/animation/REFERENCE.md +431 -0
- package/.claude/skills/opentui/references/components/REFERENCE.md +143 -0
- package/.claude/skills/opentui/references/components/code-diff.md +496 -0
- package/.claude/skills/opentui/references/components/containers.md +412 -0
- package/.claude/skills/opentui/references/components/inputs.md +531 -0
- package/.claude/skills/opentui/references/components/text-display.md +384 -0
- package/.claude/skills/opentui/references/core/REFERENCE.md +145 -0
- package/.claude/skills/opentui/references/core/api.md +506 -0
- package/.claude/skills/opentui/references/core/configuration.md +166 -0
- package/.claude/skills/opentui/references/core/gotchas.md +393 -0
- package/.claude/skills/opentui/references/core/patterns.md +448 -0
- package/.claude/skills/opentui/references/keyboard/REFERENCE.md +511 -0
- package/.claude/skills/opentui/references/layout/REFERENCE.md +337 -0
- package/.claude/skills/opentui/references/layout/patterns.md +444 -0
- package/.claude/skills/opentui/references/react/REFERENCE.md +174 -0
- package/.claude/skills/opentui/references/react/api.md +435 -0
- package/.claude/skills/opentui/references/react/configuration.md +301 -0
- package/.claude/skills/opentui/references/react/gotchas.md +443 -0
- package/.claude/skills/opentui/references/react/patterns.md +501 -0
- package/.claude/skills/opentui/references/solid/REFERENCE.md +201 -0
- package/.claude/skills/opentui/references/solid/api.md +543 -0
- package/.claude/skills/opentui/references/solid/configuration.md +315 -0
- package/.claude/skills/opentui/references/solid/gotchas.md +415 -0
- package/.claude/skills/opentui/references/solid/patterns.md +558 -0
- package/.claude/skills/opentui/references/testing/REFERENCE.md +614 -0
- package/index.ts +169 -38
- package/package.json +1 -1
- package/update.ts +87 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
# Keyboard Input Handling
|
|
2
|
+
|
|
3
|
+
How to handle keyboard input in OpenTUI applications.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
OpenTUI provides keyboard input handling through:
|
|
8
|
+
- **Core**: `renderer.keyInput` EventEmitter
|
|
9
|
+
- **React**: `useKeyboard()` hook
|
|
10
|
+
- **Solid**: `useKeyboard()` hook
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
Use this reference when you need keyboard shortcuts, focus-aware input handling, or custom keybindings.
|
|
15
|
+
|
|
16
|
+
## KeyEvent Object
|
|
17
|
+
|
|
18
|
+
All keyboard handlers receive a `KeyEvent` object:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
interface KeyEvent {
|
|
22
|
+
name: string // Key name: "a", "escape", "f1", etc.
|
|
23
|
+
sequence: string // Raw escape sequence
|
|
24
|
+
ctrl: boolean // Ctrl modifier held
|
|
25
|
+
shift: boolean // Shift modifier held
|
|
26
|
+
meta: boolean // Alt modifier held
|
|
27
|
+
option: boolean // Option modifier held (macOS)
|
|
28
|
+
eventType: "press" | "release" | "repeat"
|
|
29
|
+
repeated: boolean // Key is being held (repeat event)
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Basic Usage
|
|
34
|
+
|
|
35
|
+
### Core
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { createCliRenderer, type KeyEvent } from "@opentui/core"
|
|
39
|
+
|
|
40
|
+
const renderer = await createCliRenderer()
|
|
41
|
+
|
|
42
|
+
renderer.keyInput.on("keypress", (key: KeyEvent) => {
|
|
43
|
+
if (key.name === "escape") {
|
|
44
|
+
renderer.destroy()
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (key.ctrl && key.name === "s") {
|
|
49
|
+
saveDocument()
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### React
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { useKeyboard, useRenderer } from "@opentui/react"
|
|
58
|
+
|
|
59
|
+
function App() {
|
|
60
|
+
const renderer = useRenderer()
|
|
61
|
+
useKeyboard((key) => {
|
|
62
|
+
if (key.name === "escape") {
|
|
63
|
+
renderer.destroy()
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return <text>Press ESC to exit</text>
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
### Solid
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import { useKeyboard, useRenderer } from "@opentui/solid"
|
|
76
|
+
|
|
77
|
+
function App() {
|
|
78
|
+
const renderer = useRenderer()
|
|
79
|
+
useKeyboard((key) => {
|
|
80
|
+
if (key.name === "escape") {
|
|
81
|
+
renderer.destroy()
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return <text>Press ESC to exit</text>
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Key Names
|
|
90
|
+
|
|
91
|
+
### Alphabetic Keys
|
|
92
|
+
|
|
93
|
+
Lowercase: `a`, `b`, `c`, ... `z`
|
|
94
|
+
|
|
95
|
+
With Shift: Check `key.shift && key.name === "a"` for uppercase
|
|
96
|
+
|
|
97
|
+
### Numeric Keys
|
|
98
|
+
|
|
99
|
+
`0`, `1`, `2`, ... `9`
|
|
100
|
+
|
|
101
|
+
### Function Keys
|
|
102
|
+
|
|
103
|
+
`f1`, `f2`, `f3`, ... `f12`
|
|
104
|
+
|
|
105
|
+
### Special Keys
|
|
106
|
+
|
|
107
|
+
| Key Name | Description |
|
|
108
|
+
|----------|-------------|
|
|
109
|
+
| `escape` | Escape key |
|
|
110
|
+
| `enter` | Enter/Return |
|
|
111
|
+
| `return` | Enter/Return (alias) |
|
|
112
|
+
| `tab` | Tab key |
|
|
113
|
+
| `backspace` | Backspace |
|
|
114
|
+
| `delete` | Delete key |
|
|
115
|
+
| `space` | Spacebar |
|
|
116
|
+
|
|
117
|
+
### Arrow Keys
|
|
118
|
+
|
|
119
|
+
| Key Name | Description |
|
|
120
|
+
|----------|-------------|
|
|
121
|
+
| `up` | Up arrow |
|
|
122
|
+
| `down` | Down arrow |
|
|
123
|
+
| `left` | Left arrow |
|
|
124
|
+
| `right` | Right arrow |
|
|
125
|
+
|
|
126
|
+
### Navigation Keys
|
|
127
|
+
|
|
128
|
+
| Key Name | Description |
|
|
129
|
+
|----------|-------------|
|
|
130
|
+
| `home` | Home key |
|
|
131
|
+
| `end` | End key |
|
|
132
|
+
| `pageup` | Page Up |
|
|
133
|
+
| `pagedown` | Page Down |
|
|
134
|
+
| `insert` | Insert key |
|
|
135
|
+
|
|
136
|
+
## Modifier Keys
|
|
137
|
+
|
|
138
|
+
Check modifier properties on `KeyEvent`:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
renderer.keyInput.on("keypress", (key) => {
|
|
142
|
+
if (key.ctrl && key.name === "c") {
|
|
143
|
+
// Ctrl+C
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (key.shift && key.name === "tab") {
|
|
147
|
+
// Shift+Tab
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (key.meta && key.name === "s") {
|
|
151
|
+
// Alt+S (meta = Alt on most systems)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (key.option && key.name === "a") {
|
|
155
|
+
// Option+A (macOS)
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Modifier Combinations
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// Ctrl+Shift+S
|
|
164
|
+
if (key.ctrl && key.shift && key.name === "s") {
|
|
165
|
+
saveAs()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Ctrl+Alt+Delete (careful with system shortcuts!)
|
|
169
|
+
if (key.ctrl && key.meta && key.name === "delete") {
|
|
170
|
+
// ...
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Event Types
|
|
175
|
+
|
|
176
|
+
### Press Events (Default)
|
|
177
|
+
|
|
178
|
+
Normal key press:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
renderer.keyInput.on("keypress", (key) => {
|
|
182
|
+
if (key.eventType === "press") {
|
|
183
|
+
// Initial key press
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Repeat Events
|
|
189
|
+
|
|
190
|
+
Key held down:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
renderer.keyInput.on("keypress", (key) => {
|
|
194
|
+
if (key.eventType === "repeat" || key.repeated) {
|
|
195
|
+
// Key is being held
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Release Events
|
|
201
|
+
|
|
202
|
+
Key released (opt-in):
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
// React
|
|
206
|
+
useKeyboard(
|
|
207
|
+
(key) => {
|
|
208
|
+
if (key.eventType === "release") {
|
|
209
|
+
// Key released
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
{ release: true } // Enable release events
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
// Solid
|
|
216
|
+
useKeyboard(
|
|
217
|
+
(key) => {
|
|
218
|
+
if (key.eventType === "release") {
|
|
219
|
+
// Key released
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
{ release: true }
|
|
223
|
+
)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Patterns
|
|
227
|
+
|
|
228
|
+
### Navigation Menu
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
function Menu() {
|
|
232
|
+
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
233
|
+
const items = ["Home", "Settings", "Help", "Quit"]
|
|
234
|
+
|
|
235
|
+
useKeyboard((key) => {
|
|
236
|
+
switch (key.name) {
|
|
237
|
+
case "up":
|
|
238
|
+
case "k":
|
|
239
|
+
setSelectedIndex(i => Math.max(0, i - 1))
|
|
240
|
+
break
|
|
241
|
+
case "down":
|
|
242
|
+
case "j":
|
|
243
|
+
setSelectedIndex(i => Math.min(items.length - 1, i + 1))
|
|
244
|
+
break
|
|
245
|
+
case "enter":
|
|
246
|
+
handleSelect(items[selectedIndex])
|
|
247
|
+
break
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<box flexDirection="column">
|
|
253
|
+
{items.map((item, i) => (
|
|
254
|
+
<text
|
|
255
|
+
key={item}
|
|
256
|
+
fg={i === selectedIndex ? "#00FF00" : "#FFFFFF"}
|
|
257
|
+
>
|
|
258
|
+
{i === selectedIndex ? "> " : " "}{item}
|
|
259
|
+
</text>
|
|
260
|
+
))}
|
|
261
|
+
</box>
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Modal Escape
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
function Modal({ onClose, children }) {
|
|
270
|
+
useKeyboard((key) => {
|
|
271
|
+
if (key.name === "escape") {
|
|
272
|
+
onClose()
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<box border padding={2}>
|
|
278
|
+
{children}
|
|
279
|
+
</box>
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Vim-style Modes
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
function Editor() {
|
|
288
|
+
const [mode, setMode] = useState<"normal" | "insert">("normal")
|
|
289
|
+
const [content, setContent] = useState("")
|
|
290
|
+
|
|
291
|
+
useKeyboard((key) => {
|
|
292
|
+
if (mode === "normal") {
|
|
293
|
+
switch (key.name) {
|
|
294
|
+
case "i":
|
|
295
|
+
setMode("insert")
|
|
296
|
+
break
|
|
297
|
+
case "escape":
|
|
298
|
+
// Already in normal mode
|
|
299
|
+
break
|
|
300
|
+
case "j":
|
|
301
|
+
moveCursorDown()
|
|
302
|
+
break
|
|
303
|
+
case "k":
|
|
304
|
+
moveCursorUp()
|
|
305
|
+
break
|
|
306
|
+
}
|
|
307
|
+
} else if (mode === "insert") {
|
|
308
|
+
if (key.name === "escape") {
|
|
309
|
+
setMode("normal")
|
|
310
|
+
}
|
|
311
|
+
// Input component handles text in insert mode
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
return (
|
|
316
|
+
<box flexDirection="column">
|
|
317
|
+
<text>Mode: {mode}</text>
|
|
318
|
+
<textarea
|
|
319
|
+
value={content}
|
|
320
|
+
onChange={setContent}
|
|
321
|
+
focused={mode === "insert"}
|
|
322
|
+
/>
|
|
323
|
+
</box>
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Game Controls
|
|
329
|
+
|
|
330
|
+
```tsx
|
|
331
|
+
function Game() {
|
|
332
|
+
const [pressed, setPressed] = useState(new Set<string>())
|
|
333
|
+
|
|
334
|
+
useKeyboard(
|
|
335
|
+
(key) => {
|
|
336
|
+
setPressed(keys => {
|
|
337
|
+
const newKeys = new Set(keys)
|
|
338
|
+
if (key.eventType === "release") {
|
|
339
|
+
newKeys.delete(key.name)
|
|
340
|
+
} else {
|
|
341
|
+
newKeys.add(key.name)
|
|
342
|
+
}
|
|
343
|
+
return newKeys
|
|
344
|
+
})
|
|
345
|
+
},
|
|
346
|
+
{ release: true }
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
// Game logic uses pressed set
|
|
350
|
+
useEffect(() => {
|
|
351
|
+
if (pressed.has("up") || pressed.has("w")) {
|
|
352
|
+
moveUp()
|
|
353
|
+
}
|
|
354
|
+
if (pressed.has("down") || pressed.has("s")) {
|
|
355
|
+
moveDown()
|
|
356
|
+
}
|
|
357
|
+
}, [pressed])
|
|
358
|
+
|
|
359
|
+
return <text>WASD or arrows to move</text>
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Keyboard Shortcuts Help
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
function ShortcutsHelp() {
|
|
367
|
+
const shortcuts = [
|
|
368
|
+
{ keys: "Ctrl+S", action: "Save" },
|
|
369
|
+
{ keys: "Ctrl+Q", action: "Quit" },
|
|
370
|
+
{ keys: "Ctrl+F", action: "Find" },
|
|
371
|
+
{ keys: "Tab", action: "Next field" },
|
|
372
|
+
{ keys: "Shift+Tab", action: "Previous field" },
|
|
373
|
+
]
|
|
374
|
+
|
|
375
|
+
return (
|
|
376
|
+
<box border title="Keyboard Shortcuts" padding={1}>
|
|
377
|
+
{shortcuts.map(({ keys, action }) => (
|
|
378
|
+
<box key={keys} flexDirection="row">
|
|
379
|
+
<text width={15} fg="#00FFFF">{keys}</text>
|
|
380
|
+
<text>{action}</text>
|
|
381
|
+
</box>
|
|
382
|
+
))}
|
|
383
|
+
</box>
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Paste Events
|
|
389
|
+
|
|
390
|
+
Handle pasted text:
|
|
391
|
+
|
|
392
|
+
### Core
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
renderer.keyInput.on("paste", (text: string) => {
|
|
396
|
+
console.log("Pasted:", text)
|
|
397
|
+
})
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Solid
|
|
401
|
+
|
|
402
|
+
Solid provides a dedicated `usePaste` hook:
|
|
403
|
+
|
|
404
|
+
```tsx
|
|
405
|
+
import { usePaste } from "@opentui/solid"
|
|
406
|
+
|
|
407
|
+
function App() {
|
|
408
|
+
usePaste((event) => {
|
|
409
|
+
console.log("Pasted:", event.text)
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
return <text>Paste something</text>
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
> **Note**: `usePaste` is **Solid-only**. React does not have this hook - handle paste via the Core event emitter or input component's `onChange`.
|
|
417
|
+
|
|
418
|
+
## Clipboard API (OSC 52)
|
|
419
|
+
|
|
420
|
+
Copy text to the system clipboard using OSC 52 escape sequences. Works over SSH and in most modern terminal emulators.
|
|
421
|
+
|
|
422
|
+
### Core
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
// Copy to clipboard
|
|
426
|
+
const success = renderer.copyToClipboardOSC52("text to copy")
|
|
427
|
+
|
|
428
|
+
// Check if OSC 52 is supported
|
|
429
|
+
if (renderer.isOsc52Supported()) {
|
|
430
|
+
renderer.copyToClipboardOSC52("Hello!")
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Clear clipboard
|
|
434
|
+
renderer.clearClipboardOSC52()
|
|
435
|
+
|
|
436
|
+
// Target specific clipboard (X11)
|
|
437
|
+
import { ClipboardTarget } from "@opentui/core"
|
|
438
|
+
renderer.copyToClipboardOSC52("text", ClipboardTarget.Primary) // X11 primary
|
|
439
|
+
renderer.copyToClipboardOSC52("text", ClipboardTarget.Clipboard) // System clipboard (default)
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### From Selection
|
|
443
|
+
|
|
444
|
+
The Selection object provides a convenience method:
|
|
445
|
+
|
|
446
|
+
```tsx
|
|
447
|
+
// Solid
|
|
448
|
+
useSelectionHandler((selection) => {
|
|
449
|
+
selection.copyToClipboard() // Uses OSC 52 internally
|
|
450
|
+
})
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
## Focus and Input Components
|
|
454
|
+
|
|
455
|
+
Input components (`<input>`, `<textarea>`, `<select>`) capture keyboard events when focused:
|
|
456
|
+
|
|
457
|
+
```tsx
|
|
458
|
+
<input focused /> // Receives keyboard input
|
|
459
|
+
|
|
460
|
+
// Global useKeyboard still fires, but input consumes characters
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
To prevent conflicts, check if an input is focused before handling global shortcuts:
|
|
464
|
+
|
|
465
|
+
```tsx
|
|
466
|
+
function App() {
|
|
467
|
+
const renderer = useRenderer()
|
|
468
|
+
const [inputFocused, setInputFocused] = useState(false)
|
|
469
|
+
|
|
470
|
+
useKeyboard((key) => {
|
|
471
|
+
if (inputFocused) return // Let input handle it
|
|
472
|
+
|
|
473
|
+
// Global shortcuts
|
|
474
|
+
if (key.name === "escape") {
|
|
475
|
+
renderer.destroy()
|
|
476
|
+
}
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
return (
|
|
480
|
+
<input
|
|
481
|
+
focused={inputFocused}
|
|
482
|
+
onFocus={() => setInputFocused(true)}
|
|
483
|
+
onBlur={() => setInputFocused(false)}
|
|
484
|
+
/>
|
|
485
|
+
)
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## Gotchas
|
|
490
|
+
|
|
491
|
+
### Terminal Limitations
|
|
492
|
+
|
|
493
|
+
Some key combinations are captured by the terminal or OS:
|
|
494
|
+
- `Ctrl+C` often sends SIGINT (use `exitOnCtrlC: false` to handle)
|
|
495
|
+
- `Ctrl+Z` suspends the process
|
|
496
|
+
- Some function keys may be intercepted
|
|
497
|
+
|
|
498
|
+
### SSH and Remote Sessions
|
|
499
|
+
|
|
500
|
+
Key detection may vary over SSH. Test on target environments.
|
|
501
|
+
|
|
502
|
+
### Multiple Handlers
|
|
503
|
+
|
|
504
|
+
Multiple `useKeyboard` calls all receive events. Coordinate handlers to prevent conflicts.
|
|
505
|
+
|
|
506
|
+
## See Also
|
|
507
|
+
|
|
508
|
+
- [React API](../react/api.md) - `useKeyboard` hook reference
|
|
509
|
+
- [Solid API](../solid/api.md) - `useKeyboard` hook reference
|
|
510
|
+
- [Input Components](../components/inputs.md) - Focus management with input, textarea, select
|
|
511
|
+
- [Testing](../testing/REFERENCE.md) - Simulating key presses in tests
|