silvery 0.3.0 → 0.4.1

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 (120) hide show
  1. package/README.md +41 -145
  2. package/dist/chalk.js +3 -0
  3. package/dist/chalk.js.map +11 -0
  4. package/dist/index.js +340 -0
  5. package/dist/index.js.map +282 -0
  6. package/dist/ink.js +129 -0
  7. package/dist/ink.js.map +140 -0
  8. package/dist/runtime.js +394 -0
  9. package/dist/runtime.js.map +286 -0
  10. package/dist/theme.js +343 -0
  11. package/dist/theme.js.map +286 -0
  12. package/dist/ui/animation.js +3 -0
  13. package/dist/ui/animation.js.map +15 -0
  14. package/dist/ui/ansi.js +3 -0
  15. package/dist/ui/ansi.js.map +10 -0
  16. package/dist/ui/cli.js +8 -0
  17. package/dist/ui/cli.js.map +14 -0
  18. package/dist/ui/display.js +4 -0
  19. package/dist/ui/display.js.map +10 -0
  20. package/dist/ui/image.js +4 -0
  21. package/dist/ui/image.js.map +15 -0
  22. package/dist/ui/input.js +3 -0
  23. package/dist/ui/input.js.map +11 -0
  24. package/dist/ui/progress.js +8 -0
  25. package/dist/ui/progress.js.map +20 -0
  26. package/dist/ui/react.js +3 -0
  27. package/dist/ui/react.js.map +15 -0
  28. package/dist/ui/utils.js +3 -0
  29. package/dist/ui/utils.js.map +10 -0
  30. package/dist/ui/wrappers.js +14 -0
  31. package/dist/ui/wrappers.js.map +19 -0
  32. package/dist/ui.js +17 -0
  33. package/dist/ui.js.map +20 -0
  34. package/package.json +67 -15
  35. package/src/index.ts +67 -1
  36. package/src/runtime.ts +4 -0
  37. package/src/theme.ts +4 -0
  38. package/src/ui/animation.ts +2 -0
  39. package/src/ui/ansi.ts +2 -0
  40. package/src/ui/cli.ts +2 -0
  41. package/src/ui/display.ts +2 -0
  42. package/src/ui/image.ts +2 -0
  43. package/src/ui/input.ts +2 -0
  44. package/src/ui/progress.ts +2 -0
  45. package/src/ui/react.ts +2 -0
  46. package/src/ui/utils.ts +2 -0
  47. package/src/ui/wrappers.ts +2 -0
  48. package/src/ui.ts +4 -0
  49. package/examples/CLAUDE.md +0 -75
  50. package/examples/_banner.tsx +0 -60
  51. package/examples/cli.ts +0 -228
  52. package/examples/index.md +0 -101
  53. package/examples/inline/inline-nontty.tsx +0 -98
  54. package/examples/inline/inline-progress.tsx +0 -79
  55. package/examples/inline/inline-simple.tsx +0 -63
  56. package/examples/inline/scrollback.tsx +0 -185
  57. package/examples/interactive/_input-debug.tsx +0 -110
  58. package/examples/interactive/_stdin-test.ts +0 -71
  59. package/examples/interactive/_textarea-bare.tsx +0 -45
  60. package/examples/interactive/aichat/components.tsx +0 -468
  61. package/examples/interactive/aichat/index.tsx +0 -207
  62. package/examples/interactive/aichat/script.ts +0 -460
  63. package/examples/interactive/aichat/state.ts +0 -326
  64. package/examples/interactive/aichat/types.ts +0 -19
  65. package/examples/interactive/app-todo.tsx +0 -198
  66. package/examples/interactive/async-data.tsx +0 -208
  67. package/examples/interactive/cli-wizard.tsx +0 -332
  68. package/examples/interactive/clipboard.tsx +0 -183
  69. package/examples/interactive/components.tsx +0 -463
  70. package/examples/interactive/data-explorer.tsx +0 -506
  71. package/examples/interactive/dev-tools.tsx +0 -379
  72. package/examples/interactive/explorer.tsx +0 -747
  73. package/examples/interactive/gallery.tsx +0 -652
  74. package/examples/interactive/inline-bench.tsx +0 -136
  75. package/examples/interactive/kanban.tsx +0 -267
  76. package/examples/interactive/layout-ref.tsx +0 -185
  77. package/examples/interactive/outline.tsx +0 -171
  78. package/examples/interactive/paste-demo.tsx +0 -198
  79. package/examples/interactive/scroll.tsx +0 -77
  80. package/examples/interactive/search-filter.tsx +0 -240
  81. package/examples/interactive/task-list.tsx +0 -279
  82. package/examples/interactive/terminal.tsx +0 -798
  83. package/examples/interactive/textarea.tsx +0 -103
  84. package/examples/interactive/theme.tsx +0 -336
  85. package/examples/interactive/transform.tsx +0 -256
  86. package/examples/interactive/virtual-10k.tsx +0 -413
  87. package/examples/kitty/canvas.tsx +0 -519
  88. package/examples/kitty/generate-samples.ts +0 -236
  89. package/examples/kitty/image-component.tsx +0 -273
  90. package/examples/kitty/images.tsx +0 -604
  91. package/examples/kitty/input.tsx +0 -371
  92. package/examples/kitty/keys.tsx +0 -378
  93. package/examples/kitty/paint.tsx +0 -1017
  94. package/examples/layout/dashboard.tsx +0 -551
  95. package/examples/layout/live-resize.tsx +0 -290
  96. package/examples/layout/overflow.tsx +0 -51
  97. package/examples/playground/README.md +0 -69
  98. package/examples/playground/build.ts +0 -61
  99. package/examples/playground/index.html +0 -420
  100. package/examples/playground/playground-app.tsx +0 -416
  101. package/examples/runtime/elm-counter.tsx +0 -206
  102. package/examples/runtime/hello-runtime.tsx +0 -73
  103. package/examples/runtime/pipe-composition.tsx +0 -184
  104. package/examples/runtime/run-counter.tsx +0 -78
  105. package/examples/runtime/runtime-counter.tsx +0 -197
  106. package/examples/screenshots/generate.tsx +0 -563
  107. package/examples/scrollback-perf.tsx +0 -230
  108. package/examples/viewer.tsx +0 -654
  109. package/examples/web/build.ts +0 -365
  110. package/examples/web/canvas-app.tsx +0 -80
  111. package/examples/web/canvas.html +0 -89
  112. package/examples/web/dom-app.tsx +0 -81
  113. package/examples/web/dom.html +0 -113
  114. package/examples/web/showcase-app.tsx +0 -107
  115. package/examples/web/showcase.html +0 -34
  116. package/examples/web/showcases/index.tsx +0 -56
  117. package/examples/web/viewer-app.tsx +0 -555
  118. package/examples/web/viewer.html +0 -30
  119. package/examples/web/xterm-app.tsx +0 -105
  120. package/examples/web/xterm.html +0 -118
