@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.
Files changed (55) hide show
  1. package/.agents/skills/opentui/SKILL.md +198 -0
  2. package/.agents/skills/opentui/references/animation/REFERENCE.md +431 -0
  3. package/.agents/skills/opentui/references/components/REFERENCE.md +143 -0
  4. package/.agents/skills/opentui/references/components/code-diff.md +496 -0
  5. package/.agents/skills/opentui/references/components/containers.md +412 -0
  6. package/.agents/skills/opentui/references/components/inputs.md +531 -0
  7. package/.agents/skills/opentui/references/components/text-display.md +384 -0
  8. package/.agents/skills/opentui/references/core/REFERENCE.md +145 -0
  9. package/.agents/skills/opentui/references/core/api.md +506 -0
  10. package/.agents/skills/opentui/references/core/configuration.md +166 -0
  11. package/.agents/skills/opentui/references/core/gotchas.md +393 -0
  12. package/.agents/skills/opentui/references/core/patterns.md +448 -0
  13. package/.agents/skills/opentui/references/keyboard/REFERENCE.md +511 -0
  14. package/.agents/skills/opentui/references/layout/REFERENCE.md +337 -0
  15. package/.agents/skills/opentui/references/layout/patterns.md +444 -0
  16. package/.agents/skills/opentui/references/react/REFERENCE.md +174 -0
  17. package/.agents/skills/opentui/references/react/api.md +435 -0
  18. package/.agents/skills/opentui/references/react/configuration.md +301 -0
  19. package/.agents/skills/opentui/references/react/gotchas.md +443 -0
  20. package/.agents/skills/opentui/references/react/patterns.md +501 -0
  21. package/.agents/skills/opentui/references/solid/REFERENCE.md +201 -0
  22. package/.agents/skills/opentui/references/solid/api.md +543 -0
  23. package/.agents/skills/opentui/references/solid/configuration.md +315 -0
  24. package/.agents/skills/opentui/references/solid/gotchas.md +415 -0
  25. package/.agents/skills/opentui/references/solid/patterns.md +558 -0
  26. package/.agents/skills/opentui/references/testing/REFERENCE.md +614 -0
  27. package/.claude/skills/opentui/SKILL.md +198 -0
  28. package/.claude/skills/opentui/references/animation/REFERENCE.md +431 -0
  29. package/.claude/skills/opentui/references/components/REFERENCE.md +143 -0
  30. package/.claude/skills/opentui/references/components/code-diff.md +496 -0
  31. package/.claude/skills/opentui/references/components/containers.md +412 -0
  32. package/.claude/skills/opentui/references/components/inputs.md +531 -0
  33. package/.claude/skills/opentui/references/components/text-display.md +384 -0
  34. package/.claude/skills/opentui/references/core/REFERENCE.md +145 -0
  35. package/.claude/skills/opentui/references/core/api.md +506 -0
  36. package/.claude/skills/opentui/references/core/configuration.md +166 -0
  37. package/.claude/skills/opentui/references/core/gotchas.md +393 -0
  38. package/.claude/skills/opentui/references/core/patterns.md +448 -0
  39. package/.claude/skills/opentui/references/keyboard/REFERENCE.md +511 -0
  40. package/.claude/skills/opentui/references/layout/REFERENCE.md +337 -0
  41. package/.claude/skills/opentui/references/layout/patterns.md +444 -0
  42. package/.claude/skills/opentui/references/react/REFERENCE.md +174 -0
  43. package/.claude/skills/opentui/references/react/api.md +435 -0
  44. package/.claude/skills/opentui/references/react/configuration.md +301 -0
  45. package/.claude/skills/opentui/references/react/gotchas.md +443 -0
  46. package/.claude/skills/opentui/references/react/patterns.md +501 -0
  47. package/.claude/skills/opentui/references/solid/REFERENCE.md +201 -0
  48. package/.claude/skills/opentui/references/solid/api.md +543 -0
  49. package/.claude/skills/opentui/references/solid/configuration.md +315 -0
  50. package/.claude/skills/opentui/references/solid/gotchas.md +415 -0
  51. package/.claude/skills/opentui/references/solid/patterns.md +558 -0
  52. package/.claude/skills/opentui/references/testing/REFERENCE.md +614 -0
  53. package/index.ts +169 -38
  54. package/package.json +1 -1
  55. 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