@silvery/examples 0.17.3 → 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} +15 -19
  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 +18 -13
  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 -80
  88. package/apps/search-filter.tsx +0 -240
  89. package/apps/selection.tsx +0 -346
  90. package/apps/spatial-focus-demo.tsx +0 -372
  91. package/apps/task-list.tsx +0 -271
  92. package/apps/terminal-caps-demo.tsx +0 -317
  93. package/apps/terminal.tsx +0 -784
  94. package/apps/text-selection-demo.tsx +0 -193
  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 -49
  101. package/components/hello.tsx +0 -38
  102. package/components/progress-bar.tsx +0 -52
  103. package/components/select-list.tsx +0 -54
  104. package/components/spinner.tsx +0 -44
  105. package/components/text-input.tsx +0 -61
  106. package/components/virtual-list.tsx +0 -56
  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
@@ -1,138 +0,0 @@
1
- /**
2
- * Inline incremental rendering benchmark
3
- *
4
- * Measures output phase bytes and timing for inline mode:
5
- * - Full render (bare outputPhase — always full render, no instance state)
6
- * - Incremental render (createOutputPhase — instance-scoped cursor tracking)
7
- *
8
- * Usage:
9
- * bun examples/apps/inline-bench.tsx
10
- */
11
-
12
- import { TerminalBuffer } from "@silvery/ag-term/buffer"
13
- import { createOutputPhase, outputPhase } from "@silvery/ag-term/pipeline/output-phase"
14
-
15
- const RUNS = 500
16
-
17
- function fillBuffer(buf: TerminalBuffer, rows: number, prefix = ""): void {
18
- for (let y = 0; y < rows; y++) {
19
- const text = `${prefix}Item ${y}: Content line with some styling and longer text here`
20
- for (let x = 0; x < Math.min(text.length, buf.width); x++) {
21
- buf.setCell(x, y, { char: text[x]! })
22
- }
23
- }
24
- }
25
-
26
- interface BenchResult {
27
- name: string
28
- timings: number[]
29
- bytes: number[]
30
- }
31
-
32
- function benchmarkOutputPhase(
33
- name: string,
34
- width: number,
35
- height: number,
36
- contentRows: number,
37
- changedCells: number,
38
- forceFullRender: boolean,
39
- ): BenchResult {
40
- const timings: number[] = []
41
- const bytes: number[] = []
42
-
43
- for (let i = 0; i < RUNS; i++) {
44
- // Create fresh buffers each iteration
45
- const prev = new TerminalBuffer(width, height)
46
- fillBuffer(prev, contentRows)
47
-
48
- const next = new TerminalBuffer(width, height)
49
- fillBuffer(next, contentRows)
50
-
51
- // Apply changes
52
- for (let c = 0; c < changedCells; c++) {
53
- const row = Math.floor((c / Math.max(changedCells, 1)) * contentRows)
54
- const col = c % Math.min(10, width)
55
- next.setCell(col, row, { char: "X" })
56
- }
57
-
58
- if (forceFullRender) {
59
- // Bare outputPhase always uses fresh state → full render
60
- outputPhase(null, prev, "inline", 0, height)
61
- const t0 = performance.now()
62
- const output = outputPhase(prev, next, "inline", 0, height)
63
- const t1 = performance.now()
64
- timings.push(t1 - t0)
65
- bytes.push(output.length)
66
- } else {
67
- // createOutputPhase captures instance state → incremental after first render
68
- const render = createOutputPhase({})
69
- render(null, prev, "inline", 0, height) // first render (inits tracking)
70
- const t0 = performance.now()
71
- const output = render(prev, next, "inline", 0, height) // incremental
72
- const t1 = performance.now()
73
- timings.push(t1 - t0)
74
- bytes.push(output.length)
75
- }
76
- }
77
-
78
- timings.sort((a, b) => a - b)
79
- bytes.sort((a, b) => a - b)
80
-
81
- return { name, timings, bytes }
82
- }
83
-
84
- function printResult(r: BenchResult): void {
85
- const p50t = r.timings[Math.floor(r.timings.length * 0.5)]!
86
- const p95t = r.timings[Math.floor(r.timings.length * 0.95)]!
87
- const p50b = r.bytes[Math.floor(r.bytes.length * 0.5)]!
88
- const avgB = r.bytes.reduce((a, b) => a + b, 0) / r.bytes.length
89
-
90
- console.log(
91
- ` ${r.name.padEnd(40)} ` +
92
- `p50=${p50t.toFixed(3).padStart(7)}ms ` +
93
- `p95=${p95t.toFixed(3).padStart(7)}ms ` +
94
- `bytes=${Math.round(p50b).toString().padStart(6)} (avg ${Math.round(avgB)})`,
95
- )
96
- }
97
-
98
- function printComparison(full: BenchResult, incr: BenchResult): void {
99
- const fullP50b = full.bytes[Math.floor(full.bytes.length * 0.5)]!
100
- const incrP50b = incr.bytes[Math.floor(incr.bytes.length * 0.5)]!
101
- const fullP50t = full.timings[Math.floor(full.timings.length * 0.5)]!
102
- const incrP50t = incr.timings[Math.floor(incr.timings.length * 0.5)]!
103
-
104
- const byteRatio = fullP50b / Math.max(incrP50b, 1)
105
- const timeRatio = fullP50t / Math.max(incrP50t, 0.001)
106
-
107
- console.log(
108
- ` ${"→ savings".padEnd(40)} ` +
109
- `time=${timeRatio.toFixed(1)}x faster ` +
110
- `bytes=${byteRatio.toFixed(0)}x fewer (${fullP50b} → ${incrP50b})`,
111
- )
112
- }
113
-
114
- export async function main() {
115
- console.log(`\n═══ Inline Output Phase: Full vs Incremental (${RUNS} runs) ═══\n`)
116
-
117
- const configs = [
118
- { label: "10 rows, 1 change", w: 80, h: 20, rows: 10, changes: 1 },
119
- { label: "30 rows, 1 change", w: 120, h: 40, rows: 30, changes: 1 },
120
- { label: "50 rows, 1 change", w: 120, h: 60, rows: 50, changes: 1 },
121
- { label: "50 rows, 3 changes", w: 120, h: 60, rows: 50, changes: 3 },
122
- { label: "50 rows, 10 changes", w: 120, h: 60, rows: 50, changes: 10 },
123
- ]
124
-
125
- for (const cfg of configs) {
126
- console.log(`--- ${cfg.label} ---`)
127
- const full = benchmarkOutputPhase(`full render`, cfg.w, cfg.h, cfg.rows, cfg.changes, true)
128
- const incr = benchmarkOutputPhase(`incremental`, cfg.w, cfg.h, cfg.rows, cfg.changes, false)
129
- printResult(full)
130
- printResult(incr)
131
- printComparison(full, incr)
132
- console.log()
133
- }
134
- }
135
-
136
- if (import.meta.main) {
137
- await main()
138
- }
package/apps/kanban.tsx DELETED
@@ -1,265 +0,0 @@
1
- /**
2
- * Kanban Board Example
3
- *
4
- * A 3-column kanban board demonstrating:
5
- * - Todo, In Progress, Done columns
6
- * - Move items between columns with arrow keys
7
- * - Each column uses native overflow="scroll" for scrolling
8
- * - Flexbox layout for proportional sizing
9
- */
10
-
11
- import React, { useState } from "react"
12
- import { render, Box, Text, useInput, useApp, createTerm, type Key } from "silvery"
13
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
14
-
15
- export const meta: ExampleMeta = {
16
- name: "Kanban Board",
17
- description: "3-column kanban with card movement and independent scroll",
18
- demo: true,
19
- features: ["Box flexDirection", "useInput", "backgroundColor", "multi-column layout"],
20
- }
21
-
22
- // ============================================================================
23
- // Types
24
- // ============================================================================
25
-
26
- type ColumnId = "todo" | "inProgress" | "done"
27
-
28
- interface Card {
29
- id: number
30
- title: string
31
- tags: string[]
32
- }
33
-
34
- interface Column {
35
- id: ColumnId
36
- title: string
37
- cards: Card[]
38
- }
39
-
40
- // ============================================================================
41
- // Initial Data
42
- // ============================================================================
43
-
44
- const initialColumns: Column[] = [
45
- {
46
- id: "todo",
47
- title: "To Do",
48
- cards: [
49
- { id: 1, title: "Design new landing page", tags: ["design"] },
50
- { id: 2, title: "Write API documentation", tags: ["docs"] },
51
- { id: 3, title: "Set up monitoring", tags: ["devops"] },
52
- { id: 4, title: "Create onboarding flow", tags: ["ux"] },
53
- { id: 5, title: "Database optimization", tags: ["backend"] },
54
- { id: 6, title: "Mobile responsive fixes", tags: ["frontend"] },
55
- { id: 7, title: "Add dark mode", tags: ["frontend", "ux"] },
56
- { id: 8, title: "Implement caching", tags: ["backend"] },
57
- ],
58
- },
59
- {
60
- id: "inProgress",
61
- title: "In Progress",
62
- cards: [
63
- { id: 9, title: "User authentication", tags: ["backend", "security"] },
64
- { id: 10, title: "Dashboard redesign", tags: ["frontend", "design"] },
65
- { id: 11, title: "API rate limiting", tags: ["backend"] },
66
- ],
67
- },
68
- {
69
- id: "done",
70
- title: "Done",
71
- cards: [
72
- { id: 12, title: "Project setup", tags: ["devops"] },
73
- { id: 13, title: "CI/CD pipeline", tags: ["devops"] },
74
- { id: 14, title: "Initial wireframes", tags: ["design"] },
75
- { id: 15, title: "Database schema", tags: ["backend"] },
76
- ],
77
- },
78
- ]
79
-
80
- // ============================================================================
81
- // Components
82
- // ============================================================================
83
-
84
- const tagColors: Record<string, string> = {
85
- frontend: "$info",
86
- backend: "$accent",
87
- design: "$warning",
88
- devops: "$success",
89
- docs: "$primary",
90
- ux: "$muted",
91
- security: "$error",
92
- }
93
-
94
- function Tag({ name }: { name: string }) {
95
- const color = tagColors[name] ?? "$muted"
96
- return (
97
- <Text color={color} dim>
98
- #{name}
99
- </Text>
100
- )
101
- }
102
-
103
- function CardComponent({ card, isSelected }: { card: Card; isSelected: boolean }) {
104
- return (
105
- <Box flexDirection="column" borderStyle="round" borderColor={isSelected ? "$primary" : "$border"}>
106
- {isSelected ? (
107
- <Box backgroundColor="$primary" paddingX={1}>
108
- <Text color="$primary-fg" bold wrap="truncate">
109
- {card.title}
110
- </Text>
111
- </Box>
112
- ) : (
113
- <Box paddingX={1}>
114
- <Text wrap="truncate">{card.title}</Text>
115
- </Box>
116
- )}
117
- <Box gap={1} paddingX={1}>
118
- {card.tags.map((tag) => (
119
- <Tag key={tag} name={tag} />
120
- ))}
121
- </Box>
122
- </Box>
123
- )
124
- }
125
-
126
- function ColumnComponent({
127
- column,
128
- isSelected,
129
- selectedCardIndex,
130
- }: {
131
- column: Column
132
- isSelected: boolean
133
- selectedCardIndex: number
134
- }) {
135
- return (
136
- <Box
137
- flexDirection="column"
138
- flexGrow={1}
139
- flexBasis={0}
140
- borderStyle="single"
141
- borderColor={isSelected ? "$primary" : "$border"}
142
- >
143
- <Box backgroundColor={isSelected ? "$primary" : undefined} paddingX={1}>
144
- <Text bold color={isSelected ? "$primary-fg" : "$text"}>
145
- {column.title}
146
- </Text>
147
- <Text color={isSelected ? "$primary-fg" : "$muted"}> ({column.cards.length})</Text>
148
- </Box>
149
-
150
- <Box
151
- flexDirection="column"
152
- paddingX={1}
153
- overflow="scroll"
154
- scrollTo={isSelected ? selectedCardIndex : undefined}
155
- flexGrow={1}
156
- >
157
- {column.cards.map((card, cardIndex) => (
158
- <CardComponent key={card.id} card={card} isSelected={isSelected && cardIndex === selectedCardIndex} />
159
- ))}
160
-
161
- {column.cards.length === 0 && (
162
- <Text dim italic>
163
- No cards
164
- </Text>
165
- )}
166
- </Box>
167
- </Box>
168
- )
169
- }
170
-
171
- export function KanbanBoard() {
172
- const { exit } = useApp()
173
- const [columns, setColumns] = useState<Column[]>(initialColumns)
174
- const [selectedColumn, setSelectedColumn] = useState(0)
175
- const [selectedCard, setSelectedCard] = useState(0)
176
-
177
- const currentColumn = columns[selectedColumn]
178
- const currentColumnCards = currentColumn?.cards ?? []
179
- const boundedSelectedCard = Math.min(selectedCard, Math.max(0, currentColumnCards.length - 1))
180
-
181
- useInput((input: string, key: Key) => {
182
- if (input === "q" || key.escape) {
183
- exit()
184
- }
185
-
186
- // Column navigation
187
- if (key.leftArrow || input === "h") {
188
- setSelectedColumn((prev) => Math.max(0, prev - 1))
189
- setSelectedCard(0)
190
- }
191
- if (key.rightArrow || input === "l") {
192
- setSelectedColumn((prev) => Math.min(columns.length - 1, prev + 1))
193
- setSelectedCard(0)
194
- }
195
-
196
- // Card navigation
197
- if (key.upArrow || input === "k") {
198
- setSelectedCard((prev) => Math.max(0, prev - 1))
199
- }
200
- if (key.downArrow || input === "j") {
201
- setSelectedCard((prev) => Math.min(currentColumnCards.length - 1, prev + 1))
202
- }
203
-
204
- // Move card between columns
205
- if (input === "<" || input === ",") {
206
- moveCard(-1)
207
- }
208
- if (input === ">" || input === ".") {
209
- moveCard(1)
210
- }
211
- })
212
-
213
- function moveCard(direction: number): void {
214
- const targetColumnIndex = selectedColumn + direction
215
- if (targetColumnIndex < 0 || targetColumnIndex >= columns.length) return
216
- if (currentColumnCards.length === 0) return
217
-
218
- const cardToMove = currentColumnCards[boundedSelectedCard]
219
- if (!cardToMove) return
220
-
221
- setColumns((prev) => {
222
- const next = prev.map((col) => ({ ...col, cards: [...col.cards] }))
223
- next[selectedColumn]!.cards.splice(boundedSelectedCard, 1)
224
- next[targetColumnIndex]!.cards.push(cardToMove)
225
- return next
226
- })
227
-
228
- setSelectedColumn(targetColumnIndex)
229
- setSelectedCard(columns[targetColumnIndex]!.cards.length)
230
- }
231
-
232
- return (
233
- <Box flexDirection="column" padding={1} height="100%">
234
- <Box flexGrow={1} flexDirection="row" gap={1} overflow="hidden">
235
- {columns.map((column, colIndex) => (
236
- <ColumnComponent
237
- key={column.id}
238
- column={column}
239
- isSelected={colIndex === selectedColumn}
240
- selectedCardIndex={colIndex === selectedColumn ? boundedSelectedCard : -1}
241
- />
242
- ))}
243
- </Box>
244
- </Box>
245
- )
246
- }
247
-
248
- // ============================================================================
249
- // Main
250
- // ============================================================================
251
-
252
- export async function main() {
253
- using term = createTerm()
254
- const { waitUntilExit } = await render(
255
- <ExampleBanner meta={meta} controls="h/l column j/k card </> move Esc/q quit">
256
- <KanbanBoard />
257
- </ExampleBanner>,
258
- term,
259
- )
260
- await waitUntilExit()
261
- }
262
-
263
- if (import.meta.main) {
264
- await main()
265
- }
@@ -1,173 +0,0 @@
1
- /**
2
- * Layout Ref Example
3
- *
4
- * Demonstrates imperative access to layout information:
5
- * - forwardRef on Box and Text components
6
- * - BoxHandle for accessing layout info imperatively
7
- * - onLayout callback for responding to size changes
8
- */
9
-
10
- import React, { useRef, useState, useEffect } from "react"
11
- import { render, Box, Text, H1, Kbd, Muted, useInput, useApp, createTerm, type BoxHandle, type Key } from "silvery"
12
- import { ExampleBanner, type ExampleMeta } from "../_banner.js"
13
-
14
- export const meta: ExampleMeta = {
15
- name: "Layout Ref",
16
- description: "useBoxRect + useScrollRect for imperative layout measurement",
17
- features: ["forwardRef", "BoxHandle", "onLayout", "getBoxRect()"],
18
- }
19
-
20
- // ============================================================================
21
- // Components
22
- // ============================================================================
23
-
24
- interface LayoutInfo {
25
- x: number
26
- y: number
27
- width: number
28
- height: number
29
- }
30
-
31
- function ResizablePane({
32
- title,
33
- color,
34
- onLayoutChange,
35
- }: {
36
- title: string
37
- color: string
38
- onLayoutChange: (info: LayoutInfo) => void
39
- }) {
40
- const boxRef = useRef<BoxHandle>(null)
41
-
42
- // onLayout callback fires when this Box's dimensions change
43
- return (
44
- <Box
45
- ref={boxRef}
46
- flexGrow={1}
47
- borderStyle="round"
48
- borderColor={color}
49
- padding={1}
50
- onLayout={(layout) => onLayoutChange(layout)}
51
- >
52
- <H1 color={color}>{title}</H1>
53
- </Box>
54
- )
55
- }
56
-
57
- function ImperativeAccessDemo() {
58
- const boxRef = useRef<BoxHandle>(null)
59
- const [info, setInfo] = useState<string>("Click 'i' to inspect")
60
-
61
- const inspect = () => {
62
- if (!boxRef.current) {
63
- setInfo("No ref attached")
64
- return
65
- }
66
-
67
- const content = boxRef.current.getBoxRect()
68
- const screen = boxRef.current.getScrollRect()
69
- const node = boxRef.current.getNode()
70
-
71
- setInfo(
72
- `Content: ${content?.width}x${content?.height} at (${content?.x},${content?.y})\n` +
73
- `Screen: ${screen?.width}x${screen?.height} at (${screen?.x},${screen?.y})\n` +
74
- `Node: ${node ? "available" : "null"}`,
75
- )
76
- }
77
-
78
- return (
79
- <Box ref={boxRef} flexDirection="column" borderStyle="double" borderColor="$accent" padding={1}>
80
- <H1 color="$accent">Imperative Access (BoxHandle)</H1>
81
- <Muted>Press 'i' to inspect this box</Muted>
82
- <Box marginTop={1}>
83
- <Text>{info}</Text>
84
- </Box>
85
- {/* Expose inspect function via closure */}
86
- <InspectTrigger onInspect={inspect} />
87
- </Box>
88
- )
89
- }
90
-
91
- // Hidden component to trigger inspect on keypress
92
- function InspectTrigger({ onInspect }: { onInspect: () => void }) {
93
- useInput((input: string) => {
94
- if (input === "i") {
95
- onInspect()
96
- }
97
- })
98
- return null
99
- }
100
-
101
- export function LayoutRefApp() {
102
- const { exit } = useApp()
103
- const [layouts, setLayouts] = useState<Record<string, LayoutInfo>>({})
104
-
105
- useInput((input: string, key: Key) => {
106
- if (input === "q" || key.escape) {
107
- exit()
108
- }
109
- })
110
-
111
- const handleLayoutChange = (pane: string) => (info: LayoutInfo) => {
112
- setLayouts((prev) => ({ ...prev, [pane]: info }))
113
- }
114
-
115
- return (
116
- <Box flexDirection="column" padding={1}>
117
- {/* Row of resizable panes with onLayout callbacks */}
118
- <Box flexDirection="row" gap={1} height={8}>
119
- <ResizablePane title="Pane A" color="$success" onLayoutChange={handleLayoutChange("a")} />
120
- <ResizablePane title="Pane B" color="$info" onLayoutChange={handleLayoutChange("b")} />
121
- <ResizablePane title="Pane C" color="$primary" onLayoutChange={handleLayoutChange("c")} />
122
- </Box>
123
-
124
- {/* Show layout info from onLayout callbacks */}
125
- <Box marginTop={1} borderStyle="single" borderColor="$border" padding={1}>
126
- <Box flexDirection="column">
127
- <Text bold dim>
128
- onLayout Results:
129
- </Text>
130
- {Object.entries(layouts).map(([pane, info]) => (
131
- <Text key={pane} dim>
132
- Pane {pane.toUpperCase()}: {info.width}x{info.height} at ({info.x},{info.y})
133
- </Text>
134
- ))}
135
- {Object.keys(layouts).length === 0 && (
136
- <Text dim italic>
137
- Waiting for layout...
138
- </Text>
139
- )}
140
- </Box>
141
- </Box>
142
-
143
- {/* Imperative access demo */}
144
- <Box flexGrow={1} marginTop={1}>
145
- <ImperativeAccessDemo />
146
- </Box>
147
-
148
- <Muted>
149
- {" "}
150
- <Kbd>i</Kbd> inspect <Kbd>Esc/q</Kbd> quit
151
- </Muted>
152
- </Box>
153
- )
154
- }
155
-
156
- // ============================================================================
157
- // Main
158
- // ============================================================================
159
-
160
- export async function main() {
161
- using term = createTerm()
162
- const { waitUntilExit } = await render(
163
- <ExampleBanner meta={meta} controls="i inspect Esc/q quit">
164
- <LayoutRefApp />
165
- </ExampleBanner>,
166
- term,
167
- )
168
- await waitUntilExit()
169
- }
170
-
171
- if (import.meta.main) {
172
- await main()
173
- }