@silvery/examples 0.5.6 → 0.17.4

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 (112) hide show
  1. package/dist/UPNG-Cy7ViL8f.mjs +5074 -0
  2. package/dist/__vite-browser-external-2447137e-BML7CYau.mjs +4 -0
  3. package/dist/_banner-DLPxCqVy.mjs +44 -0
  4. package/dist/ansi-CCE2pVS0.mjs +16397 -0
  5. package/dist/apng-HhhBjRGt.mjs +68 -0
  6. package/dist/apng-mwUQbTTF.mjs +3 -0
  7. package/dist/apps/aichat/index.mjs +1299 -0
  8. package/dist/apps/app-todo.mjs +139 -0
  9. package/dist/apps/async-data.mjs +204 -0
  10. package/dist/apps/cli-wizard.mjs +339 -0
  11. package/dist/apps/clipboard.mjs +198 -0
  12. package/dist/apps/components.mjs +864 -0
  13. package/dist/apps/data-explorer.mjs +483 -0
  14. package/dist/apps/dev-tools.mjs +397 -0
  15. package/dist/apps/explorer.mjs +698 -0
  16. package/dist/apps/gallery.mjs +766 -0
  17. package/dist/apps/inline-bench.mjs +115 -0
  18. package/dist/apps/kanban.mjs +280 -0
  19. package/dist/apps/layout-ref.mjs +187 -0
  20. package/dist/apps/outline.mjs +203 -0
  21. package/dist/apps/paste-demo.mjs +189 -0
  22. package/dist/apps/scroll.mjs +86 -0
  23. package/dist/apps/search-filter.mjs +287 -0
  24. package/dist/apps/selection.mjs +355 -0
  25. package/dist/apps/spatial-focus-demo.mjs +388 -0
  26. package/dist/apps/task-list.mjs +258 -0
  27. package/dist/apps/terminal-caps-demo.mjs +315 -0
  28. package/dist/apps/terminal.mjs +872 -0
  29. package/dist/apps/text-selection-demo.mjs +254 -0
  30. package/dist/apps/textarea.mjs +178 -0
  31. package/dist/apps/theme.mjs +661 -0
  32. package/dist/apps/transform.mjs +215 -0
  33. package/dist/apps/virtual-10k.mjs +422 -0
  34. package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
  35. package/dist/backends-Bahh9mKN.mjs +1179 -0
  36. package/dist/backends-CCtCDQ94.mjs +3 -0
  37. package/dist/{cli.mjs → bin/cli.mjs} +21 -25
  38. package/dist/chunk-BSw8zbkd.mjs +37 -0
  39. package/dist/components/counter.mjs +48 -0
  40. package/dist/components/hello.mjs +31 -0
  41. package/dist/components/progress-bar.mjs +59 -0
  42. package/dist/components/select-list.mjs +85 -0
  43. package/dist/components/spinner.mjs +57 -0
  44. package/dist/components/text-input.mjs +62 -0
  45. package/dist/components/virtual-list.mjs +51 -0
  46. package/dist/flexily-zero-adapter-UB-ra8fR.mjs +3374 -0
  47. package/dist/gif-BZaqPPVX.mjs +3 -0
  48. package/dist/gif-BtnXuxLF.mjs +71 -0
  49. package/dist/gifenc-CLRW41dk.mjs +728 -0
  50. package/dist/jsx-runtime-dMs_8fNu.mjs +241 -0
  51. package/dist/key-mapping-5oYQdAQE.mjs +3 -0
  52. package/dist/key-mapping-D4LR1go6.mjs +130 -0
  53. package/dist/layout/dashboard.mjs +1204 -0
  54. package/dist/layout/live-resize.mjs +303 -0
  55. package/dist/layout/overflow.mjs +70 -0
  56. package/dist/layout/text-layout.mjs +335 -0
  57. package/dist/node-NuJ94BWl.mjs +1083 -0
  58. package/dist/plugins-D1KtkT4a.mjs +3057 -0
  59. package/dist/resvg-js-C_8Wps1F.mjs +201 -0
  60. package/dist/src-BTEVGpd9.mjs +23538 -0
  61. package/dist/src-CUUOuRH6.mjs +5322 -0
  62. package/dist/src-CzfRafCQ.mjs +814 -0
  63. package/dist/usingCtx-CsEf0xO3.mjs +57 -0
  64. package/dist/yoga-adapter-BVtQ5OJR.mjs +237 -0
  65. package/package.json +19 -14
  66. package/_banner.tsx +0 -60
  67. package/apps/aichat/components.tsx +0 -469
  68. package/apps/aichat/index.tsx +0 -220
  69. package/apps/aichat/script.ts +0 -460
  70. package/apps/aichat/state.ts +0 -325
  71. package/apps/aichat/types.ts +0 -19
  72. package/apps/app-todo.tsx +0 -201
  73. package/apps/async-data.tsx +0 -196
  74. package/apps/cli-wizard.tsx +0 -332
  75. package/apps/clipboard.tsx +0 -183
  76. package/apps/components.tsx +0 -658
  77. package/apps/data-explorer.tsx +0 -490
  78. package/apps/dev-tools.tsx +0 -395
  79. package/apps/explorer.tsx +0 -731
  80. package/apps/gallery.tsx +0 -653
  81. package/apps/inline-bench.tsx +0 -138
  82. package/apps/kanban.tsx +0 -265
  83. package/apps/layout-ref.tsx +0 -173
  84. package/apps/outline.tsx +0 -160
  85. package/apps/panes/index.tsx +0 -203
  86. package/apps/paste-demo.tsx +0 -185
  87. package/apps/scroll.tsx +0 -77
  88. package/apps/search-filter.tsx +0 -240
  89. package/apps/selection.tsx +0 -342
  90. package/apps/spatial-focus-demo.tsx +0 -368
  91. package/apps/task-list.tsx +0 -271
  92. package/apps/terminal-caps-demo.tsx +0 -334
  93. package/apps/terminal.tsx +0 -800
  94. package/apps/text-selection-demo.tsx +0 -189
  95. package/apps/textarea.tsx +0 -155
  96. package/apps/theme.tsx +0 -515
  97. package/apps/transform.tsx +0 -229
  98. package/apps/virtual-10k.tsx +0 -405
  99. package/apps/vterm-demo/index.tsx +0 -216
  100. package/components/counter.tsx +0 -45
  101. package/components/hello.tsx +0 -34
  102. package/components/progress-bar.tsx +0 -48
  103. package/components/select-list.tsx +0 -50
  104. package/components/spinner.tsx +0 -40
  105. package/components/text-input.tsx +0 -57
  106. package/components/virtual-list.tsx +0 -52
  107. package/dist/cli.d.mts +0 -1
  108. package/dist/cli.mjs.map +0 -1
  109. package/layout/dashboard.tsx +0 -953
  110. package/layout/live-resize.tsx +0 -282
  111. package/layout/overflow.tsx +0 -51
  112. package/layout/text-layout.tsx +0 -283
