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,290 +0,0 @@
1
- /**
2
- * Live Resize Demo
3
- *
4
- * THE showcase demo for silvery's unique capability: components that know their size.
5
- *
6
- * Demonstrates:
7
- * - useContentRect() providing real-time width/height during render
8
- * - Multi-column layout that reflows from 1 to 2 to 3 columns based on width
9
- * - Responsive breakpoints with visual feedback
10
- * - Content that adapts its presentation based on available space
11
- * - No useEffect, no layout thrashing — dimensions are synchronous
12
- *
13
- * Usage: bun run examples/live-resize/index.tsx
14
- *
15
- * Try resizing your terminal to see the layout reflow in real-time!
16
- *
17
- * Controls:
18
- * Esc/q or Ctrl+C - Quit
19
- */
20
-
21
- import React from "react"
22
- import { Box, Text, H1, H3, Kbd, Muted, Small, useContentRect } from "../../src/index.js"
23
- import { run, useInput, type Key } from "@silvery/term/runtime"
24
- import { useCallback } from "react"
25
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
26
-
27
- export const meta: ExampleMeta = {
28
- name: "Live Resize",
29
- description: "Responsive multi-column grid that reflows based on terminal width",
30
- features: ["useContentRect()", "responsive breakpoints", "Box flexDirection"],
31
- }
32
-
33
- // ============================================================================
34
- // Types
35
- // ============================================================================
36
-
37
- interface CardData {
38
- title: string
39
- icon: string
40
- value: string
41
- detail: string
42
- color: string
43
- sparkline: string
44
- }
45
-
46
- // ============================================================================
47
- // Data
48
- // ============================================================================
49
-
50
- const CARDS: CardData[] = [
51
- {
52
- title: "CPU Usage",
53
- icon: "\u{1f4bb}",
54
- value: "42%",
55
- detail: "4 cores, 2.4 GHz base",
56
- color: "green",
57
- sparkline: "\u2582\u2583\u2585\u2587\u2586\u2584\u2583\u2585\u2587\u2588\u2586\u2584\u2583\u2582\u2583\u2585",
58
- },
59
- {
60
- title: "Memory",
61
- icon: "\u{1f9e0}",
62
- value: "8.2 GB",
63
- detail: "of 16 GB (51% used)",
64
- color: "cyan",
65
- sparkline: "\u2584\u2584\u2585\u2585\u2585\u2586\u2586\u2586\u2585\u2585\u2586\u2586\u2587\u2587\u2586\u2586",
66
- },
67
- {
68
- title: "Disk I/O",
69
- icon: "\u{1f4be}",
70
- value: "234 MB/s",
71
- detail: "Read: 180 MB/s Write: 54 MB/s",
72
- color: "yellow",
73
- sparkline: "\u2581\u2582\u2583\u2587\u2588\u2587\u2584\u2582\u2581\u2582\u2585\u2587\u2586\u2583\u2582\u2581",
74
- },
75
- {
76
- title: "Network",
77
- icon: "\u{1f310}",
78
- value: "1.2 Gb/s",
79
- detail: "In: 800 Mb/s Out: 400 Mb/s",
80
- color: "magenta",
81
- sparkline: "\u2583\u2584\u2585\u2586\u2587\u2586\u2585\u2584\u2585\u2586\u2587\u2588\u2587\u2586\u2585\u2584",
82
- },
83
- {
84
- title: "Processes",
85
- icon: "\u{2699}\u{fe0f}",
86
- value: "247",
87
- detail: "12 running, 235 sleeping",
88
- color: "blue",
89
- sparkline: "\u2585\u2585\u2585\u2586\u2585\u2585\u2585\u2585\u2586\u2585\u2585\u2585\u2586\u2585\u2585\u2585",
90
- },
91
- {
92
- title: "Temperature",
93
- icon: "\u{1f321}\u{fe0f}",
94
- value: "62 C",
95
- detail: "Max: 85 C (safe range)",
96
- color: "red",
97
- sparkline: "\u2583\u2583\u2584\u2584\u2585\u2585\u2586\u2586\u2585\u2585\u2584\u2584\u2583\u2584\u2585\u2585",
98
- },
99
- ]
100
-
101
- // ============================================================================
102
- // Components
103
- // ============================================================================
104
-
105
- function MetricCard({ card, compact }: { card: CardData; compact: boolean }): JSX.Element {
106
- if (compact) {
107
- // Minimal: single-line card for narrow terminals
108
- return (
109
- <Box borderStyle="round" borderColor={card.color} paddingX={1} flexDirection="row" justifyContent="space-between">
110
- <H1 color={card.color}>{card.title}</H1>
111
- <H3>{card.value}</H3>
112
- </Box>
113
- )
114
- }
115
-
116
- // Full card with sparkline and details
117
- return (
118
- <Box borderStyle="round" borderColor={card.color} paddingX={1} flexDirection="column" flexGrow={1}>
119
- <Box justifyContent="space-between">
120
- <H1 color={card.color}>{card.title}</H1>
121
- <H1 color={card.color}>{card.value}</H1>
122
- </Box>
123
- <Text color={card.color}>{card.sparkline}</Text>
124
- <Small>{card.detail}</Small>
125
- </Box>
126
- )
127
- }
128
-
129
- function BreakpointIndicator({ width, columns }: { width: number; columns: number }): JSX.Element {
130
- const breakpoints = [
131
- { threshold: 0, cols: 1, label: "< 60" },
132
- { threshold: 60, cols: 2, label: "60-99" },
133
- { threshold: 100, cols: 3, label: "100+" },
134
- ]
135
-
136
- return (
137
- <Box gap={2} paddingX={1}>
138
- {breakpoints.map((bp) => {
139
- const isActive = bp.cols === columns
140
- return (
141
- <Box key={bp.cols} gap={1}>
142
- <Text color={isActive ? "green" : "gray"} bold={isActive}>
143
- {isActive ? "\u25cf" : "\u25cb"}
144
- </Text>
145
- <Text color={isActive ? "white" : "gray"} bold={isActive}>
146
- {bp.cols} col{bp.cols > 1 ? "s" : " "} ({bp.label})
147
- </Text>
148
- </Box>
149
- )
150
- })}
151
- </Box>
152
- )
153
- }
154
-
155
- function GridLayout({
156
- cards,
157
- columns,
158
- compact,
159
- }: {
160
- cards: CardData[]
161
- columns: number
162
- compact: boolean
163
- }): JSX.Element {
164
- if (columns === 1) {
165
- return (
166
- <Box flexDirection="column" gap={compact ? 0 : 1} flexGrow={1}>
167
- {cards.map((card) => (
168
- <MetricCard key={card.title} card={card} compact={compact} />
169
- ))}
170
- </Box>
171
- )
172
- }
173
-
174
- // Build rows of N columns
175
- const rows: CardData[][] = []
176
- for (let i = 0; i < cards.length; i += columns) {
177
- rows.push(cards.slice(i, i + columns))
178
- }
179
-
180
- return (
181
- <Box flexDirection="column" gap={1} flexGrow={1}>
182
- {rows.map((row, rowIndex) => (
183
- <Box key={rowIndex} flexDirection="row" gap={1}>
184
- {row.map((card) => (
185
- <Box key={card.title} flexGrow={1} flexBasis={0}>
186
- <MetricCard card={card} compact={false} />
187
- </Box>
188
- ))}
189
- {/* Fill remaining slots for even spacing */}
190
- {row.length < columns &&
191
- Array.from({ length: columns - row.length }, (_, i) => (
192
- <Box key={`spacer-${i}`} flexGrow={1} flexBasis={0} />
193
- ))}
194
- </Box>
195
- ))}
196
- </Box>
197
- )
198
- }
199
-
200
- function CodeSnippet({ width }: { width: number }): JSX.Element {
201
- const showSnippet = width >= 60
202
-
203
- if (!showSnippet) {
204
- return (
205
- <Box paddingX={1}>
206
- <Text dim italic>
207
- (Widen terminal to see the code that powers this)
208
- </Text>
209
- </Box>
210
- )
211
- }
212
-
213
- return (
214
- <Box flexDirection="column" borderStyle="single" borderColor="$border" paddingX={1}>
215
- <H1 color="yellow">How it works:</H1>
216
- <Text color="gray">
217
- {" "}
218
- <Text color="magenta">const</Text> {"{"} width {"}"} = <Text color="cyan">useContentRect</Text>()
219
- </Text>
220
- <Text color="gray">
221
- {" "}
222
- <Text color="magenta">const</Text> columns = width {">"} 100 ? <Text color="green">3</Text> : width {">"} 60 ?{" "}
223
- <Text color="green">2</Text> : <Text color="green">1</Text>
224
- </Text>
225
- <Text dim italic>
226
- {" "}// No useEffect, no layout thrashing. Synchronous.
227
- </Text>
228
- </Box>
229
- )
230
- }
231
-
232
- // ============================================================================
233
- // Main App
234
- // ============================================================================
235
-
236
- function LiveResize(): JSX.Element {
237
- const { width, height } = useContentRect()
238
-
239
- // Responsive breakpoints
240
- const columns = width >= 100 ? 3 : width >= 60 ? 2 : 1
241
- const compact = height < 20 || width < 40
242
-
243
- useInput(
244
- useCallback((input: string, key: Key) => {
245
- if (input === "q" || key.escape || (key.ctrl && input === "c")) {
246
- return "exit"
247
- }
248
- }, []),
249
- )
250
-
251
- return (
252
- <Box flexDirection="column" width="100%" height="100%" padding={1}>
253
- {/* Breakpoint indicator */}
254
- <BreakpointIndicator width={width} columns={columns} />
255
-
256
- {/* Main grid */}
257
- <Box flexGrow={1} flexDirection="column" marginTop={1}>
258
- <GridLayout cards={CARDS} columns={columns} compact={compact} />
259
- </Box>
260
-
261
- {/* Code snippet showing how it works */}
262
- {!compact && <CodeSnippet width={width} />}
263
-
264
- {/* Footer */}
265
- <Box justifyContent="space-between" paddingX={1}>
266
- <Muted>Resize your terminal to see the layout reflow</Muted>
267
- <Muted>
268
- <Kbd>Esc/q</Kbd> quit
269
- </Muted>
270
- </Box>
271
- </Box>
272
- )
273
- }
274
-
275
- // ============================================================================
276
- // Main
277
- // ============================================================================
278
-
279
- async function main() {
280
- const handle = await run(
281
- <ExampleBanner meta={meta} controls="Resize terminal to see reflow Esc/q quit">
282
- <LiveResize />
283
- </ExampleBanner>,
284
- )
285
- await handle.waitUntilExit()
286
- }
287
-
288
- if (import.meta.main) {
289
- main().catch(console.error)
290
- }
@@ -1,51 +0,0 @@
1
- import React from "react"
2
- import { render, Box, Text, useApp, useInput, createTerm } from "../../src/index.js"
3
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
4
-
5
- export const meta: ExampleMeta = {
6
- name: "Overflow",
7
- description: 'overflow="hidden" content clipping demonstration',
8
- features: ['overflow="hidden"', "Box height"],
9
- }
10
-
11
- export function OverflowApp() {
12
- const { exit } = useApp()
13
- useInput((input, key) => {
14
- if (input === "q" || key.escape) exit()
15
- })
16
-
17
- return (
18
- <Box flexDirection="column" padding={1}>
19
- <Text color="yellow">Title</Text>
20
-
21
- <Box borderStyle="single" borderColor="$primary" height={5} overflow="hidden">
22
- <Box flexDirection="column" flexGrow={1}>
23
- <Text>Line 1</Text>
24
- <Text>Line 2</Text>
25
- <Text>Line 3</Text>
26
- <Text>Line 4</Text>
27
- <Text>Line 5</Text>
28
- <Text>Line 6 - should NOT appear</Text>
29
- <Text>Line 7 - should NOT appear</Text>
30
- </Box>
31
- </Box>
32
-
33
- <Text color="$success">This should NOT be corrupted</Text>
34
- </Box>
35
- )
36
- }
37
-
38
- async function main() {
39
- using term = createTerm()
40
- const { waitUntilExit } = await render(
41
- <ExampleBanner meta={meta} controls="Esc/q quit">
42
- <OverflowApp />
43
- </ExampleBanner>,
44
- term,
45
- )
46
- await waitUntilExit()
47
- }
48
-
49
- if (import.meta.main) {
50
- main().catch(console.error)
51
- }
@@ -1,69 +0,0 @@
1
- # Silvery Canvas Playground
2
-
3
- Interactive browser demo of Silvery's Canvas 2D adapter. Renders React components to an HTML5 `<canvas>` element using the same layout engine and rendering pipeline as the terminal adapter.
4
-
5
- ## Quick Start
6
-
7
- ```bash
8
- # Build the playground bundle
9
- cd vendor/silvery
10
- bun run examples/playground/build.ts
11
-
12
- # Open in browser
13
- open examples/playground/index.html
14
- ```
15
-
16
- No dev server required -- just open `index.html` directly in any modern browser.
17
-
18
- ## What It Shows
19
-
20
- The playground includes seven preset examples accessible via buttons or number keys (1-7):
21
-
22
- | # | Preset | Demonstrates |
23
- | --- | ---------- | --------------------------------------------------------- |
24
- | 1 | Hello | Basic Box + Text, `useContentRect()` size display |
25
- | 2 | Text | Bold, italic, underline styles (single/double/curly/etc.) |
26
- | 3 | Colors | Named ANSI colors, hex, RGB, background fills |
27
- | 4 | Flexbox | Row/column layouts, `flexGrow`, `gap`, nested panels |
28
- | 5 | Borders | single, double, round, bold border styles |
29
- | 6 | Dashboard | Multi-panel system monitor layout |
30
- | 7 | Responsive | Layout adapts between horizontal/vertical based on width |
31
-
32
- Resize the browser window to see layouts recompute. The canvas size is shown in the bottom-right corner.
33
-
34
- ## Architecture
35
-
36
- The playground uses the same rendering pipeline as Silvery's terminal mode:
37
-
38
- ```
39
- React JSX
40
- | React reconciler builds SilveryNode tree
41
- v
42
- Flexily layout engine (pure JS flexbox)
43
- | Computes { x, y, width, height } for every node
44
- v
45
- Canvas adapter (CanvasRenderBuffer)
46
- | drawText(), fillRect(), drawChar() to OffscreenCanvas
47
- v
48
- Visible <canvas> element
49
- | ctx.drawImage(offscreenCanvas, 0, 0)
50
- v
51
- Browser display
52
- ```
53
-
54
- Key files:
55
-
56
- - `src/adapters/canvas-adapter.ts` -- Canvas `RenderAdapter` implementation
57
- - `src/canvas/index.ts` -- `renderToCanvas()` entry point and React integration
58
- - `src/render-adapter.ts` -- The `RenderAdapter` interface shared by all targets
59
-
60
- ## Building a Full Playground (Live JSX Editing)
61
-
62
- A static HTML page cannot bundle a JSX transpiler. For a full live-editing experience with Monaco editor, see `docs/playground-design.md`. The architecture uses:
63
-
64
- - **Vite** for dev server and HMR
65
- - **Monaco Editor** for JSX editing with TypeScript intellisense
66
- - **Sucrase** (in-browser) for JSX transpilation
67
- - **silvery/canvas** for rendering the user's components
68
-
69
- Deployment targets: GitHub Pages (static export), StackBlitz (zero-install), or self-hosted.
@@ -1,61 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Build the Canvas Playground
4
- *
5
- * Bundles the playground React app for browser usage.
6
- * Run: bun run examples/playground/build.ts
7
- */
8
-
9
- import { mkdir } from "node:fs/promises"
10
- import { join, dirname } from "node:path"
11
-
12
- const __dirname = dirname(new URL(import.meta.url).pathname)
13
- const distDir = join(__dirname, "dist")
14
-
15
- // Ensure dist directory exists
16
- await mkdir(distDir, { recursive: true })
17
-
18
- // Browser-safe defines for Node.js globals.
19
- // loggily and @silvery/ansi access process.env at module init,
20
- // which throws ReferenceError in browsers where `process` is undefined.
21
- const browserDefines: Record<string, string> = {
22
- "process.env.NODE_ENV": '"production"',
23
- "process.env.LOG_LEVEL": "undefined",
24
- "process.env.TRACE": "undefined",
25
- "process.env.TRACE_FORMAT": "undefined",
26
- "process.env.DEBUG": "undefined",
27
- "process.env.NO_COLOR": "undefined",
28
- "process.env.FORCE_COLOR": "undefined",
29
- "process.env.TERM": "undefined",
30
- "process.env.TERM_PROGRAM": "undefined",
31
- "process.env.COLORTERM": "undefined",
32
- "process.env.CI": "undefined",
33
- "process.env.GITHUB_ACTIONS": "undefined",
34
- "process.env.KITTY_WINDOW_ID": "undefined",
35
- "process.env.WT_SESSION": "undefined",
36
- "process.env.LANG": "undefined",
37
- "process.env.LC_ALL": "undefined",
38
- "process.env.LC_CTYPE": "undefined",
39
- }
40
-
41
- const result = await Bun.build({
42
- entrypoints: [join(__dirname, "playground-app.tsx")],
43
- outdir: distDir,
44
- target: "browser",
45
- format: "esm",
46
- minify: false,
47
- sourcemap: "external",
48
- define: browserDefines,
49
- })
50
-
51
- if (!result.success) {
52
- console.error("Playground build failed:")
53
- for (const log of result.logs) {
54
- console.error(log)
55
- }
56
- process.exit(1)
57
- }
58
-
59
- console.log("Built examples/playground/dist/playground-app.js")
60
- console.log("\nOpen in browser:")
61
- console.log(" examples/playground/index.html")