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,371 +0,0 @@
1
- /**
2
- * Rich Input
3
- *
4
- * Combined keyboard + mouse showcase demonstrating all rich terminal input
5
- * features: Kitty protocol modifiers, mouse tracking, mode switching, and
6
- * a live event log.
7
- *
8
- * Features:
9
- * - Keybinding display using parseHotkey with ⌘ ⌥ ⌃ ⇧ ✦ symbols
10
- * - Mouse-clickable UI elements
11
- * - Mode switching (normal/insert)
12
- * - Event log showing all input events
13
- * - Kitty auto-detection
14
- *
15
- * Run: bun vendor/silvery/examples/kitty/rich-input.tsx
16
- */
17
-
18
- import React, { useState, useRef, useEffect } from "react"
19
- import {
20
- render,
21
- Box,
22
- Text,
23
- useInput,
24
- useApp,
25
- createTerm,
26
- parseHotkey,
27
- parseMouseSequence,
28
- isMouseSequence,
29
- KittyFlags,
30
- enableKittyKeyboard,
31
- disableKittyKeyboard,
32
- enableMouse,
33
- disableMouse,
34
- detectKittyFromStdio,
35
- type Key,
36
- type ParsedMouse,
37
- } from "../../src/index.js"
38
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
39
-
40
- export const meta: ExampleMeta = {
41
- name: "Mouse & Keys",
42
- description: "Combined keyboard + mouse input showcase with Kitty protocol",
43
- features: ["parseHotkey()", "parseMouseSequence()", "⌘ ⌥ ⌃ ⇧ ✦"],
44
- }
45
-
46
- type Mode = "normal" | "insert"
47
- type EventEntry = {
48
- index: number
49
- type: "key" | "mouse"
50
- summary: string
51
- color?: string
52
- }
53
-
54
- // Keybinding definitions with macOS symbols
55
- const KEYBINDINGS = [
56
- { hotkey: "i", action: "Enter insert mode", mode: "normal" as const },
57
- { hotkey: "Escape", action: "Return to normal mode", mode: "insert" as const },
58
- { hotkey: "⌃c", action: "Quit", mode: "both" as const },
59
- { hotkey: "j", action: "Move down", mode: "normal" as const },
60
- { hotkey: "k", action: "Move up", mode: "normal" as const },
61
- { hotkey: "⇧J", action: "Move item down", mode: "normal" as const },
62
- { hotkey: "⇧K", action: "Move item up", mode: "normal" as const },
63
- { hotkey: "⌘s", action: "Save (Kitty only)", mode: "both" as const },
64
- { hotkey: "✦⌘x", action: "Special action (Kitty only)", mode: "normal" as const },
65
- { hotkey: "q", action: "Quit", mode: "normal" as const },
66
- ]
67
-
68
- const ITEMS = ["Inbox", "Today", "Upcoming", "Projects", "Archive", "Trash"]
69
-
70
- function RichInputDemo({ kittySupported }: { kittySupported: boolean }): JSX.Element {
71
- const { exit } = useApp()
72
- const stdin = process.stdin
73
- const [mode, setMode] = useState<Mode>("normal")
74
- const [cursor, setCursor] = useState(0)
75
- const [events, setEvents] = useState<EventEntry[]>([])
76
- const [insertText, setInsertText] = useState("")
77
- const [mousePos, setMousePos] = useState<{ x: number; y: number } | null>(null)
78
- const counterRef = useRef(0)
79
-
80
- // Enable mouse tracking
81
- useEffect(() => {
82
- process.stdout.write(enableMouse())
83
- return () => {
84
- process.stdout.write(disableMouse())
85
- }
86
- }, [])
87
-
88
- function addEvent(type: "key" | "mouse", summary: string, color?: string) {
89
- counterRef.current++
90
- setEvents((prev) => [...prev.slice(-18), { index: counterRef.current, type, summary, color }])
91
- }
92
-
93
- // Listen to raw stdin for mouse events + Kitty key details
94
- useEffect(() => {
95
- const onData = (data: Buffer) => {
96
- const raw = data.toString()
97
-
98
- if (isMouseSequence(raw)) {
99
- const parsed = parseMouseSequence(raw)
100
- if (!parsed) return
101
-
102
- setMousePos({ x: parsed.x, y: parsed.y })
103
-
104
- const mods: string[] = []
105
- if (parsed.ctrl) mods.push("⌃")
106
- if (parsed.shift) mods.push("⇧")
107
- if (parsed.meta) mods.push("⌥")
108
- const modStr = mods.length > 0 ? mods.join("") + " " : ""
109
-
110
- if (parsed.action === "down") {
111
- const btn = ["Left", "Middle", "Right"][parsed.button] ?? `Btn${parsed.button}`
112
- addEvent("mouse", `${modStr}${btn} click at (${parsed.x},${parsed.y})`, "blue")
113
- } else if (parsed.action === "wheel") {
114
- addEvent(
115
- "mouse",
116
- `${modStr}Scroll ${parsed.delta! < 0 ? "up" : "down"} at (${parsed.x},${parsed.y})`,
117
- "magenta",
118
- )
119
- }
120
- }
121
- }
122
-
123
- stdin.on("data", onData)
124
- return () => {
125
- stdin.off("data", onData)
126
- }
127
- }, [stdin])
128
-
129
- useInput((input: string, key: Key) => {
130
- // Always: Ctrl+C or q (in normal mode) to quit
131
- if (key.ctrl && input === "c") {
132
- addEvent("key", "⌃C Quit", "red")
133
- exit()
134
- return
135
- }
136
-
137
- if (mode === "normal") {
138
- if (input === "q") {
139
- addEvent("key", "q Quit", "red")
140
- exit()
141
- return
142
- }
143
- if (input === "i") {
144
- setMode("insert")
145
- setInsertText("")
146
- addEvent("key", "i Enter insert mode", "green")
147
- return
148
- }
149
- if (input === "j" || key.downArrow) {
150
- setCursor((c) => Math.min(c + 1, ITEMS.length - 1))
151
- addEvent("key", `${input === "j" ? "j" : "Arrow"} Move down`)
152
- return
153
- }
154
- if (input === "k" || key.upArrow) {
155
- setCursor((c) => Math.max(c - 1, 0))
156
- addEvent("key", `${input === "k" ? "k" : "Arrow"} Move up`)
157
- return
158
- }
159
- if (input === "J" && key.shift) {
160
- addEvent("key", "⇧J Move item down", "yellow")
161
- return
162
- }
163
- if (input === "K" && key.shift) {
164
- addEvent("key", "⇧K Move item up", "yellow")
165
- return
166
- }
167
- // Kitty-only: Super modifier
168
- if (key.super && input === "s") {
169
- addEvent("key", "⌘S Save", "green")
170
- return
171
- }
172
- if (key.hyper && key.super && input === "x") {
173
- addEvent("key", "✦⌘X Special action!", "magenta")
174
- return
175
- }
176
-
177
- // Log unhandled keys
178
- if (input && input >= " ") {
179
- addEvent("key", `${input} (unbound)`, "gray")
180
- }
181
- } else if (mode === "insert") {
182
- if (key.escape) {
183
- setMode("normal")
184
- addEvent("key", "Esc Normal mode", "green")
185
- return
186
- }
187
- if (key.backspace) {
188
- setInsertText((t) => t.slice(0, -1))
189
- addEvent("key", "Backspace", "gray")
190
- return
191
- }
192
- if (input && input >= " ") {
193
- setInsertText((t) => t + input)
194
- addEvent("key", `'${input}'`, "gray")
195
- return
196
- }
197
- }
198
- })
199
-
200
- return (
201
- <Box flexDirection="column" padding={1}>
202
- {/* Top bar */}
203
- <Box gap={2} marginBottom={1}>
204
- <Text>
205
- <Text bold>Mode:</Text>{" "}
206
- <Text color={mode === "normal" ? "cyan" : "green"} bold>
207
- {mode.toUpperCase()}
208
- </Text>
209
- </Text>
210
- <Text>
211
- <Text bold>Kitty:</Text> {kittySupported ? <Text color="green">yes</Text> : <Text color="yellow">no</Text>}
212
- </Text>
213
- {mousePos && (
214
- <Text>
215
- <Text bold>Mouse:</Text> ({mousePos.x},{mousePos.y})
216
- </Text>
217
- )}
218
- </Box>
219
-
220
- <Box gap={2}>
221
- {/* Left: List + keybindings */}
222
- <Box flexDirection="column" width={35}>
223
- {/* Interactive list */}
224
- <Text bold color="cyan">
225
- Items
226
- </Text>
227
- <Box flexDirection="column" marginTop={1}>
228
- {ITEMS.map((item, i) => (
229
- <Text key={i}>
230
- <Text color={i === cursor ? "cyan" : "white"} bold={i === cursor}>
231
- {i === cursor ? ">" : " "} {item}
232
- </Text>
233
- </Text>
234
- ))}
235
- </Box>
236
-
237
- {/* Insert mode text */}
238
- {mode === "insert" && (
239
- <Box marginTop={1} borderStyle="single" borderColor="green" paddingX={1}>
240
- <Text>
241
- <Text bold color="green">
242
- Input:
243
- </Text>{" "}
244
- {insertText}
245
- <Text color="green">|</Text>
246
- </Text>
247
- </Box>
248
- )}
249
-
250
- {/* Keybinding reference */}
251
- <Box flexDirection="column" marginTop={1}>
252
- <Text bold dim>
253
- Keybindings
254
- </Text>
255
- {KEYBINDINGS.filter((kb) => kb.mode === "both" || kb.mode === mode).map((kb, i) => {
256
- const parsed = parseHotkey(kb.hotkey)
257
- const hotkeyDisplay = formatHotkey(kb.hotkey, parsed)
258
- const needsKitty = kb.hotkey.includes("⌘") || kb.hotkey.includes("✦")
259
- return (
260
- <Text key={i} dimColor={needsKitty && !kittySupported}>
261
- <Text bold color="yellow">
262
- {hotkeyDisplay.padEnd(10)}
263
- </Text>{" "}
264
- {kb.action}
265
- {needsKitty && !kittySupported ? <Text dim> (needs Kitty)</Text> : ""}
266
- </Text>
267
- )
268
- })}
269
- </Box>
270
- </Box>
271
-
272
- {/* Right: Event log */}
273
- <Box flexDirection="column" width={45}>
274
- <Text bold color="cyan">
275
- All Events ({counterRef.current})
276
- </Text>
277
- <Box height={1} />
278
- {events.length === 0 ? (
279
- <Text dim>Interact to see events...</Text>
280
- ) : (
281
- events.map((e, i) => (
282
- <Text key={i} dimColor={i < events.length - 1}>
283
- <Text color={e.type === "key" ? "cyan" : "blue"}>{e.type === "key" ? "KEY" : "PTR"}</Text>{" "}
284
- <Text color={(e.color ?? "white") as any}>{e.summary}</Text>
285
- </Text>
286
- ))
287
- )}
288
- </Box>
289
- </Box>
290
- </Box>
291
- )
292
- }
293
-
294
- function formatHotkey(raw: string, parsed: ReturnType<typeof parseHotkey>): string {
295
- // Use the raw string if it already uses symbols
296
- if (/[⌘⌥⌃⇧✦]/.test(raw)) return raw
297
- // Otherwise build from parsed
298
- const parts: string[] = []
299
- if (parsed.ctrl) parts.push("⌃")
300
- if (parsed.shift) parts.push("⇧")
301
- if (parsed.alt) parts.push("⌥")
302
- if (parsed.super) parts.push("⌘")
303
- if (parsed.hyper) parts.push("✦")
304
- parts.push(parsed.key)
305
- return parts.join("")
306
- }
307
-
308
- async function main() {
309
- const cleanup = () => {
310
- const stdout = process.stdout
311
- stdout.write("\x1b[?1003l\x1b[?1006l") // Disable mouse
312
- stdout.write("\x1b[?25h") // Show cursor
313
- stdout.write("\x1b[?1049l") // Exit alternate screen
314
- stdout.write("\x1b[0m") // Reset colors
315
- if (process.stdin.isTTY && process.stdin.isRaw) {
316
- try {
317
- process.stdin.setRawMode(false)
318
- } catch {}
319
- }
320
- }
321
- process.on("uncaughtException", (err) => {
322
- cleanup()
323
- throw err
324
- })
325
-
326
- // Detect Kitty support
327
- const kittyResult = await detectKittyFromStdio(process.stdout, process.stdin)
328
-
329
- // Enable Kitty with full flags if supported
330
- if (kittyResult.supported) {
331
- const flags =
332
- KittyFlags.DISAMBIGUATE |
333
- KittyFlags.REPORT_EVENTS |
334
- KittyFlags.REPORT_ALTERNATE |
335
- KittyFlags.REPORT_ALL_KEYS |
336
- KittyFlags.REPORT_TEXT
337
- process.stdout.write(enableKittyKeyboard(flags))
338
- }
339
-
340
- using term = createTerm()
341
- const { waitUntilExit } = await render(
342
- <ExampleBanner meta={meta} controls="i insert Esc normal j/k navigate q quit">
343
- <RichInputDemo kittySupported={kittyResult.supported} />
344
- </ExampleBanner>,
345
- term,
346
- )
347
- await waitUntilExit()
348
-
349
- // Cleanup
350
- if (kittyResult.supported) {
351
- process.stdout.write(disableKittyKeyboard())
352
- }
353
- }
354
-
355
- if (import.meta.main) {
356
- main().catch((err) => {
357
- // Restore terminal on crash
358
- const stdout = process.stdout
359
- stdout.write("\x1b[?1003l\x1b[?1006l") // Disable mouse
360
- stdout.write("\x1b[?25h") // Show cursor
361
- stdout.write("\x1b[?1049l") // Exit alternate screen
362
- stdout.write("\x1b[0m") // Reset colors
363
- if (process.stdin.isTTY && process.stdin.isRaw) {
364
- try {
365
- process.stdin.setRawMode(false)
366
- } catch {}
367
- }
368
- console.error(err)
369
- process.exit(1)
370
- })
371
- }