@@ -0,0 +1,215 @@
1
+ import { t as _usingCtx } from "../usingCtx-CsEf0xO3.mjs";
2
+ import { t as require_jsx_runtime } from "../jsx-runtime-dMs_8fNu.mjs";
3
+ import { t as ExampleBanner } from "../_banner-DLPxCqVy.mjs";
4
+ import { useState } from "react";
5
+ import { Box, H1, Kbd, Muted, Small, Text, Transform, createTerm, render, useApp, useInput } from "silvery";
6
+ //#region apps/transform.tsx
7
+ /**
8
+ * Transform Component Demo
9
+ *
10
+ * Shows the Transform component for text post-processing. Each transform
11
+ * applies a string transformation to every line of rendered text output.
12
+ *
13
+ * Features:
14
+ * - Multiple transforms: uppercase, leetspeak, reverse, ROT13, etc.
15
+ * - Cycle through transforms with j/k
16
+ * - Shows original and transformed text side by side
17
+ * - Uses Transform from silvery components
18
+ *
19
+ * Run: bun vendor/silvery/examples/apps/transform.tsx
20
+ */
21
+ var import_jsx_runtime = require_jsx_runtime();
22
+ const meta = {
23
+ name: "Transform",
24
+ description: "Text post-processing with the Transform component",
25
+ features: [
26
+ "Transform",
27
+ "transform function",
28
+ "side-by-side comparison"
29
+ ]
30
+ };
31
+ const leetMap = {
32
+ a: "4",
33
+ e: "3",
34
+ i: "1",
35
+ o: "0",
36
+ s: "5",
37
+ t: "7",
38
+ A: "4",
39
+ E: "3",
40
+ I: "1",
41
+ O: "0",
42
+ S: "5",
43
+ T: "7"
44
+ };
45
+ const rot13Char = (c) => {
46
+ const code = c.charCodeAt(0);
47
+ if (code >= 65 && code <= 90) return String.fromCharCode((code - 65 + 13) % 26 + 65);
48
+ if (code >= 97 && code <= 122) return String.fromCharCode((code - 97 + 13) % 26 + 97);
49
+ return c;
50
+ };
51
+ const transforms = [
52
+ {
53
+ name: "Uppercase",
54
+ description: "Convert all characters to upper case",
55
+ fn: (s) => s.toUpperCase()
56
+ },
57
+ {
58
+ name: "Lowercase",
59
+ description: "Convert all characters to lower case",
60
+ fn: (s) => s.toLowerCase()
61
+ },
62
+ {
63
+ name: "Leetspeak",
64
+ description: "Replace letters with numbers (a=4, e=3, i=1, ...)",
65
+ fn: (s) => s.split("").map((c) => leetMap[c] ?? c).join("")
66
+ },
67
+ {
68
+ name: "Reverse",
69
+ description: "Reverse each line of text",
70
+ fn: (s) => s.split("").reverse().join("")
71
+ },
72
+ {
73
+ name: "ROT13",
74
+ description: "Caesar cipher — shift each letter by 13 positions",
75
+ fn: (s) => s.split("").map(rot13Char).join("")
76
+ },
77
+ {
78
+ name: "Alternating Case",
79
+ description: "Alternate between upper and lower case characters",
80
+ fn: (s) => s.split("").map((c, i) => i % 2 === 0 ? c.toUpperCase() : c.toLowerCase()).join("")
81
+ },
82
+ {
83
+ name: "Spaces to Dots",
84
+ description: "Replace spaces with middle dots for visibility",
85
+ fn: (s) => s.replace(/ /g, "·")
86
+ }
87
+ ];
88
+ const sampleLines = [
89
+ "The quick brown fox jumps",
90
+ "over the lazy dog on a",
91
+ "beautiful sunny afternoon.",
92
+ "",
93
+ "Pack my box with five dozen",
94
+ "liquor jugs and enjoy them."
95
+ ];
96
+ function TransformSelector({ current, transforms: items }) {
97
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
98
+ flexDirection: "column",
99
+ overflow: "scroll",
100
+ scrollTo: current,
101
+ height: 7,
102
+ children: items.map((t, index) => {
103
+ const isSelected = index === current;
104
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
105
+ paddingX: 1,
106
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
107
+ color: isSelected ? "$bg" : void 0,
108
+ backgroundColor: isSelected ? "$primary" : void 0,
109
+ bold: isSelected,
110
+ children: [isSelected ? " > " : " ", t.name]
111
+ })
112
+ }, t.name);
113
+ })
114
+ });
115
+ }
116
+ function TextPanel({ title, titleColor, children }) {
117
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
118
+ flexDirection: "column",
119
+ flexGrow: 1,
120
+ borderStyle: "round",
121
+ borderColor: "$border",
122
+ paddingX: 1,
123
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
124
+ marginBottom: 1,
125
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(H1, {
126
+ color: titleColor,
127
+ children: title
128
+ })
129
+ }), children]
130
+ });
131
+ }
132
+ function TransformDemo() {
133
+ const { exit } = useApp();
134
+ const [currentIndex, setCurrentIndex] = useState(0);
135
+ const current = transforms[currentIndex];
136
+ useInput((input, key) => {
137
+ if (input === "q" || key.escape) {
138
+ exit();
139
+ return;
140
+ }
141
+ if (key.upArrow || input === "k") setCurrentIndex((prev) => Math.max(0, prev - 1));
142
+ if (key.downArrow || input === "j") setCurrentIndex((prev) => Math.min(transforms.length - 1, prev + 1));
143
+ });
144
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
145
+ flexDirection: "column",
146
+ padding: 1,
147
+ gap: 1,
148
+ children: [
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
150
+ flexDirection: "column",
151
+ borderStyle: "round",
152
+ borderColor: "$primary",
153
+ paddingX: 1,
154
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
155
+ marginBottom: 1,
156
+ gap: 1,
157
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(H1, { children: "Transform" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Small, { children: [
158
+ "— ",
159
+ current.name,
160
+ ": ",
161
+ current.description
162
+ ] })]
163
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TransformSelector, {
164
+ current: currentIndex,
165
+ transforms
166
+ })]
167
+ }),
168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
169
+ flexDirection: "row",
170
+ gap: 1,
171
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextPanel, {
172
+ title: "Original",
173
+ titleColor: "$muted",
174
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
175
+ flexDirection: "column",
176
+ children: sampleLines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: line || " " }, i))
177
+ })
178
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextPanel, {
179
+ title: `${current.name}`,
180
+ titleColor: "$warning",
181
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Transform, {
182
+ transform: current.fn,
183
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: sampleLines.join("\n") })
184
+ })
185
+ })]
186
+ }),
187
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Muted, { children: [
188
+ " ",
189
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children: "j/k" }),
190
+ " select transform ",
191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children: "Esc/q" }),
192
+ " quit"
193
+ ] })
194
+ ]
195
+ });
196
+ }
197
+ async function main() {
198
+ try {
199
+ var _usingCtx$1 = _usingCtx();
200
+ const term = _usingCtx$1.u(createTerm());
201
+ const { waitUntilExit } = await render(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ExampleBanner, {
202
+ meta,
203
+ controls: "j/k select transform Esc/q quit",
204
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TransformDemo, {})
205
+ }), term);
206
+ await waitUntilExit();
207
+ } catch (_) {
208
+ _usingCtx$1.e = _;
209
+ } finally {
210
+ _usingCtx$1.d();
211
+ }
212
+ }
213
+ if (import.meta.main) await main();
214
+ //#endregion
215
+ export { TransformDemo, main, meta };
@@ -0,0 +1,422 @@
1
+ import { t as require_jsx_runtime } from "../jsx-runtime-dMs_8fNu.mjs";
2
+ import { t as ExampleBanner } from "../_banner-DLPxCqVy.mjs";
3
+ import { useCallback, useMemo, useState } from "react";
4
+ import { Box, Divider, Kbd, ListView, Muted, Strong, Text, useBoxRect } from "silvery";
5
+ import { run, useInput as useInput$1 } from "silvery/runtime";
6
+ //#region apps/virtual-10k.tsx
7
+ /**
8
+ * Virtual Scroll Benchmark — 10,000 Items
9
+ *
10
+ * Demonstrates that ListView handles massive datasets with instant scrolling.
11
+ * Only visible items + overscan are rendered, regardless of total count.
12
+ *
13
+ * Demonstrates:
14
+ * - ListView with 10,000 items and variable heights
15
+ * - Smooth j/k navigation with position indicator
16
+ * - useBoxRect() for adaptive column count
17
+ * - Page up/down with large jumps
18
+ * - Visual item variety (priorities, tags, progress bars)
19
+ *
20
+ * Usage: bun run examples/apps/virtual-10k.tsx
21
+ *
22
+ * Controls:
23
+ * j/k or Up/Down - Navigate one item
24
+ * d/u - Half-page down/up
25
+ * g/G - Jump to first/last
26
+ * / - Search by number
27
+ * Esc/q or Ctrl+C - Quit
28
+ */
29
+ var import_jsx_runtime = require_jsx_runtime();
30
+ const meta = {
31
+ name: "Virtual 10K",
32
+ description: "ListView scrolling through 10,000 items with instant navigation",
33
+ features: [
34
+ "ListView",
35
+ "10K items",
36
+ "useBoxRect()",
37
+ "variable estimateHeight"
38
+ ]
39
+ };
40
+ const PRIORITIES = [
41
+ "P0",
42
+ "P1",
43
+ "P2",
44
+ "P3"
45
+ ];
46
+ const STATUSES = [
47
+ "todo",
48
+ "in-progress",
49
+ "done",
50
+ "blocked"
51
+ ];
52
+ const TAG_POOL = [
53
+ "frontend",
54
+ "backend",
55
+ "api",
56
+ "database",
57
+ "security",
58
+ "performance",
59
+ "ux",
60
+ "docs",
61
+ "testing",
62
+ "devops",
63
+ "mobile",
64
+ "infra"
65
+ ];
66
+ const ADJECTIVES = [
67
+ "Implement",
68
+ "Fix",
69
+ "Refactor",
70
+ "Optimize",
71
+ "Design",
72
+ "Review",
73
+ "Update",
74
+ "Add",
75
+ "Remove",
76
+ "Migrate",
77
+ "Configure",
78
+ "Deploy"
79
+ ];
80
+ const NOUNS = [
81
+ "authentication flow",
82
+ "database schema",
83
+ "API endpoint",
84
+ "caching layer",
85
+ "error handling",
86
+ "test suite",
87
+ "CI pipeline",
88
+ "monitoring",
89
+ "rate limiter",
90
+ "search index",
91
+ "notification system",
92
+ "user dashboard",
93
+ "payment processing",
94
+ "file upload",
95
+ "websocket handler",
96
+ "session manager"
97
+ ];
98
+ function seededRandom(seed) {
99
+ let s = seed;
100
+ return () => {
101
+ s = s * 1664525 + 1013904223 & 2147483647;
102
+ return s / 2147483647;
103
+ };
104
+ }
105
+ function generateItems(count) {
106
+ const rng = seededRandom(42);
107
+ const items = [];
108
+ for (let i = 0; i < count; i++) {
109
+ const adj = ADJECTIVES[Math.floor(rng() * ADJECTIVES.length)];
110
+ const noun = NOUNS[Math.floor(rng() * NOUNS.length)];
111
+ const priority = PRIORITIES[Math.floor(rng() * PRIORITIES.length)];
112
+ const status = STATUSES[Math.floor(rng() * STATUSES.length)];
113
+ const tagCount = 1 + Math.floor(rng() * 3);
114
+ const tags = [];
115
+ for (let t = 0; t < tagCount; t++) {
116
+ const tag = TAG_POOL[Math.floor(rng() * TAG_POOL.length)];
117
+ if (!tags.includes(tag)) tags.push(tag);
118
+ }
119
+ const progress = status === "done" ? 100 : status === "todo" ? 0 : Math.floor(rng() * 90) + 5;
120
+ items.push({
121
+ id: i + 1,
122
+ title: `${adj} ${noun}`,
123
+ priority,
124
+ status,
125
+ tags,
126
+ progress,
127
+ description: `Task #${i + 1}: ${adj.toLowerCase()} the ${noun} for improved reliability.`
128
+ });
129
+ }
130
+ return items;
131
+ }
132
+ const TOTAL_ITEMS = 1e4;
133
+ const ALL_ITEMS = generateItems(TOTAL_ITEMS);
134
+ const PRIORITY_COLORS = {
135
+ P0: "$error",
136
+ P1: "$warning",
137
+ P2: "$info",
138
+ P3: "$muted"
139
+ };
140
+ const STATUS_ICONS = {
141
+ todo: "○",
142
+ "in-progress": "◔",
143
+ done: "●",
144
+ blocked: "■"
145
+ };
146
+ const STATUS_COLORS = {
147
+ todo: "$muted",
148
+ "in-progress": "$warning",
149
+ done: "$success",
150
+ blocked: "$error"
151
+ };
152
+ function ProgressBar$1({ percent, width: barWidth }) {
153
+ const effectiveWidth = Math.max(5, barWidth);
154
+ const filled = Math.round(percent / 100 * effectiveWidth);
155
+ const empty = effectiveWidth - filled;
156
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
157
+ color: "$success",
158
+ children: "█".repeat(filled)
159
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
160
+ dim: true,
161
+ children: "░".repeat(empty)
162
+ })] });
163
+ }
164
+ function ItemRow({ item, isSelected, showDetail }) {
165
+ const idStr = String(item.id).padStart(5, " ");
166
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
167
+ flexDirection: "column",
168
+ paddingX: 1,
169
+ backgroundColor: isSelected ? "$primary" : void 0,
170
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, { children: [
171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
172
+ color: STATUS_COLORS[item.status],
173
+ children: STATUS_ICONS[item.status]
174
+ }),
175
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
176
+ dim: true,
177
+ children: [
178
+ " ",
179
+ idStr,
180
+ " "
181
+ ]
182
+ }),
183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
184
+ bold: true,
185
+ color: PRIORITY_COLORS[item.priority],
186
+ children: item.priority
187
+ }),
188
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: " " }),
189
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
190
+ bold: isSelected,
191
+ children: item.title
192
+ }),
193
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: " " }),
194
+ item.tags.map((tag) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
195
+ dim: true,
196
+ color: "$info",
197
+ children: [
198
+ " ",
199
+ "#",
200
+ tag
201
+ ]
202
+ }, tag))
203
+ ] }), showDetail && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
204
+ paddingLeft: 8,
205
+ children: [
206
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
207
+ dim: true,
208
+ children: item.description
209
+ }),
210
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: " " }),
211
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProgressBar$1, {
212
+ percent: item.progress,
213
+ width: 10
214
+ }),
215
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
216
+ dim: true,
217
+ children: [
218
+ " ",
219
+ item.progress,
220
+ "%"
221
+ ]
222
+ })
223
+ ]
224
+ })]
225
+ });
226
+ }
227
+ function ScrollIndicator({ current, total, width }) {
228
+ const percent = total > 0 ? Math.round((current + 1) / total * 100) : 0;
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
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
233
+ gap: 2,
234
+ paddingX: 1,
235
+ children: [
236
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Strong, {
237
+ color: "$primary",
238
+ children: (current + 1).toLocaleString()
239
+ }),
240
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
241
+ dim: true,
242
+ children: "of"
243
+ }),
244
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Strong, { children: total.toLocaleString() }),
245
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
246
+ color: "$primary",
247
+ children: "█".repeat(filled)
248
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
249
+ dim: true,
250
+ children: "░".repeat(empty)
251
+ })] }),
252
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Strong, {
253
+ color: "$primary",
254
+ children: [percent, "%"]
255
+ })
256
+ ]
257
+ });
258
+ }
259
+ function StatsBar({ items }) {
260
+ const stats = useMemo(() => {
261
+ let p0 = 0, p1 = 0, p2 = 0, p3 = 0;
262
+ let todo = 0, inProg = 0, done = 0, blocked = 0;
263
+ for (const item of items) {
264
+ if (item.priority === "P0") p0++;
265
+ else if (item.priority === "P1") p1++;
266
+ else if (item.priority === "P2") p2++;
267
+ else p3++;
268
+ if (item.status === "todo") todo++;
269
+ else if (item.status === "in-progress") inProg++;
270
+ else if (item.status === "done") done++;
271
+ else blocked++;
272
+ }
273
+ return {
274
+ p0,
275
+ p1,
276
+ p2,
277
+ p3,
278
+ todo,
279
+ inProg,
280
+ done,
281
+ blocked
282
+ };
283
+ }, [items]);
284
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
285
+ gap: 2,
286
+ paddingX: 1,
287
+ children: [
288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Strong, {
289
+ color: "$error",
290
+ children: ["P0:", stats.p0]
291
+ }),
292
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Strong, {
293
+ color: "$warning",
294
+ children: ["P1:", stats.p1]
295
+ }),
296
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
297
+ color: "$info",
298
+ children: ["P2:", stats.p2]
299
+ }),
300
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
301
+ dim: true,
302
+ children: ["P3:", stats.p3]
303
+ }),
304
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
305
+ dim: true,
306
+ children: "|"
307
+ }),
308
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
309
+ color: "$muted",
310
+ children: [
311
+ STATUS_ICONS.todo,
312
+ " ",
313
+ stats.todo
314
+ ]
315
+ }),
316
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
317
+ color: "$warning",
318
+ children: [
319
+ STATUS_ICONS["in-progress"],
320
+ " ",
321
+ stats.inProg
322
+ ]
323
+ }),
324
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
325
+ color: "$success",
326
+ children: [
327
+ STATUS_ICONS.done,
328
+ " ",
329
+ stats.done
330
+ ]
331
+ }),
332
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
333
+ color: "$error",
334
+ children: [
335
+ STATUS_ICONS.blocked,
336
+ " ",
337
+ stats.blocked
338
+ ]
339
+ })
340
+ ]
341
+ });
342
+ }
343
+ function VirtualBenchmark() {
344
+ const { width, height } = useBoxRect();
345
+ const [cursor, setCursor] = useState(0);
346
+ const [showDetail, setShowDetail] = useState(false);
347
+ const listHeight = Math.max(5, height - 5);
348
+ const halfPage = Math.max(1, Math.floor(listHeight / 2));
349
+ const estimateHeight = useCallback((index) => {
350
+ if (showDetail && index === cursor) return 2;
351
+ return 1;
352
+ }, [showDetail, cursor]);
353
+ useInput$1(useCallback((input, key) => {
354
+ if (input === "q" || key.escape || key.ctrl && input === "c") return "exit";
355
+ if (input === "j" || key.downArrow) setCursor((c) => Math.min(TOTAL_ITEMS - 1, c + 1));
356
+ if (input === "k" || key.upArrow) setCursor((c) => Math.max(0, c - 1));
357
+ if (input === "d" || key.pageDown) setCursor((c) => Math.min(TOTAL_ITEMS - 1, c + halfPage));
358
+ if (input === "u" || key.pageUp) setCursor((c) => Math.max(0, c - halfPage));
359
+ if (input === "g" || key.home) setCursor(0);
360
+ if (input === "G" || key.end) setCursor(TOTAL_ITEMS - 1);
361
+ if (key.return || input === " ") setShowDetail((d) => !d);
362
+ }, [halfPage]));
363
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
364
+ flexDirection: "column",
365
+ width: "100%",
366
+ height: "100%",
367
+ children: [
368
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatsBar, { items: ALL_ITEMS }),
369
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
370
+ paddingX: 1,
371
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Divider, {})
372
+ }),
373
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
374
+ flexGrow: 1,
375
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ListView, {
376
+ items: ALL_ITEMS,
377
+ height: listHeight,
378
+ estimateHeight,
379
+ scrollTo: cursor,
380
+ overscan: 5,
381
+ renderItem: (item, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ItemRow, {
382
+ item,
383
+ isSelected: index === cursor,
384
+ showDetail: showDetail && index === cursor
385
+ }, item.id)
386
+ })
387
+ }),
388
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollIndicator, {
389
+ current: cursor,
390
+ total: TOTAL_ITEMS,
391
+ width
392
+ }),
393
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
394
+ paddingX: 1,
395
+ justifyContent: "center",
396
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Muted, { children: [
397
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children: "j/k" }),
398
+ " navigate ",
399
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children: "d/u" }),
400
+ " half-page ",
401
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children: "g/G" }),
402
+ " start/end ",
403
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children: "Enter" }),
404
+ " detail",
405
+ " ",
406
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Kbd, { children: "Esc/q" }),
407
+ " quit"
408
+ ] })
409
+ })
410
+ ]
411
+ });
412
+ }
413
+ async function main() {
414
+ await (await run(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ExampleBanner, {
415
+ meta,
416
+ controls: "j/k navigate d/u half-page g/G start/end Enter detail Esc/q quit",
417
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VirtualBenchmark, {})
418
+ }))).waitUntilExit();
419
+ }
420
+ if (import.meta.main) await main();
421
+ //#endregion
422
+ export { main, meta };