silvery 0.0.1 → 0.3.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/LICENSE +21 -0
- package/README.md +174 -11
- package/bin/silvery.ts +258 -0
- package/examples/CLAUDE.md +75 -0
- package/examples/_banner.tsx +60 -0
- package/examples/cli.ts +228 -0
- package/examples/index.md +101 -0
- package/examples/inline/inline-nontty.tsx +98 -0
- package/examples/inline/inline-progress.tsx +79 -0
- package/examples/inline/inline-simple.tsx +63 -0
- package/examples/inline/scrollback.tsx +185 -0
- package/examples/interactive/_input-debug.tsx +110 -0
- package/examples/interactive/_stdin-test.ts +71 -0
- package/examples/interactive/_textarea-bare.tsx +45 -0
- package/examples/interactive/aichat/components.tsx +468 -0
- package/examples/interactive/aichat/index.tsx +207 -0
- package/examples/interactive/aichat/script.ts +460 -0
- package/examples/interactive/aichat/state.ts +326 -0
- package/examples/interactive/aichat/types.ts +19 -0
- package/examples/interactive/app-todo.tsx +198 -0
- package/examples/interactive/async-data.tsx +208 -0
- package/examples/interactive/cli-wizard.tsx +332 -0
- package/examples/interactive/clipboard.tsx +183 -0
- package/examples/interactive/components.tsx +463 -0
- package/examples/interactive/data-explorer.tsx +506 -0
- package/examples/interactive/dev-tools.tsx +379 -0
- package/examples/interactive/explorer.tsx +747 -0
- package/examples/interactive/gallery.tsx +652 -0
- package/examples/interactive/inline-bench.tsx +136 -0
- package/examples/interactive/kanban.tsx +267 -0
- package/examples/interactive/layout-ref.tsx +185 -0
- package/examples/interactive/outline.tsx +171 -0
- package/examples/interactive/paste-demo.tsx +198 -0
- package/examples/interactive/scroll.tsx +77 -0
- package/examples/interactive/search-filter.tsx +240 -0
- package/examples/interactive/task-list.tsx +279 -0
- package/examples/interactive/terminal.tsx +798 -0
- package/examples/interactive/textarea.tsx +103 -0
- package/examples/interactive/theme.tsx +336 -0
- package/examples/interactive/transform.tsx +256 -0
- package/examples/interactive/virtual-10k.tsx +413 -0
- package/examples/kitty/canvas.tsx +519 -0
- package/examples/kitty/generate-samples.ts +236 -0
- package/examples/kitty/image-component.tsx +273 -0
- package/examples/kitty/images.tsx +604 -0
- package/examples/kitty/input.tsx +371 -0
- package/examples/kitty/keys.tsx +378 -0
- package/examples/kitty/paint.tsx +1017 -0
- package/examples/layout/dashboard.tsx +551 -0
- package/examples/layout/live-resize.tsx +290 -0
- package/examples/layout/overflow.tsx +51 -0
- package/examples/playground/README.md +69 -0
- package/examples/playground/build.ts +61 -0
- package/examples/playground/index.html +420 -0
- package/examples/playground/playground-app.tsx +416 -0
- package/examples/runtime/elm-counter.tsx +206 -0
- package/examples/runtime/hello-runtime.tsx +73 -0
- package/examples/runtime/pipe-composition.tsx +184 -0
- package/examples/runtime/run-counter.tsx +78 -0
- package/examples/runtime/runtime-counter.tsx +197 -0
- package/examples/screenshots/generate.tsx +563 -0
- package/examples/scrollback-perf.tsx +230 -0
- package/examples/viewer.tsx +654 -0
- package/examples/web/build.ts +365 -0
- package/examples/web/canvas-app.tsx +80 -0
- package/examples/web/canvas.html +89 -0
- package/examples/web/dom-app.tsx +81 -0
- package/examples/web/dom.html +113 -0
- package/examples/web/showcase-app.tsx +107 -0
- package/examples/web/showcase.html +34 -0
- package/examples/web/showcases/index.tsx +56 -0
- package/examples/web/viewer-app.tsx +555 -0
- package/examples/web/viewer.html +30 -0
- package/examples/web/xterm-app.tsx +105 -0
- package/examples/web/xterm.html +118 -0
- package/package.json +106 -5
- package/src/index.ts +5 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrollback Mode — REPL
|
|
3
|
+
*
|
|
4
|
+
* Interactive expression evaluator demonstrating useScrollback + VirtualList virtualized.
|
|
5
|
+
* Completed results freeze into terminal scrollback; the active prompt stays at bottom.
|
|
6
|
+
*
|
|
7
|
+
* Controls:
|
|
8
|
+
* Type expression + Enter - Evaluate
|
|
9
|
+
* q (when input empty) - Quit
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import React, { useState, useCallback } from "react"
|
|
13
|
+
import { Box, Text, Divider, VirtualList, useInput, type Key } from "../../src/index.js"
|
|
14
|
+
import { run, useExit } from "@silvery/term/runtime"
|
|
15
|
+
import { useScrollback } from "../../src/hooks/useScrollback.js"
|
|
16
|
+
import { ExampleBanner, type ExampleMeta } from "../_banner.js"
|
|
17
|
+
|
|
18
|
+
export const meta: ExampleMeta = {
|
|
19
|
+
name: "Scrollback",
|
|
20
|
+
description: "REPL with useScrollback + VirtualList virtualized for terminal scrollback",
|
|
21
|
+
features: ["useScrollback()", "VirtualList virtualized", "inline mode"],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// Data
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
interface Result {
|
|
29
|
+
id: number
|
|
30
|
+
expr: string
|
|
31
|
+
value: string
|
|
32
|
+
frozen: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let nextId = 0
|
|
36
|
+
|
|
37
|
+
function evaluate(expr: string): string {
|
|
38
|
+
try {
|
|
39
|
+
// eslint-disable-next-line no-eval
|
|
40
|
+
return String(eval(expr))
|
|
41
|
+
} catch (e: unknown) {
|
|
42
|
+
return `Error: ${e instanceof Error ? e.message : String(e)}`
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// Component
|
|
48
|
+
// =============================================================================
|
|
49
|
+
|
|
50
|
+
export function Repl() {
|
|
51
|
+
const exit = useExit()
|
|
52
|
+
const [results, setResults] = useState<Result[]>([])
|
|
53
|
+
const [input, setInput] = useState("")
|
|
54
|
+
const [cursor, setCursor] = useState(0)
|
|
55
|
+
|
|
56
|
+
// Push frozen results to terminal scrollback
|
|
57
|
+
const frozenCount = useScrollback(results, {
|
|
58
|
+
frozen: (r) => r.frozen,
|
|
59
|
+
render: (r) => `$ ${r.expr}\n→ ${r.value}`,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const submit = useCallback(() => {
|
|
63
|
+
const expr = input.trim()
|
|
64
|
+
if (!expr) return
|
|
65
|
+
|
|
66
|
+
const value = evaluate(expr)
|
|
67
|
+
const id = nextId++
|
|
68
|
+
|
|
69
|
+
// Mark all existing results as frozen, add new one unfrozen
|
|
70
|
+
setResults((prev) => [...prev.map((r) => ({ ...r, frozen: true })), { id, expr, value, frozen: false }])
|
|
71
|
+
setInput("")
|
|
72
|
+
setCursor(0)
|
|
73
|
+
}, [input])
|
|
74
|
+
|
|
75
|
+
useInput((ch: string, key: Key) => {
|
|
76
|
+
if (key.return) {
|
|
77
|
+
submit()
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
if (key.escape || (ch === "q" && input === "")) {
|
|
81
|
+
exit()
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
if (key.backspace) {
|
|
85
|
+
if (cursor > 0) {
|
|
86
|
+
setInput((v) => v.slice(0, cursor - 1) + v.slice(cursor))
|
|
87
|
+
setCursor((c) => c - 1)
|
|
88
|
+
}
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
if (key.leftArrow) {
|
|
92
|
+
setCursor((c) => Math.max(0, c - 1))
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
if (key.rightArrow) {
|
|
96
|
+
setCursor((c) => Math.min(input.length, c + 1))
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
// Ctrl+A: beginning of line
|
|
100
|
+
if (key.ctrl && ch === "a") {
|
|
101
|
+
setCursor(0)
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
// Ctrl+E: end of line
|
|
105
|
+
if (key.ctrl && ch === "e") {
|
|
106
|
+
setCursor(input.length)
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
// Ctrl+U: clear line
|
|
110
|
+
if (key.ctrl && ch === "u") {
|
|
111
|
+
setInput("")
|
|
112
|
+
setCursor(0)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
if (ch >= " ") {
|
|
116
|
+
setInput((v) => v.slice(0, cursor) + ch + v.slice(cursor))
|
|
117
|
+
setCursor((c) => c + 1)
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const activeCount = results.length - frozenCount
|
|
122
|
+
const beforeCursor = input.slice(0, cursor)
|
|
123
|
+
const atCursor = input[cursor] ?? " "
|
|
124
|
+
const afterCursor = input.slice(cursor + 1)
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<Box flexDirection="column">
|
|
128
|
+
{/* Active (non-virtualized) results via VirtualList */}
|
|
129
|
+
{activeCount > 0 && (
|
|
130
|
+
<VirtualList
|
|
131
|
+
items={results}
|
|
132
|
+
virtualized={(r) => r.frozen}
|
|
133
|
+
height={activeCount * 2}
|
|
134
|
+
itemHeight={2}
|
|
135
|
+
scrollTo={0}
|
|
136
|
+
renderItem={(r) => (
|
|
137
|
+
<Box key={r.id} flexDirection="column">
|
|
138
|
+
<Text>
|
|
139
|
+
<Text color="gray">{"$ "}</Text>
|
|
140
|
+
<Text>{r.expr}</Text>
|
|
141
|
+
</Text>
|
|
142
|
+
<Text>
|
|
143
|
+
<Text color="cyan">{"→ "}</Text>
|
|
144
|
+
<Text>{r.value}</Text>
|
|
145
|
+
</Text>
|
|
146
|
+
</Box>
|
|
147
|
+
)}
|
|
148
|
+
/>
|
|
149
|
+
)}
|
|
150
|
+
|
|
151
|
+
{/* Separator */}
|
|
152
|
+
<Divider />
|
|
153
|
+
|
|
154
|
+
{/* Input prompt */}
|
|
155
|
+
<Text>
|
|
156
|
+
<Text color="yellow">{"› "}</Text>
|
|
157
|
+
<Text>{beforeCursor}</Text>
|
|
158
|
+
<Text inverse>{atCursor}</Text>
|
|
159
|
+
<Text>{afterCursor}</Text>
|
|
160
|
+
</Text>
|
|
161
|
+
|
|
162
|
+
{/* Status */}
|
|
163
|
+
<Text dim>
|
|
164
|
+
{results.length} result{results.length !== 1 ? "s" : ""} | Esc/q to quit
|
|
165
|
+
</Text>
|
|
166
|
+
</Box>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// =============================================================================
|
|
171
|
+
// Main
|
|
172
|
+
// =============================================================================
|
|
173
|
+
|
|
174
|
+
async function main() {
|
|
175
|
+
await run(
|
|
176
|
+
<ExampleBanner meta={meta} controls="Type expr + Enter Esc/q quit">
|
|
177
|
+
<Repl />
|
|
178
|
+
</ExampleBanner>,
|
|
179
|
+
{ mode: "inline" },
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (import.meta.main) {
|
|
184
|
+
main().catch(console.error)
|
|
185
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Debug Tool
|
|
3
|
+
*
|
|
4
|
+
* Minimal diagnostic to find where keypresses are lost.
|
|
5
|
+
* Shows every event received by useInput + TextArea side by side.
|
|
6
|
+
*
|
|
7
|
+
* Run: bun vendor/silvery/examples/interactive/_input-debug.tsx
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React, { useState, useRef } from "react"
|
|
11
|
+
import { render, Box, Text, TextArea, useInput, useApp, createTerm, type Key } from "../../src/index.js"
|
|
12
|
+
|
|
13
|
+
function InputDebug(): JSX.Element {
|
|
14
|
+
const { exit } = useApp()
|
|
15
|
+
|
|
16
|
+
// Track raw useInput events
|
|
17
|
+
const [rawEvents, setRawEvents] = useState<string[]>([])
|
|
18
|
+
const rawCountRef = useRef(0)
|
|
19
|
+
|
|
20
|
+
// Track TextArea value
|
|
21
|
+
const [textValue, setTextValue] = useState("")
|
|
22
|
+
const textChangeCountRef = useRef(0)
|
|
23
|
+
|
|
24
|
+
// Raw useInput handler — logs EVERY event
|
|
25
|
+
useInput((input: string, key: Key) => {
|
|
26
|
+
if (key.escape) {
|
|
27
|
+
exit()
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
rawCountRef.current++
|
|
31
|
+
const desc = describeKey(input, key)
|
|
32
|
+
setRawEvents((prev) => [...prev.slice(-15), `#${rawCountRef.current} ${desc}`])
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// TextArea onChange handler
|
|
36
|
+
function handleChange(value: string) {
|
|
37
|
+
textChangeCountRef.current++
|
|
38
|
+
setTextValue(value)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Box flexDirection="column" padding={1}>
|
|
43
|
+
<Text bold color="cyan">
|
|
44
|
+
Input Pipeline Diagnostic
|
|
45
|
+
</Text>
|
|
46
|
+
<Text dim>Type slowly (1 char/2sec). Compare left (raw events) vs right (TextArea value).</Text>
|
|
47
|
+
<Text dim>Press Esc to quit.</Text>
|
|
48
|
+
<Box height={1} />
|
|
49
|
+
|
|
50
|
+
<Box gap={4}>
|
|
51
|
+
{/* Left: Raw useInput events */}
|
|
52
|
+
<Box flexDirection="column" width={40}>
|
|
53
|
+
<Text bold color="yellow">
|
|
54
|
+
useInput events: {rawCountRef.current}
|
|
55
|
+
</Text>
|
|
56
|
+
{rawEvents.map((e, i) => (
|
|
57
|
+
<Text key={i} dimColor={i < rawEvents.length - 1}>
|
|
58
|
+
{e}
|
|
59
|
+
</Text>
|
|
60
|
+
))}
|
|
61
|
+
</Box>
|
|
62
|
+
|
|
63
|
+
{/* Right: TextArea */}
|
|
64
|
+
<Box flexDirection="column" width={40}>
|
|
65
|
+
<Text bold color="green">
|
|
66
|
+
TextArea value ({textValue.length} chars, {textChangeCountRef.current} changes):
|
|
67
|
+
</Text>
|
|
68
|
+
<Box borderStyle="single" borderColor="green">
|
|
69
|
+
<Box paddingX={1}>
|
|
70
|
+
<TextArea value={textValue} onChange={handleChange} height={4} placeholder="Type here..." />
|
|
71
|
+
</Box>
|
|
72
|
+
</Box>
|
|
73
|
+
<Box marginTop={1}>
|
|
74
|
+
<Text>Value: {JSON.stringify(textValue)}</Text>
|
|
75
|
+
</Box>
|
|
76
|
+
</Box>
|
|
77
|
+
</Box>
|
|
78
|
+
</Box>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function describeKey(input: string, key: Key): string {
|
|
83
|
+
const parts: string[] = []
|
|
84
|
+
if (key.ctrl) parts.push("Ctrl")
|
|
85
|
+
if (key.meta) parts.push("Meta")
|
|
86
|
+
if (key.shift) parts.push("Shift")
|
|
87
|
+
|
|
88
|
+
if (key.return) parts.push("Enter")
|
|
89
|
+
else if (key.escape) parts.push("Esc")
|
|
90
|
+
else if (key.backspace) parts.push("BS")
|
|
91
|
+
else if (key.delete) parts.push("Del")
|
|
92
|
+
else if (key.upArrow) parts.push("Up")
|
|
93
|
+
else if (key.downArrow) parts.push("Down")
|
|
94
|
+
else if (key.leftArrow) parts.push("Left")
|
|
95
|
+
else if (key.rightArrow) parts.push("Right")
|
|
96
|
+
else if (key.tab) parts.push("Tab")
|
|
97
|
+
else if (input.length === 1 && input >= " ") parts.push(`'${input}'`)
|
|
98
|
+
else if (input) parts.push(`raw:${JSON.stringify(input)}`)
|
|
99
|
+
else parts.push("(empty)")
|
|
100
|
+
|
|
101
|
+
return parts.join("+")
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function main() {
|
|
105
|
+
using term = createTerm()
|
|
106
|
+
const { waitUntilExit } = await render(<InputDebug />, term)
|
|
107
|
+
await waitUntilExit()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
main().catch(console.error)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Bare stdin test — no React, no silvery.
|
|
4
|
+
*
|
|
5
|
+
* Tests whether Bun's stdin "readable" events deliver all keypresses.
|
|
6
|
+
* Run in a real terminal (not through a tool): bun vendor/silvery/examples/interactive/_stdin-test.ts
|
|
7
|
+
*
|
|
8
|
+
* Type characters slowly. Each keypress should appear immediately.
|
|
9
|
+
* Press Ctrl+C to exit.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = await import("node:fs")
|
|
13
|
+
|
|
14
|
+
const log = fs.createWriteStream("/tmp/stdin-test.log", { flags: "w" })
|
|
15
|
+
function logMsg(msg: string) {
|
|
16
|
+
const ts = new Date().toISOString().slice(11, 23)
|
|
17
|
+
log.write(`[${ts}] ${msg}\n`)
|
|
18
|
+
process.stdout.write(msg + "\n")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
logMsg("=== Bare stdin readable test ===")
|
|
22
|
+
logMsg("Type characters slowly. Press Ctrl+C to exit.")
|
|
23
|
+
logMsg("")
|
|
24
|
+
|
|
25
|
+
process.stdin.setEncoding("utf8")
|
|
26
|
+
process.stdin.setRawMode(true)
|
|
27
|
+
process.stdin.ref()
|
|
28
|
+
|
|
29
|
+
let count = 0
|
|
30
|
+
|
|
31
|
+
function handleReadable() {
|
|
32
|
+
const chunk = process.stdin.read() as string | null
|
|
33
|
+
if (chunk === null) {
|
|
34
|
+
logMsg(` readable event but stdin.read() returned null`)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
39
|
+
count++
|
|
40
|
+
const ch = chunk[i]!
|
|
41
|
+
const code = ch.charCodeAt(0)
|
|
42
|
+
|
|
43
|
+
if (code === 3) {
|
|
44
|
+
// Ctrl+C
|
|
45
|
+
logMsg(`\n=== Total: ${count} keypresses ===`)
|
|
46
|
+
log.end()
|
|
47
|
+
process.stdin.setRawMode(false)
|
|
48
|
+
process.stdin.unref()
|
|
49
|
+
process.exit(0)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (code >= 32 && code < 127) {
|
|
53
|
+
logMsg(`#${count} char='${ch}' code=${code} chunkLen=${chunk.length}`)
|
|
54
|
+
} else {
|
|
55
|
+
logMsg(
|
|
56
|
+
`#${count} code=0x${code.toString(16).padStart(2, "0")} chunkLen=${chunk.length} raw=${JSON.stringify(chunk.slice(i))}`,
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Try reading again in case more data
|
|
62
|
+
const more = process.stdin.read() as string | null
|
|
63
|
+
if (more !== null) {
|
|
64
|
+
logMsg(` EXTRA data in buffer: ${JSON.stringify(more)}`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
process.stdin.on("readable", handleReadable)
|
|
69
|
+
|
|
70
|
+
logMsg("Listening for stdin readable events...")
|
|
71
|
+
logMsg("")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bare TextArea Test — single useInput hook, no parent useInput.
|
|
3
|
+
*
|
|
4
|
+
* Tests whether the bug is from two useInput hooks competing.
|
|
5
|
+
* Run: bun vendor/silvery/examples/interactive/_textarea-bare.tsx
|
|
6
|
+
*
|
|
7
|
+
* If chars are STILL eaten → bug is in TextArea or input pipeline
|
|
8
|
+
* If chars are NOT eaten → bug is from two useInput hooks
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React, { useState } from "react"
|
|
12
|
+
import { render, Box, Text, TextArea, createTerm } from "../../src/index.js"
|
|
13
|
+
|
|
14
|
+
function BareTextArea(): JSX.Element {
|
|
15
|
+
const [value, setValue] = useState("")
|
|
16
|
+
|
|
17
|
+
// NO parent useInput — only TextArea's internal one
|
|
18
|
+
return (
|
|
19
|
+
<Box flexDirection="column" padding={1}>
|
|
20
|
+
<Text bold color="cyan">
|
|
21
|
+
Bare TextArea (single useInput hook)
|
|
22
|
+
</Text>
|
|
23
|
+
<Text dim>Type slowly. Ctrl+C to exit.</Text>
|
|
24
|
+
<Box height={1} />
|
|
25
|
+
|
|
26
|
+
<Box borderStyle="single" borderColor="cyan" paddingX={1}>
|
|
27
|
+
<TextArea value={value} onChange={setValue} height={4} placeholder="Type here..." />
|
|
28
|
+
</Box>
|
|
29
|
+
|
|
30
|
+
<Box marginTop={1}>
|
|
31
|
+
<Text>
|
|
32
|
+
Value ({value.length} chars): {JSON.stringify(value)}
|
|
33
|
+
</Text>
|
|
34
|
+
</Box>
|
|
35
|
+
</Box>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function main() {
|
|
40
|
+
using term = createTerm()
|
|
41
|
+
const { waitUntilExit } = await render(<BareTextArea />, term)
|
|
42
|
+
await waitUntilExit()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
main().catch(console.error)
|