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,236 +0,0 @@
1
- /**
2
- * Generate sample PNG images for the Kitty protocol examples.
3
- *
4
- * Creates 3 sample images in examples/kitty/samples/:
5
- * - gradient.png — Rainbow gradient (256x192)
6
- * - checker.png — Colorful checkerboard pattern (256x192)
7
- * - circles.png — Overlapping colored circles on dark background (256x192)
8
- *
9
- * Uses raw PNG encoding with Node's zlib — no external dependencies.
10
- *
11
- * Run: bun vendor/silvery/examples/kitty/generate-samples.ts
12
- */
13
-
14
- import { deflateSync } from "node:zlib"
15
- import { writeFileSync, mkdirSync } from "node:fs"
16
- import { resolve, dirname } from "node:path"
17
- import { fileURLToPath } from "node:url"
18
-
19
- const WIDTH = 256
20
- const HEIGHT = 192
21
-
22
- // ---------------------------------------------------------------------------
23
- // HSV to RGB
24
- // ---------------------------------------------------------------------------
25
-
26
- function hsvToRgb(h: number, s: number, v: number): [number, number, number] {
27
- const c = v * s
28
- const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
29
- const m = v - c
30
-
31
- let r = 0,
32
- g = 0,
33
- b = 0
34
- if (h < 60) {
35
- r = c
36
- g = x
37
- } else if (h < 120) {
38
- r = x
39
- g = c
40
- } else if (h < 180) {
41
- g = c
42
- b = x
43
- } else if (h < 240) {
44
- g = x
45
- b = c
46
- } else if (h < 300) {
47
- r = x
48
- b = c
49
- } else {
50
- r = c
51
- b = x
52
- }
53
-
54
- return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)]
55
- }
56
-
57
- // ---------------------------------------------------------------------------
58
- // Raw PNG encoder (no dependencies)
59
- // ---------------------------------------------------------------------------
60
-
61
- function crc32(buf: Buffer): number {
62
- // CRC-32 lookup table
63
- const table: number[] = []
64
- for (let n = 0; n < 256; n++) {
65
- let c = n
66
- for (let k = 0; k < 8; k++) {
67
- c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1
68
- }
69
- table[n] = c
70
- }
71
-
72
- let crc = 0xffffffff
73
- for (let i = 0; i < buf.length; i++) {
74
- crc = table[(crc ^ buf[i]!) & 0xff]! ^ (crc >>> 8)
75
- }
76
- return (crc ^ 0xffffffff) >>> 0
77
- }
78
-
79
- function makePngChunk(type: string, data: Buffer): Buffer {
80
- const length = Buffer.alloc(4)
81
- length.writeUInt32BE(data.length, 0)
82
-
83
- const typeAndData = Buffer.concat([Buffer.from(type, "ascii"), data])
84
- const crc = Buffer.alloc(4)
85
- crc.writeUInt32BE(crc32(typeAndData), 0)
86
-
87
- return Buffer.concat([length, typeAndData, crc])
88
- }
89
-
90
- function rgbaToPng(rgba: Buffer, width: number, height: number): Buffer {
91
- // PNG signature
92
- const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
93
-
94
- // IHDR chunk
95
- const ihdr = Buffer.alloc(13)
96
- ihdr.writeUInt32BE(width, 0)
97
- ihdr.writeUInt32BE(height, 4)
98
- ihdr[8] = 8 // bit depth
99
- ihdr[9] = 6 // color type: RGBA
100
- ihdr[10] = 0 // compression
101
- ihdr[11] = 0 // filter
102
- ihdr[12] = 0 // interlace
103
- const ihdrChunk = makePngChunk("IHDR", ihdr)
104
-
105
- // IDAT chunk: filter each row with filter type 0 (None), then deflate
106
- const rawRows = Buffer.alloc(height * (1 + width * 4))
107
- for (let y = 0; y < height; y++) {
108
- const rowOffset = y * (1 + width * 4)
109
- rawRows[rowOffset] = 0 // filter type: None
110
- rgba.copy(rawRows, rowOffset + 1, y * width * 4, (y + 1) * width * 4)
111
- }
112
- const compressed = deflateSync(rawRows)
113
- const idatChunk = makePngChunk("IDAT", compressed)
114
-
115
- // IEND chunk
116
- const iendChunk = makePngChunk("IEND", Buffer.alloc(0))
117
-
118
- return Buffer.concat([signature, ihdrChunk, idatChunk, iendChunk])
119
- }
120
-
121
- // ---------------------------------------------------------------------------
122
- // Image generators
123
- // ---------------------------------------------------------------------------
124
-
125
- function generateGradient(): Buffer {
126
- const rgba = Buffer.alloc(WIDTH * HEIGHT * 4)
127
-
128
- for (let y = 0; y < HEIGHT; y++) {
129
- for (let x = 0; x < WIDTH; x++) {
130
- const offset = (y * WIDTH + x) * 4
131
- const hue = (x / WIDTH) * 360
132
- const brightness = 0.3 + 0.7 * (1 - y / HEIGHT)
133
- const [r, g, b] = hsvToRgb(hue, 1.0, brightness)
134
- rgba[offset] = r
135
- rgba[offset + 1] = g
136
- rgba[offset + 2] = b
137
- rgba[offset + 3] = 255
138
- }
139
- }
140
-
141
- return rgbaToPng(rgba, WIDTH, HEIGHT)
142
- }
143
-
144
- function generateChecker(): Buffer {
145
- const rgba = Buffer.alloc(WIDTH * HEIGHT * 4)
146
- const checkerSize = 24
147
-
148
- for (let y = 0; y < HEIGHT; y++) {
149
- for (let x = 0; x < WIDTH; x++) {
150
- const offset = (y * WIDTH + x) * 4
151
- const isLight = (Math.floor(x / checkerSize) + Math.floor(y / checkerSize)) % 2 === 0
152
- const hue = (x / WIDTH) * 360
153
- const [hr, hg, hb] = hsvToRgb(hue, 0.6, 1.0)
154
-
155
- if (isLight) {
156
- rgba[offset] = Math.min(255, hr + 60)
157
- rgba[offset + 1] = Math.min(255, hg + 60)
158
- rgba[offset + 2] = Math.min(255, hb + 60)
159
- } else {
160
- rgba[offset] = Math.max(0, hr - 100)
161
- rgba[offset + 1] = Math.max(0, hg - 100)
162
- rgba[offset + 2] = Math.max(0, hb - 100)
163
- }
164
- rgba[offset + 3] = 255
165
- }
166
- }
167
-
168
- return rgbaToPng(rgba, WIDTH, HEIGHT)
169
- }
170
-
171
- function generateCircles(): Buffer {
172
- const rgba = Buffer.alloc(WIDTH * HEIGHT * 4)
173
-
174
- // Dark background
175
- for (let i = 0; i < WIDTH * HEIGHT * 4; i += 4) {
176
- rgba[i] = 20
177
- rgba[i + 1] = 20
178
- rgba[i + 2] = 30
179
- rgba[i + 3] = 255
180
- }
181
-
182
- // Draw overlapping circles with additive blending
183
- const circles: { cx: number; cy: number; r: number; color: [number, number, number] }[] = [
184
- { cx: 90, cy: 80, r: 70, color: [200, 40, 40] },
185
- { cx: 166, cy: 80, r: 70, color: [40, 180, 40] },
186
- { cx: 128, cy: 140, r: 70, color: [40, 80, 220] },
187
- { cx: 50, cy: 140, r: 50, color: [220, 180, 30] },
188
- { cx: 206, cy: 140, r: 50, color: [180, 40, 220] },
189
- { cx: 128, cy: 60, r: 45, color: [40, 200, 200] },
190
- ]
191
-
192
- for (const circle of circles) {
193
- for (let y = 0; y < HEIGHT; y++) {
194
- for (let x = 0; x < WIDTH; x++) {
195
- const dx = x - circle.cx
196
- const dy = y - circle.cy
197
- const dist = Math.sqrt(dx * dx + dy * dy)
198
-
199
- if (dist < circle.r) {
200
- const offset = (y * WIDTH + x) * 4
201
- // Soft edge with smooth falloff
202
- const alpha = dist > circle.r - 8 ? (circle.r - dist) / 8 : 1.0
203
-
204
- // Additive blending
205
- rgba[offset] = Math.min(255, rgba[offset]! + Math.round(circle.color[0] * alpha))
206
- rgba[offset + 1] = Math.min(255, rgba[offset + 1]! + Math.round(circle.color[1] * alpha))
207
- rgba[offset + 2] = Math.min(255, rgba[offset + 2]! + Math.round(circle.color[2] * alpha))
208
- }
209
- }
210
- }
211
- }
212
-
213
- return rgbaToPng(rgba, WIDTH, HEIGHT)
214
- }
215
-
216
- // ---------------------------------------------------------------------------
217
- // Main
218
- // ---------------------------------------------------------------------------
219
-
220
- const samplesDir = resolve(dirname(fileURLToPath(import.meta.url)), "samples")
221
- mkdirSync(samplesDir, { recursive: true })
222
-
223
- const samples = [
224
- { name: "gradient.png", generate: generateGradient },
225
- { name: "checker.png", generate: generateChecker },
226
- { name: "circles.png", generate: generateCircles },
227
- ]
228
-
229
- for (const sample of samples) {
230
- const path = resolve(samplesDir, sample.name)
231
- const png = sample.generate()
232
- writeFileSync(path, png)
233
- console.log(` ${sample.name} (${png.length} bytes)`)
234
- }
235
-
236
- console.log(`\nGenerated ${samples.length} samples in ${samplesDir}`)
@@ -1,273 +0,0 @@
1
- /**
2
- * Image Component Demo
3
- *
4
- * Demonstrates the `<Image>` component — a high-level React component for
5
- * displaying images in the terminal. Unlike the raw Kitty graphics protocol
6
- * example (images.tsx), this uses the declarative component API with automatic
7
- * protocol detection and fallback support.
8
- *
9
- * Features:
10
- * - Uses the <Image> component from silvery
11
- * - Auto-generated rainbow test pattern (no external files needed)
12
- * - Fallback text when graphics protocol is not supported
13
- * - Protocol auto-detection status display
14
- * - Adjustable image dimensions with +/- keys
15
- *
16
- * Run: bun vendor/silvery/examples/kitty/image-component.tsx
17
- */
18
-
19
- import { deflateSync } from "node:zlib"
20
- import React, { useState, useMemo } from "react"
21
- import { render, Box, Text, Image, useInput, useApp, createTerm, type Key } from "../../src/index.js"
22
- import { isKittyGraphicsSupported } from "../../src/image/kitty-graphics.js"
23
- import { isSixelSupported } from "../../src/image/sixel-encoder.js"
24
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
25
-
26
- export const meta: ExampleMeta = {
27
- name: "Image Component",
28
- description: "Declarative <Image> component with protocol auto-detection",
29
- features: ["Image", "Kitty graphics", "Sixel", "fallback text", "protocol detection"],
30
- }
31
-
32
- // ============================================================================
33
- // PNG Generation
34
- // ============================================================================
35
-
36
- /** Convert HSV (h: 0-360, s/v: 0-1) to RGB bytes */
37
- function hsvToRgb(h: number, s: number, v: number): [number, number, number] {
38
- const c = v * s
39
- const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
40
- const m = v - c
41
-
42
- let r = 0,
43
- g = 0,
44
- b = 0
45
- if (h < 60) {
46
- r = c
47
- g = x
48
- } else if (h < 120) {
49
- r = x
50
- g = c
51
- } else if (h < 180) {
52
- g = c
53
- b = x
54
- } else if (h < 240) {
55
- g = x
56
- b = c
57
- } else if (h < 300) {
58
- r = x
59
- b = c
60
- } else {
61
- r = c
62
- b = x
63
- }
64
-
65
- return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)]
66
- }
67
-
68
- /** Generate a rainbow gradient PNG buffer (no external dependencies). */
69
- function generateTestPatternPng(width: number, height: number): Buffer {
70
- // Build raw RGBA pixel data with filter bytes
71
- const rawData = Buffer.alloc(height * (1 + width * 4))
72
-
73
- for (let y = 0; y < height; y++) {
74
- const rowOffset = y * (1 + width * 4)
75
- rawData[rowOffset] = 0 // PNG filter: None
76
-
77
- for (let x = 0; x < width; x++) {
78
- const hue = (x / width) * 360
79
- const saturation = 0.7 + 0.3 * Math.sin((y / height) * Math.PI)
80
- const value = 0.5 + 0.5 * Math.cos((y / height) * Math.PI * 2)
81
- const [r, g, b] = hsvToRgb(hue, saturation, value)
82
-
83
- const pixelOffset = rowOffset + 1 + x * 4
84
- rawData[pixelOffset] = r!
85
- rawData[pixelOffset + 1] = g!
86
- rawData[pixelOffset + 2] = b!
87
- rawData[pixelOffset + 3] = 255
88
- }
89
- }
90
-
91
- // Encode as minimal PNG
92
- const compressed = deflateSync(rawData)
93
-
94
- const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])
95
-
96
- function makeChunk(type: string, data: Buffer): Buffer {
97
- const len = Buffer.alloc(4)
98
- len.writeUInt32BE(data.length)
99
- const typeBytes = Buffer.from(type, "ascii")
100
- const payload = Buffer.concat([typeBytes, data])
101
- const crc = crc32(payload)
102
- const crcBuf = Buffer.alloc(4)
103
- crcBuf.writeUInt32BE(crc >>> 0)
104
- return Buffer.concat([len, payload, crcBuf])
105
- }
106
-
107
- // IHDR
108
- const ihdr = Buffer.alloc(13)
109
- ihdr.writeUInt32BE(width, 0)
110
- ihdr.writeUInt32BE(height, 4)
111
- ihdr[8] = 8 // bit depth
112
- ihdr[9] = 6 // color type: RGBA
113
- ihdr[10] = 0 // compression
114
- ihdr[11] = 0 // filter
115
- ihdr[12] = 0 // interlace
116
-
117
- return Buffer.concat([
118
- signature,
119
- makeChunk("IHDR", ihdr),
120
- makeChunk("IDAT", compressed),
121
- makeChunk("IEND", Buffer.alloc(0)),
122
- ])
123
- }
124
-
125
- /** CRC-32 for PNG chunks */
126
- function crc32(data: Buffer): number {
127
- let crc = 0xffffffff
128
- for (let i = 0; i < data.length; i++) {
129
- crc ^= data[i]!
130
- for (let j = 0; j < 8; j++) {
131
- crc = crc & 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1
132
- }
133
- }
134
- return (crc ^ 0xffffffff) >>> 0
135
- }
136
-
137
- // ============================================================================
138
- // Components
139
- // ============================================================================
140
-
141
- function ProtocolStatus(): JSX.Element {
142
- const kitty = isKittyGraphicsSupported()
143
- const sixel = isSixelSupported()
144
-
145
- const detected = kitty ? "Kitty" : sixel ? "Sixel" : "None"
146
- const color = kitty || sixel ? "green" : "yellow"
147
-
148
- return (
149
- <Box gap={1} paddingX={1}>
150
- <Text dim>Protocol:</Text>
151
- <Text color={color} bold>
152
- {detected}
153
- </Text>
154
- <Text dim>
155
- (Kitty: {kitty ? "yes" : "no"}, Sixel: {sixel ? "yes" : "no"})
156
- </Text>
157
- </Box>
158
- )
159
- }
160
-
161
- export function ImageComponentDemo(): JSX.Element {
162
- const { exit } = useApp()
163
- const [imageWidth, setImageWidth] = useState(40)
164
- const [imageHeight, setImageHeight] = useState(15)
165
-
166
- // Generate a test pattern PNG
167
- const pngBuffer = useMemo(() => generateTestPatternPng(256, 192), [])
168
-
169
- useInput((input: string, key: Key) => {
170
- if (input === "q" || key.escape) {
171
- exit()
172
- return
173
- }
174
-
175
- // Adjust width
176
- if (key.rightArrow || input === "l") {
177
- setImageWidth((prev) => Math.min(80, prev + 5))
178
- }
179
- if (key.leftArrow || input === "h") {
180
- setImageWidth((prev) => Math.max(10, prev - 5))
181
- }
182
-
183
- // Adjust height
184
- if (input === "+" || input === "=") {
185
- setImageHeight((prev) => Math.min(30, prev + 2))
186
- }
187
- if (input === "-") {
188
- setImageHeight((prev) => Math.max(5, prev - 2))
189
- }
190
- })
191
-
192
- return (
193
- <Box flexDirection="column" padding={1} gap={1}>
194
- <ProtocolStatus />
195
-
196
- <Box flexDirection="column" borderStyle="round" borderColor="cyan" paddingX={1}>
197
- <Box marginBottom={1} gap={1}>
198
- <Text bold color="cyan">
199
- {"<Image>"}
200
- </Text>
201
- <Text dim>
202
- {imageWidth}x{imageHeight} cols/rows
203
- </Text>
204
- </Box>
205
-
206
- <Image
207
- src={pngBuffer}
208
- width={imageWidth}
209
- height={imageHeight}
210
- fallback="[Rainbow gradient — graphics protocol not available]"
211
- />
212
- </Box>
213
-
214
- <Box flexDirection="column" borderStyle="round" borderColor="gray" paddingX={1}>
215
- <Box marginBottom={1}>
216
- <Text bold>Component Props</Text>
217
- </Box>
218
- <Box gap={2}>
219
- <Text>
220
- width={"{"}
221
- <Text color="green">{imageWidth}</Text>
222
- {"}"}
223
- </Text>
224
- <Text>
225
- height={"{"}
226
- <Text color="green">{imageHeight}</Text>
227
- {"}"}
228
- </Text>
229
- <Text>
230
- protocol=
231
- <Text color="yellow">"auto"</Text>
232
- </Text>
233
- </Box>
234
- <Text dim>fallback="[Rainbow gradient — graphics protocol not available]"</Text>
235
- </Box>
236
-
237
- <Text dim>
238
- {" "}
239
- <Text bold dim>
240
- h/l
241
- </Text>{" "}
242
- width{" "}
243
- <Text bold dim>
244
- +/-
245
- </Text>{" "}
246
- height{" "}
247
- <Text bold dim>
248
- Esc/q
249
- </Text>{" "}
250
- quit
251
- </Text>
252
- </Box>
253
- )
254
- }
255
-
256
- // ============================================================================
257
- // Main
258
- // ============================================================================
259
-
260
- async function main() {
261
- using term = createTerm()
262
- const { waitUntilExit } = await render(
263
- <ExampleBanner meta={meta} controls="h/l width +/- height Esc/q quit">
264
- <ImageComponentDemo />
265
- </ExampleBanner>,
266
- term,
267
- )
268
- await waitUntilExit()
269
- }
270
-
271
- if (import.meta.main) {
272
- main().catch(console.error)
273
- }