@@ -1,378 +0,0 @@
1
- /**
2
- * Key Explorer
3
- *
4
- * Interactive key chord tester — press any key combination to see exactly
5
- * how the terminal reports it. Color-coded modifiers, live event log,
6
- * and a visual modifier dashboard make it easy to understand what your
7
- * terminal can do.
8
- *
9
- * Features:
10
- * - Legacy vs Kitty parsing differences
11
- * - All modifier fields (ctrl, alt, shift, super, hyper, capsLock, numLock)
12
- * - Event types (press/repeat/release)
13
- * - shiftedKey, baseLayoutKey, associatedText
14
- * - macOS symbols in the display (⌘ ⌥ ⌃ ⇧ ✦)
15
- * - Kitty auto-detection on startup
16
- *
17
- * Run: bun vendor/silvery/examples/kitty/key-explorer.tsx
18
- */
19
-
20
- import React, { useState, useRef, useEffect } from "react"
21
- import {
22
- render,
23
- Box,
24
- Text,
25
- useInput,
26
- useApp,
27
- createTerm,
28
- parseKeypress,
29
- KittyFlags,
30
- enableKittyKeyboard,
31
- disableKittyKeyboard,
32
- detectKittyFromStdio,
33
- type Key,
34
- type ParsedKeypress,
35
- } from "../../src/index.js"
36
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
37
-
38
- export const meta: ExampleMeta = {
39
- name: "Key Events",
40
- description: "Interactive key chord tester with color-coded modifiers",
41
- features: ["parseKeypress()", "detectKittySupport()", "⌘ ⌥ ⌃ ⇧ ✦ symbols", "KittyFlags"],
42
- }
43
-
44
- // eventType is already a string ("press" | "repeat" | "release")
45
-
46
- interface KeyEvent {
47
- index: number
48
- input: string
49
- key: Key
50
- parsed: ParsedKeypress
51
- raw: string
52
- }
53
-
54
- /** Modifier definition with display name, symbol, and color */
55
- interface ModDef {
56
- symbol: string
57
- label: string
58
- color: string
59
- }
60
-
61
- const MODIFIER_DEFS: ModDef[] = [
62
- { symbol: "⌃", label: "Ctrl", color: "red" },
63
- { symbol: "⇧", label: "Shift", color: "yellow" },
64
- { symbol: "⌥", label: "Alt", color: "blue" },
65
- { symbol: "⌘", label: "Super", color: "green" },
66
- { symbol: "✦", label: "Hyper", color: "magenta" },
67
- ]
68
-
69
- function KeyExplorer({ kittySupported }: { kittySupported: boolean }): JSX.Element {
70
- const { exit } = useApp()
71
- const [events, setEvents] = useState<KeyEvent[]>([])
72
- const [latest, setLatest] = useState<KeyEvent | null>(null)
73
- const counterRef = useRef(0)
74
- const stdin = process.stdin
75
-
76
- // Listen to raw stdin for full ParsedKeypress info
77
- useEffect(() => {
78
- const onData = (data: Buffer) => {
79
- const raw = data.toString()
80
- // Skip mouse sequences
81
- if (raw.startsWith("\x1b[<")) return
82
-
83
- const parsed = parseKeypress(raw)
84
- // Don't log the quit key
85
- if (parsed.name === "escape" || (raw === "q" && !parsed.ctrl && !parsed.meta)) return
86
-
87
- counterRef.current++
88
- const [input, key] = parseInputKey(raw)
89
- const event: KeyEvent = {
90
- index: counterRef.current,
91
- input,
92
- key,
93
- parsed,
94
- raw,
95
- }
96
- setLatest(event)
97
- setEvents((prev) => [...prev.slice(-14), event])
98
- }
99
-
100
- stdin.on("data", onData)
101
- return () => {
102
- stdin.off("data", onData)
103
- }
104
- }, [stdin])
105
-
106
- useInput((input: string, key: Key) => {
107
- if (input === "q" || key.escape) {
108
- exit()
109
- }
110
- })
111
-
112
- return (
113
- <Box flexDirection="column" padding={1}>
114
- {/* Status bar */}
115
- <Box gap={2} marginBottom={1}>
116
- <Text>
117
- <Text bold color="cyan">
118
- Protocol:
119
- </Text>{" "}
120
- {kittySupported ? (
121
- <Text color="green">Kitty keyboard enabled</Text>
122
- ) : (
123
- <Text color="yellow">Legacy mode (terminal does not support Kitty)</Text>
124
- )}
125
- </Text>
126
- </Box>
127
-
128
- <Box gap={4}>
129
- {/* Left panel: Current key details */}
130
- <Box flexDirection="column" width={50}>
131
- <Text bold color="cyan">
132
- Last Key Pressed
133
- </Text>
134
- <Box height={1} />
135
- {latest ? (
136
- <KeyDetails event={latest} />
137
- ) : (
138
- <Box flexDirection="column">
139
- <Text color="cyan">Try pressing some key combinations:</Text>
140
- <Box height={1} />
141
- <Text> Ctrl+A, Shift+Tab, Alt+Enter...</Text>
142
- {kittySupported && <Text> Cmd+S, Hyper+X (Kitty-only)</Text>}
143
- <Box height={1} />
144
- <Text dim>Each keypress shows its full breakdown here.</Text>
145
- </Box>
146
- )}
147
- </Box>
148
-
149
- {/* Right panel: Event log */}
150
- <Box flexDirection="column" width={42}>
151
- <Text bold color="cyan">
152
- Event Log
153
- </Text>
154
- <Text dim>
155
- {counterRef.current} {counterRef.current === 1 ? "event" : "events"} captured
156
- </Text>
157
- <Box height={1} />
158
- {events.length === 0 ? (
159
- <Text dim>Waiting for input...</Text>
160
- ) : (
161
- events.map((e, i) => (
162
- <Text key={i} dimColor={i < events.length - 1}>
163
- <Text dim>#{String(e.index).padStart(3)}</Text> {formatEventSummary(e)}
164
- </Text>
165
- ))
166
- )}
167
- </Box>
168
- </Box>
169
- </Box>
170
- )
171
- }
172
-
173
- function KeyDetails({ event }: { event: KeyEvent }): JSX.Element {
174
- const { parsed, raw } = event
175
-
176
- // Determine which modifiers are active
177
- const modActive: boolean[] = [parsed.ctrl, parsed.shift, parsed.meta || parsed.option, parsed.super, parsed.hyper]
178
-
179
- return (
180
- <Box flexDirection="column">
181
- {/* Key name - big and prominent */}
182
- <Text>
183
- <Text bold>Name:</Text>{" "}
184
- <Text bold color="white">
185
- {parsed.name || "(none)"}
186
- </Text>
187
- </Text>
188
- <Text>
189
- <Text bold>Input:</Text> {JSON.stringify(event.input)}
190
- </Text>
191
-
192
- {/* Color-coded modifier dashboard */}
193
- <Box marginTop={1}>
194
- <Box gap={1}>
195
- {MODIFIER_DEFS.map((mod, i) => (
196
- <ModBadge key={mod.symbol} mod={mod} active={modActive[i]!} />
197
- ))}
198
- </Box>
199
- </Box>
200
-
201
- {/* Event type (Kitty-only) */}
202
- {parsed.eventType && (
203
- <Box marginTop={1}>
204
- <Text>
205
- <Text bold>Event type:</Text> <Text color="magenta">{parsed.eventType}</Text>
206
- </Text>
207
- </Box>
208
- )}
209
-
210
- {/* Kitty-specific fields */}
211
- <Box flexDirection="column" marginTop={1}>
212
- <Text bold dim>
213
- Kitty Extensions
214
- </Text>
215
- <KeyField label="shiftedKey" value={parsed.shiftedKey} />
216
- <KeyField label="baseLayoutKey" value={parsed.baseLayoutKey} />
217
- <KeyField label="associatedText" value={parsed.associatedText} />
218
- <KeyField label="capsLock" value={parsed.capsLock} />
219
- <KeyField label="numLock" value={parsed.numLock} />
220
- </Box>
221
-
222
- {/* Raw sequence */}
223
- <Box marginTop={1}>
224
- <Text>
225
- <Text bold>Raw:</Text>{" "}
226
- <Text dim>
227
- {[...raw]
228
- .map((c) =>
229
- c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127
230
- ? `\\x${c.charCodeAt(0).toString(16).padStart(2, "0")}`
231
- : c,
232
- )
233
- .join("")}
234
- </Text>
235
- </Text>
236
- </Box>
237
- <Text>
238
- <Text bold>Sequence:</Text> <Text dim>{JSON.stringify(parsed.sequence)}</Text>
239
- </Text>
240
- </Box>
241
- )
242
- }
243
-
244
- function ModBadge({ mod, active }: { mod: ModDef; active: boolean }): JSX.Element {
245
- if (active) {
246
- return (
247
- <Text backgroundColor={mod.color as any} color="white" bold>
248
- {` ${mod.symbol} ${mod.label} `}
249
- </Text>
250
- )
251
- }
252
- return (
253
- <Text dim color="gray">
254
- {` ${mod.symbol} `}
255
- </Text>
256
- )
257
- }
258
-
259
- function KeyField({ label, value }: { label: string; value: string | boolean | undefined }): JSX.Element {
260
- if (value === undefined) {
261
- return <Text dim>{label}: --</Text>
262
- }
263
- return (
264
- <Text>
265
- {label}: <Text color="yellow">{String(value)}</Text>
266
- </Text>
267
- )
268
- }
269
-
270
- function formatEventSummary(event: KeyEvent): string {
271
- const parts: string[] = []
272
- const { parsed } = event
273
- if (parsed.ctrl) parts.push("⌃")
274
- if (parsed.shift) parts.push("⇧")
275
- if (parsed.meta || parsed.option) parts.push("⌥")
276
- if (parsed.super) parts.push("⌘")
277
- if (parsed.hyper) parts.push("✦")
278
- parts.push(parsed.name || JSON.stringify(event.input))
279
- if (parsed.eventType) parts.push(`(${parsed.eventType})`)
280
- return parts.join("")
281
- }
282
-
283
- /** Parse raw input into [input, Key] using the same logic as the runtime */
284
- function parseInputKey(raw: string): [string, Key] {
285
- const parsed = parseKeypress(raw)
286
- const key: Key = {
287
- upArrow: parsed.name === "up",
288
- downArrow: parsed.name === "down",
289
- leftArrow: parsed.name === "left",
290
- rightArrow: parsed.name === "right",
291
- pageDown: parsed.name === "pagedown",
292
- pageUp: parsed.name === "pageup",
293
- home: parsed.name === "home",
294
- end: parsed.name === "end",
295
- return: parsed.name === "return",
296
- escape: parsed.name === "escape",
297
- ctrl: parsed.ctrl,
298
- shift: parsed.shift,
299
- tab: parsed.name === "tab",
300
- backspace: parsed.name === "backspace",
301
- delete: parsed.name === "delete",
302
- meta: parsed.meta || parsed.option,
303
- super: parsed.super,
304
- hyper: parsed.hyper,
305
- eventType: parsed.eventType,
306
- }
307
-
308
- // For printable chars, input is the character itself
309
- const input = parsed.name.length === 1 ? parsed.name : ""
310
- return [input, key]
311
- }
312
-
313
- async function main() {
314
- const cleanup = () => {
315
- const stdout = process.stdout
316
- stdout.write("\x1b[?1003l\x1b[?1006l") // Disable mouse
317
- stdout.write("\x1b[?25h") // Show cursor
318
- stdout.write("\x1b[?1049l") // Exit alternate screen
319
- stdout.write("\x1b[0m") // Reset colors
320
- if (process.stdin.isTTY && process.stdin.isRaw) {
321
- try {
322
- process.stdin.setRawMode(false)
323
- } catch {}
324
- }
325
- }
326
- process.on("uncaughtException", (err) => {
327
- cleanup()
328
- throw err
329
- })
330
-
331
- // Detect Kitty support before starting the app
332
- const kittyResult = await detectKittyFromStdio(process.stdout, process.stdin)
333
-
334
- // Enable Kitty with all reporting flags if supported
335
- if (kittyResult.supported) {
336
- const flags =
337
- KittyFlags.DISAMBIGUATE |
338
- KittyFlags.REPORT_EVENTS |
339
- KittyFlags.REPORT_ALTERNATE |
340
- KittyFlags.REPORT_ALL_KEYS |
341
- KittyFlags.REPORT_TEXT
342
- process.stdout.write(enableKittyKeyboard(flags))
343
- }
344
-
345
- using term = createTerm()
346
- const { waitUntilExit } = await render(
347
- <ExampleBanner meta={meta} controls="q/Esc quit">
348
- <KeyExplorer kittySupported={kittyResult.supported} />
349
- </ExampleBanner>,
350
- term,
351
- )
352
- await waitUntilExit()
353
-
354
- // Cleanup: disable Kitty protocol
355
- if (kittyResult.supported) {
356
- process.stdout.write(disableKittyKeyboard())
357
- }
358
- }
359
-
360
- if (import.meta.main) {
361
- try {
362
- await main()
363
- } catch (err) {
364
- // Restore terminal on crash
365
- const stdout = process.stdout
366
- stdout.write("\x1b[?1003l\x1b[?1006l") // Disable mouse
367
- stdout.write("\x1b[?25h") // Show cursor
368
- stdout.write("\x1b[?1049l") // Exit alternate screen
369
- stdout.write("\x1b[0m") // Reset colors
370
- if (process.stdin.isTTY && process.stdin.isRaw) {
371
- try {
372
- process.stdin.setRawMode(false)
373
- } catch {}
374
- }
375
- console.error(err)
376
- process.exit(1)
377
- }
378
- }