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,413 +0,0 @@
1
- /**
2
- * Virtual Scroll Benchmark — 10,000 Items
3
- *
4
- * Demonstrates that VirtualList handles massive datasets with instant scrolling.
5
- * Only visible items + overscan are rendered, regardless of total count.
6
- *
7
- * Demonstrates:
8
- * - VirtualList with 10,000 items and variable heights
9
- * - Smooth j/k navigation with position indicator
10
- * - useContentRect() for adaptive column count
11
- * - Page up/down with large jumps
12
- * - Visual item variety (priorities, tags, progress bars)
13
- *
14
- * Usage: bun run examples/virtual-10k/index.tsx
15
- *
16
- * Controls:
17
- * j/k or Up/Down - Navigate one item
18
- * d/u - Half-page down/up
19
- * g/G - Jump to first/last
20
- * / - Search by number
21
- * Esc/q or Ctrl+C - Quit
22
- */
23
-
24
- import React, { useState, useCallback, useMemo } from "react"
25
- import { Box, Text, Strong, Kbd, Muted, Divider, VirtualList, useContentRect } from "../../src/index.js"
26
- import { run, useInput, type Key } from "@silvery/term/runtime"
27
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
28
-
29
- export const meta: ExampleMeta = {
30
- name: "Virtual 10K",
31
- description: "VirtualList scrolling through 10,000 items with instant navigation",
32
- features: ["VirtualList", "10K items", "useContentRect()", "variable itemHeight"],
33
- }
34
-
35
- // ============================================================================
36
- // Types
37
- // ============================================================================
38
-
39
- interface Item {
40
- id: number
41
- title: string
42
- priority: "P0" | "P1" | "P2" | "P3"
43
- status: "todo" | "in-progress" | "done" | "blocked"
44
- tags: string[]
45
- progress: number
46
- description: string
47
- }
48
-
49
- // ============================================================================
50
- // Data Generation
51
- // ============================================================================
52
-
53
- const PRIORITIES: Item["priority"][] = ["P0", "P1", "P2", "P3"]
54
- const STATUSES: Item["status"][] = ["todo", "in-progress", "done", "blocked"]
55
- const TAG_POOL = [
56
- "frontend",
57
- "backend",
58
- "api",
59
- "database",
60
- "security",
61
- "performance",
62
- "ux",
63
- "docs",
64
- "testing",
65
- "devops",
66
- "mobile",
67
- "infra",
68
- ]
69
-
70
- const ADJECTIVES = [
71
- "Implement",
72
- "Fix",
73
- "Refactor",
74
- "Optimize",
75
- "Design",
76
- "Review",
77
- "Update",
78
- "Add",
79
- "Remove",
80
- "Migrate",
81
- "Configure",
82
- "Deploy",
83
- ]
84
-
85
- const NOUNS = [
86
- "authentication flow",
87
- "database schema",
88
- "API endpoint",
89
- "caching layer",
90
- "error handling",
91
- "test suite",
92
- "CI pipeline",
93
- "monitoring",
94
- "rate limiter",
95
- "search index",
96
- "notification system",
97
- "user dashboard",
98
- "payment processing",
99
- "file upload",
100
- "websocket handler",
101
- "session manager",
102
- ]
103
-
104
- function seededRandom(seed: number): () => number {
105
- let s = seed
106
- return () => {
107
- s = (s * 1664525 + 1013904223) & 0x7fffffff
108
- return s / 0x7fffffff
109
- }
110
- }
111
-
112
- function generateItems(count: number): Item[] {
113
- const rng = seededRandom(42)
114
- const items: Item[] = []
115
-
116
- for (let i = 0; i < count; i++) {
117
- const adj = ADJECTIVES[Math.floor(rng() * ADJECTIVES.length)]!
118
- const noun = NOUNS[Math.floor(rng() * NOUNS.length)]!
119
- const priority = PRIORITIES[Math.floor(rng() * PRIORITIES.length)]!
120
- const status = STATUSES[Math.floor(rng() * STATUSES.length)]!
121
- const tagCount = 1 + Math.floor(rng() * 3)
122
- const tags: string[] = []
123
- for (let t = 0; t < tagCount; t++) {
124
- const tag = TAG_POOL[Math.floor(rng() * TAG_POOL.length)]!
125
- if (!tags.includes(tag)) tags.push(tag)
126
- }
127
- const progress = status === "done" ? 100 : status === "todo" ? 0 : Math.floor(rng() * 90) + 5
128
-
129
- items.push({
130
- id: i + 1,
131
- title: `${adj} ${noun}`,
132
- priority,
133
- status,
134
- tags,
135
- progress,
136
- description: `Task #${i + 1}: ${adj.toLowerCase()} the ${noun} for improved reliability.`,
137
- })
138
- }
139
-
140
- return items
141
- }
142
-
143
- const TOTAL_ITEMS = 10_000
144
- const ALL_ITEMS = generateItems(TOTAL_ITEMS)
145
-
146
- // ============================================================================
147
- // Components
148
- // ============================================================================
149
-
150
- const PRIORITY_COLORS: Record<Item["priority"], string> = {
151
- P0: "$error",
152
- P1: "$warning",
153
- P2: "$info",
154
- P3: "$muted",
155
- }
156
-
157
- const STATUS_ICONS: Record<Item["status"], string> = {
158
- todo: "○",
159
- "in-progress": "◔",
160
- done: "●",
161
- blocked: "■",
162
- }
163
-
164
- const STATUS_COLORS: Record<Item["status"], string> = {
165
- todo: "$muted",
166
- "in-progress": "$warning",
167
- done: "$success",
168
- blocked: "$error",
169
- }
170
-
171
- function ProgressBar({ percent, width: barWidth }: { percent: number; width: number }): JSX.Element {
172
- const effectiveWidth = Math.max(5, barWidth)
173
- const filled = Math.round((percent / 100) * effectiveWidth)
174
- const empty = effectiveWidth - filled
175
-
176
- return (
177
- <Text>
178
- <Text color="$success">{"█".repeat(filled)}</Text>
179
- <Text dim>{"░".repeat(empty)}</Text>
180
- </Text>
181
- )
182
- }
183
-
184
- function ItemRow({
185
- item,
186
- isSelected,
187
- showDetail,
188
- }: {
189
- item: Item
190
- isSelected: boolean
191
- showDetail: boolean
192
- }): JSX.Element {
193
- const idStr = String(item.id).padStart(5, " ")
194
-
195
- return (
196
- <Box flexDirection="column" paddingX={1} backgroundColor={isSelected ? "$primary" : undefined}>
197
- <Box>
198
- <Text color={STATUS_COLORS[item.status]}>{STATUS_ICONS[item.status]}</Text>
199
- <Text dim> {idStr} </Text>
200
- <Text bold color={PRIORITY_COLORS[item.priority]}>
201
- {item.priority}
202
- </Text>
203
- <Text> </Text>
204
- <Text bold={isSelected}>{item.title}</Text>
205
- <Text> </Text>
206
- {item.tags.map((tag) => (
207
- <Text key={tag} dim color="$info">
208
- {" "}
209
- #{tag}
210
- </Text>
211
- ))}
212
- </Box>
213
- {showDetail && (
214
- <Box paddingLeft={8}>
215
- <Text dim>{item.description}</Text>
216
- <Text> </Text>
217
- <ProgressBar percent={item.progress} width={10} />
218
- <Text dim> {item.progress}%</Text>
219
- </Box>
220
- )}
221
- </Box>
222
- )
223
- }
224
-
225
- function ScrollIndicator({ current, total, width }: { current: number; total: number; width: number }): JSX.Element {
226
- const percent = total > 0 ? Math.round(((current + 1) / total) * 100) : 0
227
-
228
- // Progress bar
229
- const barWidth = Math.max(10, Math.min(30, width - 40))
230
- const filled = Math.round((percent / 100) * barWidth)
231
- const empty = barWidth - filled
232
-
233
- return (
234
- <Box gap={2} paddingX={1}>
235
- <Strong color="$primary">{(current + 1).toLocaleString()}</Strong>
236
- <Text dim>of</Text>
237
- <Strong>{total.toLocaleString()}</Strong>
238
- <Text>
239
- <Text color="$primary">{"█".repeat(filled)}</Text>
240
- <Text dim>{"░".repeat(empty)}</Text>
241
- </Text>
242
- <Strong color="$primary">{percent}%</Strong>
243
- </Box>
244
- )
245
- }
246
-
247
- function StatsBar({ items }: { items: Item[] }): JSX.Element {
248
- const stats = useMemo(() => {
249
- let p0 = 0,
250
- p1 = 0,
251
- p2 = 0,
252
- p3 = 0
253
- let todo = 0,
254
- inProg = 0,
255
- done = 0,
256
- blocked = 0
257
- for (const item of items) {
258
- if (item.priority === "P0") p0++
259
- else if (item.priority === "P1") p1++
260
- else if (item.priority === "P2") p2++
261
- else p3++
262
- if (item.status === "todo") todo++
263
- else if (item.status === "in-progress") inProg++
264
- else if (item.status === "done") done++
265
- else blocked++
266
- }
267
- return { p0, p1, p2, p3, todo, inProg, done, blocked }
268
- }, [items])
269
-
270
- return (
271
- <Box gap={2} paddingX={1}>
272
- <Strong color="$error">P0:{stats.p0}</Strong>
273
- <Strong color="$warning">P1:{stats.p1}</Strong>
274
- <Text color="$info">P2:{stats.p2}</Text>
275
- <Text dim>P3:{stats.p3}</Text>
276
- <Text dim>|</Text>
277
- <Text color="$muted">
278
- {STATUS_ICONS.todo} {stats.todo}
279
- </Text>
280
- <Text color="$warning">
281
- {STATUS_ICONS["in-progress"]} {stats.inProg}
282
- </Text>
283
- <Text color="$success">
284
- {STATUS_ICONS.done} {stats.done}
285
- </Text>
286
- <Text color="$error">
287
- {STATUS_ICONS.blocked} {stats.blocked}
288
- </Text>
289
- </Box>
290
- )
291
- }
292
-
293
- // ============================================================================
294
- // Main App
295
- // ============================================================================
296
-
297
- function VirtualBenchmark(): JSX.Element {
298
- const { width, height } = useContentRect()
299
- const [cursor, setCursor] = useState(0)
300
- const [showDetail, setShowDetail] = useState(false)
301
-
302
- // Calculate available list height
303
- // stats (1) + separator (1) + scroll indicator (1) + help (1) + borders
304
- const listHeight = Math.max(5, height - 5)
305
- const halfPage = Math.max(1, Math.floor(listHeight / 2))
306
-
307
- const itemHeight = useCallback(
308
- (_item: Item, index: number) => {
309
- if (showDetail && index === cursor) return 2
310
- return 1
311
- },
312
- [showDetail, cursor],
313
- )
314
-
315
- useInput(
316
- useCallback(
317
- (input: string, key: Key) => {
318
- if (input === "q" || key.escape || (key.ctrl && input === "c")) {
319
- return "exit"
320
- }
321
-
322
- // Navigation
323
- if (input === "j" || key.downArrow) {
324
- setCursor((c) => Math.min(TOTAL_ITEMS - 1, c + 1))
325
- }
326
- if (input === "k" || key.upArrow) {
327
- setCursor((c) => Math.max(0, c - 1))
328
- }
329
-
330
- // Half-page
331
- if (input === "d" || key.pageDown) {
332
- setCursor((c) => Math.min(TOTAL_ITEMS - 1, c + halfPage))
333
- }
334
- if (input === "u" || key.pageUp) {
335
- setCursor((c) => Math.max(0, c - halfPage))
336
- }
337
-
338
- // Jump to start/end
339
- if (input === "g" || key.home) {
340
- setCursor(0)
341
- }
342
- if (input === "G" || key.end) {
343
- setCursor(TOTAL_ITEMS - 1)
344
- }
345
-
346
- // Toggle detail view
347
- if (key.return || input === " ") {
348
- setShowDetail((d) => !d)
349
- }
350
- },
351
- [halfPage],
352
- ),
353
- )
354
-
355
- return (
356
- <Box flexDirection="column" width="100%" height="100%">
357
- {/* Stats */}
358
- <StatsBar items={ALL_ITEMS} />
359
-
360
- {/* Separator */}
361
- <Box paddingX={1}>
362
- <Divider />
363
- </Box>
364
-
365
- {/* Virtual list */}
366
- <Box flexGrow={1}>
367
- <VirtualList
368
- items={ALL_ITEMS}
369
- height={listHeight}
370
- itemHeight={itemHeight}
371
- scrollTo={cursor}
372
- overscan={5}
373
- renderItem={(item, index) => (
374
- <ItemRow
375
- key={item.id}
376
- item={item}
377
- isSelected={index === cursor}
378
- showDetail={showDetail && index === cursor}
379
- />
380
- )}
381
- />
382
- </Box>
383
-
384
- {/* Scroll position */}
385
- <ScrollIndicator current={cursor} total={TOTAL_ITEMS} width={width} />
386
-
387
- {/* Help */}
388
- <Box paddingX={1} justifyContent="center">
389
- <Muted>
390
- <Kbd>j/k</Kbd> navigate <Kbd>d/u</Kbd> half-page <Kbd>g/G</Kbd> start/end <Kbd>Enter</Kbd> detail{" "}
391
- <Kbd>Esc/q</Kbd> quit
392
- </Muted>
393
- </Box>
394
- </Box>
395
- )
396
- }
397
-
398
- // ============================================================================
399
- // Main
400
- // ============================================================================
401
-
402
- async function main() {
403
- const handle = await run(
404
- <ExampleBanner meta={meta} controls="j/k navigate d/u half-page g/G start/end Enter detail Esc/q quit">
405
- <VirtualBenchmark />
406
- </ExampleBanner>,
407
- )
408
- await handle.waitUntilExit()
409
- }
410
-
411
- if (import.meta.main) {
412
- main().catch(console.error)
413
- }