@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.
- package/dist/UPNG-Cy7ViL8f.mjs +5074 -0
- package/dist/__vite-browser-external-2447137e-BML7CYau.mjs +4 -0
- package/dist/_banner-DLPxCqVy.mjs +44 -0
- package/dist/ansi-CCE2pVS0.mjs +16397 -0
- package/dist/apng-HhhBjRGt.mjs +68 -0
- package/dist/apng-mwUQbTTF.mjs +3 -0
- package/dist/apps/aichat/index.mjs +1299 -0
- package/dist/apps/app-todo.mjs +139 -0
- package/dist/apps/async-data.mjs +204 -0
- package/dist/apps/cli-wizard.mjs +339 -0
- package/dist/apps/clipboard.mjs +198 -0
- package/dist/apps/components.mjs +864 -0
- package/dist/apps/data-explorer.mjs +483 -0
- package/dist/apps/dev-tools.mjs +397 -0
- package/dist/apps/explorer.mjs +698 -0
- package/dist/apps/gallery.mjs +766 -0
- package/dist/apps/inline-bench.mjs +115 -0
- package/dist/apps/kanban.mjs +280 -0
- package/dist/apps/layout-ref.mjs +187 -0
- package/dist/apps/outline.mjs +203 -0
- package/dist/apps/paste-demo.mjs +189 -0
- package/dist/apps/scroll.mjs +86 -0
- package/dist/apps/search-filter.mjs +287 -0
- package/dist/apps/selection.mjs +355 -0
- package/dist/apps/spatial-focus-demo.mjs +388 -0
- package/dist/apps/task-list.mjs +258 -0
- package/dist/apps/terminal-caps-demo.mjs +315 -0
- package/dist/apps/terminal.mjs +872 -0
- package/dist/apps/text-selection-demo.mjs +254 -0
- package/dist/apps/textarea.mjs +178 -0
- package/dist/apps/theme.mjs +661 -0
- package/dist/apps/transform.mjs +215 -0
- package/dist/apps/virtual-10k.mjs +422 -0
- package/dist/assets/resvgjs.darwin-arm64-BtufyGW1.node +0 -0
- package/dist/backends-Bahh9mKN.mjs +1179 -0
- package/dist/backends-CCtCDQ94.mjs +3 -0
- package/dist/{cli.mjs → bin/cli.mjs} +15 -19
- package/dist/chunk-BSw8zbkd.mjs +37 -0
- package/dist/components/counter.mjs +48 -0
- package/dist/components/hello.mjs +31 -0
- package/dist/components/progress-bar.mjs +59 -0
- package/dist/components/select-list.mjs +85 -0
- package/dist/components/spinner.mjs +57 -0
- package/dist/components/text-input.mjs +62 -0
- package/dist/components/virtual-list.mjs +51 -0
- package/dist/flexily-zero-adapter-UB-ra8fR.mjs +3374 -0
- package/dist/gif-BZaqPPVX.mjs +3 -0
- package/dist/gif-BtnXuxLF.mjs +71 -0
- package/dist/gifenc-CLRW41dk.mjs +728 -0
- package/dist/jsx-runtime-dMs_8fNu.mjs +241 -0
- package/dist/key-mapping-5oYQdAQE.mjs +3 -0
- package/dist/key-mapping-D4LR1go6.mjs +130 -0
- package/dist/layout/dashboard.mjs +1204 -0
- package/dist/layout/live-resize.mjs +303 -0
- package/dist/layout/overflow.mjs +70 -0
- package/dist/layout/text-layout.mjs +335 -0
- package/dist/node-NuJ94BWl.mjs +1083 -0
- package/dist/plugins-D1KtkT4a.mjs +3057 -0
- package/dist/resvg-js-C_8Wps1F.mjs +201 -0
- package/dist/src-BTEVGpd9.mjs +23538 -0
- package/dist/src-CUUOuRH6.mjs +5322 -0
- package/dist/src-CzfRafCQ.mjs +814 -0
- package/dist/usingCtx-CsEf0xO3.mjs +57 -0
- package/dist/yoga-adapter-BVtQ5OJR.mjs +237 -0
- package/package.json +18 -13
- package/_banner.tsx +0 -60
- package/apps/aichat/components.tsx +0 -469
- package/apps/aichat/index.tsx +0 -220
- package/apps/aichat/script.ts +0 -460
- package/apps/aichat/state.ts +0 -325
- package/apps/aichat/types.ts +0 -19
- package/apps/app-todo.tsx +0 -201
- package/apps/async-data.tsx +0 -196
- package/apps/cli-wizard.tsx +0 -332
- package/apps/clipboard.tsx +0 -183
- package/apps/components.tsx +0 -658
- package/apps/data-explorer.tsx +0 -490
- package/apps/dev-tools.tsx +0 -395
- package/apps/explorer.tsx +0 -731
- package/apps/gallery.tsx +0 -653
- package/apps/inline-bench.tsx +0 -138
- package/apps/kanban.tsx +0 -265
- package/apps/layout-ref.tsx +0 -173
- package/apps/outline.tsx +0 -160
- package/apps/panes/index.tsx +0 -203
- package/apps/paste-demo.tsx +0 -185
- package/apps/scroll.tsx +0 -80
- package/apps/search-filter.tsx +0 -240
- package/apps/selection.tsx +0 -346
- package/apps/spatial-focus-demo.tsx +0 -372
- package/apps/task-list.tsx +0 -271
- package/apps/terminal-caps-demo.tsx +0 -317
- package/apps/terminal.tsx +0 -784
- package/apps/text-selection-demo.tsx +0 -193
- package/apps/textarea.tsx +0 -155
- package/apps/theme.tsx +0 -515
- package/apps/transform.tsx +0 -229
- package/apps/virtual-10k.tsx +0 -405
- package/apps/vterm-demo/index.tsx +0 -216
- package/components/counter.tsx +0 -49
- package/components/hello.tsx +0 -38
- package/components/progress-bar.tsx +0 -52
- package/components/select-list.tsx +0 -54
- package/components/spinner.tsx +0 -44
- package/components/text-input.tsx +0 -61
- package/components/virtual-list.tsx +0 -56
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs.map +0 -1
- package/layout/dashboard.tsx +0 -953
- package/layout/live-resize.tsx +0 -282
- package/layout/overflow.tsx +0 -51
- package/layout/text-layout.tsx +0 -283
package/apps/outline.tsx
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Outline vs Border Comparison
|
|
3
|
-
*
|
|
4
|
-
* Side-by-side demonstration of outlineStyle vs borderStyle.
|
|
5
|
-
* Borders push content inward (adding to layout dimensions), while
|
|
6
|
-
* outlines overlap the content edge without affecting layout.
|
|
7
|
-
*
|
|
8
|
-
* Features:
|
|
9
|
-
* - Left panel: Box with borderStyle — content area is smaller
|
|
10
|
-
* - Right panel: Box with outlineStyle — content starts at edge
|
|
11
|
-
* - Toggle between styles with Tab
|
|
12
|
-
* - Live content dimensions via useBoxRect()
|
|
13
|
-
*
|
|
14
|
-
* Run: bun vendor/silvery/examples/apps/outline.tsx
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import React, { useState } from "react"
|
|
18
|
-
import { render, Box, Text, Kbd, Muted, useInput, useApp, useBoxRect, createTerm, type Key } from "silvery"
|
|
19
|
-
import { ExampleBanner, type ExampleMeta } from "../_banner.js"
|
|
20
|
-
|
|
21
|
-
export const meta: ExampleMeta = {
|
|
22
|
-
name: "Outline vs Border",
|
|
23
|
-
description: "Side-by-side comparison showing outline (no layout impact) vs border",
|
|
24
|
-
features: ["outlineStyle", "borderStyle", "useBoxRect()", "layout dimensions"],
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// ============================================================================
|
|
28
|
-
// Types
|
|
29
|
-
// ============================================================================
|
|
30
|
-
|
|
31
|
-
type StyleVariant = "single" | "double" | "round" | "bold"
|
|
32
|
-
|
|
33
|
-
const STYLES: StyleVariant[] = ["single", "double", "round", "bold"]
|
|
34
|
-
|
|
35
|
-
// ============================================================================
|
|
36
|
-
// Components
|
|
37
|
-
// ============================================================================
|
|
38
|
-
|
|
39
|
-
function ContentWithSize({ label }: { label: string }) {
|
|
40
|
-
const { width, height } = useBoxRect()
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<Box flexDirection="column">
|
|
44
|
-
<Text bold>{label}</Text>
|
|
45
|
-
<Text>
|
|
46
|
-
Content area:{" "}
|
|
47
|
-
<Text color="$success" bold>
|
|
48
|
-
{width}
|
|
49
|
-
</Text>
|
|
50
|
-
x
|
|
51
|
-
<Text color="$success" bold>
|
|
52
|
-
{height}
|
|
53
|
-
</Text>
|
|
54
|
-
</Text>
|
|
55
|
-
<Text dim>The quick brown fox</Text>
|
|
56
|
-
<Text dim>jumps over the lazy</Text>
|
|
57
|
-
<Text dim>dog on a sunny day.</Text>
|
|
58
|
-
</Box>
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function BorderPanel({ style, highlight }: { style: StyleVariant; highlight: boolean }) {
|
|
63
|
-
return (
|
|
64
|
-
<Box flexDirection="column" flexGrow={1} gap={1}>
|
|
65
|
-
<Text bold color={highlight ? "$primary" : undefined}>
|
|
66
|
-
borderStyle="{style}"
|
|
67
|
-
</Text>
|
|
68
|
-
<Box borderStyle={style} borderColor={highlight ? "$primary" : "$border"} width={30} height={9}>
|
|
69
|
-
<ContentWithSize label="Border Box" />
|
|
70
|
-
</Box>
|
|
71
|
-
<Muted>Border adds to layout.</Muted>
|
|
72
|
-
<Muted>Content is pushed inward.</Muted>
|
|
73
|
-
</Box>
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function OutlinePanel({ style, highlight }: { style: StyleVariant; highlight: boolean }) {
|
|
78
|
-
return (
|
|
79
|
-
<Box flexDirection="column" flexGrow={1} gap={1}>
|
|
80
|
-
<Text bold color={highlight ? "$warning" : undefined}>
|
|
81
|
-
outlineStyle="{style}"
|
|
82
|
-
</Text>
|
|
83
|
-
<Box outlineStyle={style} outlineColor={highlight ? "$warning" : "$border"} width={30} height={9}>
|
|
84
|
-
<ContentWithSize label="Outline Box" />
|
|
85
|
-
</Box>
|
|
86
|
-
<Muted>Outline overlaps content.</Muted>
|
|
87
|
-
<Muted>No layout impact at all.</Muted>
|
|
88
|
-
</Box>
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function OutlineDemo() {
|
|
93
|
-
const { exit } = useApp()
|
|
94
|
-
const [styleIndex, setStyleIndex] = useState(0)
|
|
95
|
-
const [focusedSide, setFocusedSide] = useState<"border" | "outline">("border")
|
|
96
|
-
|
|
97
|
-
const currentStyle = STYLES[styleIndex]!
|
|
98
|
-
|
|
99
|
-
useInput((input: string, key: Key) => {
|
|
100
|
-
if (input === "q" || key.escape) {
|
|
101
|
-
exit()
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Toggle focus between panels
|
|
106
|
-
if (key.tab || input === "\t") {
|
|
107
|
-
setFocusedSide((prev) => (prev === "border" ? "outline" : "border"))
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Cycle through border/outline styles
|
|
111
|
-
if (key.rightArrow || input === "l") {
|
|
112
|
-
setStyleIndex((prev) => (prev + 1) % STYLES.length)
|
|
113
|
-
}
|
|
114
|
-
if (key.leftArrow || input === "h") {
|
|
115
|
-
setStyleIndex((prev) => (prev - 1 + STYLES.length) % STYLES.length)
|
|
116
|
-
}
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
return (
|
|
120
|
-
<Box flexDirection="column" padding={1} gap={1}>
|
|
121
|
-
<Box gap={1}>
|
|
122
|
-
<Text bold>Style:</Text>
|
|
123
|
-
{STYLES.map((s, i) => (
|
|
124
|
-
<Text key={s} color={i === styleIndex ? "$primary" : "$muted"} bold={i === styleIndex}>
|
|
125
|
-
{i === styleIndex ? `[${s}]` : s}
|
|
126
|
-
</Text>
|
|
127
|
-
))}
|
|
128
|
-
</Box>
|
|
129
|
-
|
|
130
|
-
<Box flexDirection="row" gap={2}>
|
|
131
|
-
<BorderPanel style={currentStyle} highlight={focusedSide === "border"} />
|
|
132
|
-
<OutlinePanel style={currentStyle} highlight={focusedSide === "outline"} />
|
|
133
|
-
</Box>
|
|
134
|
-
|
|
135
|
-
<Muted>
|
|
136
|
-
{" "}
|
|
137
|
-
<Kbd>Tab</Kbd> toggle focus <Kbd>h/l</Kbd> change style <Kbd>Esc/q</Kbd> quit
|
|
138
|
-
</Muted>
|
|
139
|
-
</Box>
|
|
140
|
-
)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ============================================================================
|
|
144
|
-
// Main
|
|
145
|
-
// ============================================================================
|
|
146
|
-
|
|
147
|
-
export async function main() {
|
|
148
|
-
using term = createTerm()
|
|
149
|
-
const { waitUntilExit } = await render(
|
|
150
|
-
<ExampleBanner meta={meta} controls="Tab toggle h/l style Esc/q quit">
|
|
151
|
-
<OutlineDemo />
|
|
152
|
-
</ExampleBanner>,
|
|
153
|
-
term,
|
|
154
|
-
)
|
|
155
|
-
await waitUntilExit()
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (import.meta.main) {
|
|
159
|
-
await main()
|
|
160
|
-
}
|
package/apps/panes/index.tsx
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Panes — tmux-style split pane demo.
|
|
3
|
-
*
|
|
4
|
-
* Two AI chat panes running independently, Tab to switch focus, Esc to quit.
|
|
5
|
-
* SearchProvider with Ctrl+F bindings for app-global search.
|
|
6
|
-
*
|
|
7
|
-
* Run: bun examples/apps/panes/index.tsx [--fast]
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import React, { useState, useEffect, useMemo } from "react"
|
|
11
|
-
import { Box, Text, ListView } from "silvery"
|
|
12
|
-
import { SearchProvider, SearchBar, useSearch } from "@silvery/ag-react"
|
|
13
|
-
import { run, useInput, type Key } from "silvery/runtime"
|
|
14
|
-
import type { ExampleMeta } from "../../_banner.js"
|
|
15
|
-
import { SCRIPT } from "../aichat/script.js"
|
|
16
|
-
import type { ScriptEntry } from "../aichat/types.js"
|
|
17
|
-
import type { Exchange } from "../aichat/types.js"
|
|
18
|
-
import { ExchangeItem } from "../aichat/components.js"
|
|
19
|
-
// ListItemMeta type from ListView — inline to avoid tsconfig path issues
|
|
20
|
-
interface ListItemMeta {
|
|
21
|
-
isCursor: boolean
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const meta: ExampleMeta = {
|
|
25
|
-
name: "Panes",
|
|
26
|
-
description: "tmux-style split panes — ListView + SearchProvider + focus switching",
|
|
27
|
-
demo: true,
|
|
28
|
-
features: ["ListView", "SearchProvider", "split panes", "Tab focus"],
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ============================================================================
|
|
32
|
-
// Auto-advancing chat content
|
|
33
|
-
// ============================================================================
|
|
34
|
-
|
|
35
|
-
function usePaneContent(script: ScriptEntry[], fastMode: boolean): Exchange[] {
|
|
36
|
-
const [exchanges, setExchanges] = useState<Exchange[]>([])
|
|
37
|
-
const [idx, setIdx] = useState(0)
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
if (idx >= script.length) return
|
|
41
|
-
const delay = fastMode ? 150 : 800 + Math.random() * 1200
|
|
42
|
-
const timer = setTimeout(() => {
|
|
43
|
-
const entry = script[idx]!
|
|
44
|
-
setExchanges((prev) => [...prev, { ...entry, id: idx }])
|
|
45
|
-
setIdx((i) => i + 1)
|
|
46
|
-
}, delay)
|
|
47
|
-
return () => clearTimeout(timer)
|
|
48
|
-
}, [idx, script, fastMode])
|
|
49
|
-
|
|
50
|
-
return exchanges
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ============================================================================
|
|
54
|
-
// Chat pane
|
|
55
|
-
// ============================================================================
|
|
56
|
-
|
|
57
|
-
function ChatPane({
|
|
58
|
-
script,
|
|
59
|
-
fastMode,
|
|
60
|
-
height,
|
|
61
|
-
active,
|
|
62
|
-
surfaceId,
|
|
63
|
-
}: {
|
|
64
|
-
script: ScriptEntry[]
|
|
65
|
-
fastMode: boolean
|
|
66
|
-
height: number
|
|
67
|
-
active: boolean
|
|
68
|
-
surfaceId: string
|
|
69
|
-
}) {
|
|
70
|
-
const exchanges = usePaneContent(script, fastMode)
|
|
71
|
-
|
|
72
|
-
if (exchanges.length === 0) {
|
|
73
|
-
return (
|
|
74
|
-
<Box paddingX={1}>
|
|
75
|
-
<Text color="$muted">Waiting...</Text>
|
|
76
|
-
</Box>
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<ListView
|
|
82
|
-
items={exchanges}
|
|
83
|
-
height={height}
|
|
84
|
-
getKey={(ex: Exchange) => ex.id}
|
|
85
|
-
scrollTo={exchanges.length - 1}
|
|
86
|
-
active={active}
|
|
87
|
-
surfaceId={surfaceId}
|
|
88
|
-
cache={{
|
|
89
|
-
mode: "virtual",
|
|
90
|
-
isCacheable: (_ex: Exchange, idx: number) => idx < exchanges.length - 1,
|
|
91
|
-
}}
|
|
92
|
-
search={{ getText: (ex: Exchange) => ex.content }}
|
|
93
|
-
renderItem={(exchange: Exchange, _index: number, _meta: ListItemMeta) => (
|
|
94
|
-
<ExchangeItem
|
|
95
|
-
exchange={exchange}
|
|
96
|
-
streamPhase="done"
|
|
97
|
-
revealFraction={1}
|
|
98
|
-
pulse={false}
|
|
99
|
-
isLatest={false}
|
|
100
|
-
isFirstInGroup={true}
|
|
101
|
-
isLastInGroup={true}
|
|
102
|
-
/>
|
|
103
|
-
)}
|
|
104
|
-
/>
|
|
105
|
-
)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ============================================================================
|
|
109
|
-
// Main app
|
|
110
|
-
// ============================================================================
|
|
111
|
-
|
|
112
|
-
function PanesApp({ fastMode, rows }: { fastMode: boolean; rows: number }) {
|
|
113
|
-
const [focusedPane, setFocusedPane] = useState<"left" | "right">("left")
|
|
114
|
-
const search = useSearch()
|
|
115
|
-
|
|
116
|
-
const midpoint = Math.ceil(SCRIPT.length / 2)
|
|
117
|
-
const leftScript = useMemo(() => SCRIPT.slice(0, midpoint), [midpoint])
|
|
118
|
-
const rightScript = useMemo(() => SCRIPT.slice(midpoint), [midpoint])
|
|
119
|
-
|
|
120
|
-
useInput((input: string, key: Key) => {
|
|
121
|
-
// Don't exit while search is active — Escape closes search first
|
|
122
|
-
if (key.escape && !search.isActive) return "exit"
|
|
123
|
-
if (key.tab && !search.isActive) {
|
|
124
|
-
setFocusedPane((p) => (p === "left" ? "right" : "left"))
|
|
125
|
-
}
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
// Pane content height: rows - border(2) - title(1) - status(1) = rows - 4
|
|
129
|
-
const listHeight = Math.max(5, rows - 4)
|
|
130
|
-
|
|
131
|
-
return (
|
|
132
|
-
<Box flexDirection="column" height={rows}>
|
|
133
|
-
<Box flexDirection="row" flexGrow={1}>
|
|
134
|
-
<Box
|
|
135
|
-
width="50%"
|
|
136
|
-
flexDirection="column"
|
|
137
|
-
borderStyle="single"
|
|
138
|
-
borderColor={focusedPane === "left" ? "$primary" : "$border"}
|
|
139
|
-
overflow="hidden"
|
|
140
|
-
>
|
|
141
|
-
<Box paddingX={1}>
|
|
142
|
-
<Text color={focusedPane === "left" ? "$primary" : "$border"} bold={focusedPane === "left"}>
|
|
143
|
-
Agent A
|
|
144
|
-
</Text>
|
|
145
|
-
</Box>
|
|
146
|
-
<ChatPane
|
|
147
|
-
script={leftScript}
|
|
148
|
-
fastMode={fastMode}
|
|
149
|
-
height={listHeight}
|
|
150
|
-
active={focusedPane === "left"}
|
|
151
|
-
surfaceId="left"
|
|
152
|
-
/>
|
|
153
|
-
</Box>
|
|
154
|
-
<Box
|
|
155
|
-
width="50%"
|
|
156
|
-
flexDirection="column"
|
|
157
|
-
borderStyle="single"
|
|
158
|
-
borderColor={focusedPane === "right" ? "$primary" : "$border"}
|
|
159
|
-
overflow="hidden"
|
|
160
|
-
>
|
|
161
|
-
<Box paddingX={1}>
|
|
162
|
-
<Text color={focusedPane === "right" ? "$primary" : "$border"} bold={focusedPane === "right"}>
|
|
163
|
-
Agent B
|
|
164
|
-
</Text>
|
|
165
|
-
</Box>
|
|
166
|
-
<ChatPane
|
|
167
|
-
script={rightScript}
|
|
168
|
-
fastMode={fastMode}
|
|
169
|
-
height={listHeight}
|
|
170
|
-
active={focusedPane === "right"}
|
|
171
|
-
surfaceId="right"
|
|
172
|
-
/>
|
|
173
|
-
</Box>
|
|
174
|
-
</Box>
|
|
175
|
-
<SearchBar />
|
|
176
|
-
<Box paddingX={1}>
|
|
177
|
-
<Text color="$muted">Tab: switch pane · Ctrl+F: search · Esc: quit</Text>
|
|
178
|
-
</Box>
|
|
179
|
-
</Box>
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// ============================================================================
|
|
184
|
-
// Entry
|
|
185
|
-
// ============================================================================
|
|
186
|
-
|
|
187
|
-
export async function main() {
|
|
188
|
-
const args = process.argv.slice(2)
|
|
189
|
-
const fastMode = args.includes("--fast")
|
|
190
|
-
const rows = process.stdout.rows ?? 40
|
|
191
|
-
|
|
192
|
-
using handle = await run(
|
|
193
|
-
<SearchProvider>
|
|
194
|
-
<PanesApp fastMode={fastMode} rows={rows} />
|
|
195
|
-
</SearchProvider>,
|
|
196
|
-
{ mode: "fullscreen", kitty: false, textSizing: false },
|
|
197
|
-
)
|
|
198
|
-
await handle.waitUntilExit()
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (import.meta.main) {
|
|
202
|
-
await main()
|
|
203
|
-
}
|
package/apps/paste-demo.tsx
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bracketed Paste Demo
|
|
3
|
-
*
|
|
4
|
-
* Demonstrates bracketed paste mode — when text is pasted into the terminal,
|
|
5
|
-
* it arrives as a single event rather than individual keystrokes. This prevents
|
|
6
|
-
* pasted text from being interpreted as commands.
|
|
7
|
-
*
|
|
8
|
-
* Features:
|
|
9
|
-
* - Shows paste mode status (always enabled with render())
|
|
10
|
-
* - Displays pasted text as a single block event
|
|
11
|
-
* - Shows character count and line count of pasted text
|
|
12
|
-
* - Maintains a history of paste events
|
|
13
|
-
*
|
|
14
|
-
* Run: bun vendor/silvery/examples/apps/paste-demo.tsx
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import React, { useState } from "react"
|
|
18
|
-
import { render, Box, Text, H1, Small, Kbd, Muted, Lead, useInput, useApp, createTerm, type Key } from "silvery"
|
|
19
|
-
import { ExampleBanner, type ExampleMeta } from "../_banner.js"
|
|
20
|
-
|
|
21
|
-
export const meta: ExampleMeta = {
|
|
22
|
-
name: "Bracketed Paste",
|
|
23
|
-
description: "Receive pasted text as a single event via bracketed paste mode",
|
|
24
|
-
features: ["onPaste", "useInput", "bracketed paste mode"],
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// ============================================================================
|
|
28
|
-
// Types
|
|
29
|
-
// ============================================================================
|
|
30
|
-
|
|
31
|
-
interface PasteEvent {
|
|
32
|
-
id: number
|
|
33
|
-
text: string
|
|
34
|
-
charCount: number
|
|
35
|
-
lineCount: number
|
|
36
|
-
timestamp: string
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ============================================================================
|
|
40
|
-
// Components
|
|
41
|
-
// ============================================================================
|
|
42
|
-
|
|
43
|
-
function PasteIndicator() {
|
|
44
|
-
return (
|
|
45
|
-
<Box gap={1} paddingX={1}>
|
|
46
|
-
<Text color="$success" bold>
|
|
47
|
-
{"●"}
|
|
48
|
-
</Text>
|
|
49
|
-
<Text>Paste mode:</Text>
|
|
50
|
-
<Text color="$success" bold>
|
|
51
|
-
ENABLED
|
|
52
|
-
</Text>
|
|
53
|
-
<Muted>(bracketed paste is automatic with render())</Muted>
|
|
54
|
-
</Box>
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function PasteEventCard({ event, isLatest }: { event: PasteEvent; isLatest: boolean }) {
|
|
59
|
-
const preview = event.text.length > 60 ? event.text.slice(0, 57) + "..." : event.text
|
|
60
|
-
const displayText = preview.replace(/\n/g, "\\n").replace(/\t/g, "\\t")
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<Box
|
|
64
|
-
flexDirection="column"
|
|
65
|
-
borderStyle="round"
|
|
66
|
-
borderColor={isLatest ? "$primary" : "$border"}
|
|
67
|
-
paddingX={1}
|
|
68
|
-
marginBottom={0}
|
|
69
|
-
>
|
|
70
|
-
<Box justifyContent="space-between">
|
|
71
|
-
<H1 color={isLatest ? "$primary" : undefined}>Paste #{event.id}</H1>
|
|
72
|
-
<Small>{event.timestamp}</Small>
|
|
73
|
-
</Box>
|
|
74
|
-
<Box gap={2}>
|
|
75
|
-
<Small>
|
|
76
|
-
{event.charCount} char{event.charCount !== 1 ? "s" : ""}
|
|
77
|
-
</Small>
|
|
78
|
-
<Small>
|
|
79
|
-
{event.lineCount} line{event.lineCount !== 1 ? "s" : ""}
|
|
80
|
-
</Small>
|
|
81
|
-
</Box>
|
|
82
|
-
<Box marginTop={1}>
|
|
83
|
-
<Text color="$warning">{displayText}</Text>
|
|
84
|
-
</Box>
|
|
85
|
-
</Box>
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function EmptyState() {
|
|
90
|
-
return (
|
|
91
|
-
<Box flexDirection="column" padding={2} alignItems="center">
|
|
92
|
-
<Muted>No paste events yet.</Muted>
|
|
93
|
-
<Lead>Try pasting some text from your clipboard!</Lead>
|
|
94
|
-
<Lead>(Cmd+V on macOS, Ctrl+Shift+V on Linux)</Lead>
|
|
95
|
-
</Box>
|
|
96
|
-
)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function PasteDemo() {
|
|
100
|
-
const { exit } = useApp()
|
|
101
|
-
const [pasteHistory, setPasteHistory] = useState<PasteEvent[]>([])
|
|
102
|
-
const [nextId, setNextId] = useState(1)
|
|
103
|
-
|
|
104
|
-
useInput(
|
|
105
|
-
(input: string, key: Key) => {
|
|
106
|
-
if (input === "q" || key.escape) {
|
|
107
|
-
exit()
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Clear history
|
|
112
|
-
if (input === "x") {
|
|
113
|
-
setPasteHistory([])
|
|
114
|
-
setNextId(1)
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
onPaste: (text: string) => {
|
|
119
|
-
const now = new Date()
|
|
120
|
-
const timestamp = `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}`
|
|
121
|
-
|
|
122
|
-
const event: PasteEvent = {
|
|
123
|
-
id: nextId,
|
|
124
|
-
text,
|
|
125
|
-
charCount: text.length,
|
|
126
|
-
lineCount: text.split("\n").length,
|
|
127
|
-
timestamp,
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
setPasteHistory((prev) => [event, ...prev].slice(0, 10))
|
|
131
|
-
setNextId((prev) => prev + 1)
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
return (
|
|
137
|
-
<Box flexDirection="column" padding={1} gap={1}>
|
|
138
|
-
<PasteIndicator />
|
|
139
|
-
|
|
140
|
-
<Box flexDirection="column" borderStyle="round" borderColor="$primary" paddingX={1}>
|
|
141
|
-
<Box marginBottom={1}>
|
|
142
|
-
<H1>Paste History</H1>
|
|
143
|
-
<Small>
|
|
144
|
-
{" "}
|
|
145
|
-
— {pasteHistory.length} event{pasteHistory.length !== 1 ? "s" : ""}
|
|
146
|
-
</Small>
|
|
147
|
-
</Box>
|
|
148
|
-
|
|
149
|
-
{pasteHistory.length === 0 ? (
|
|
150
|
-
<EmptyState />
|
|
151
|
-
) : (
|
|
152
|
-
<Box flexDirection="column" overflow="scroll" height={12} gap={1}>
|
|
153
|
-
{pasteHistory.map((event, index) => (
|
|
154
|
-
<PasteEventCard key={event.id} event={event} isLatest={index === 0} />
|
|
155
|
-
))}
|
|
156
|
-
</Box>
|
|
157
|
-
)}
|
|
158
|
-
</Box>
|
|
159
|
-
|
|
160
|
-
<Muted>
|
|
161
|
-
{" "}
|
|
162
|
-
<Kbd>Paste text</Kbd> to see events <Kbd>x</Kbd> clear <Kbd>Esc/q</Kbd> quit
|
|
163
|
-
</Muted>
|
|
164
|
-
</Box>
|
|
165
|
-
)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// ============================================================================
|
|
169
|
-
// Main
|
|
170
|
-
// ============================================================================
|
|
171
|
-
|
|
172
|
-
export async function main() {
|
|
173
|
-
using term = createTerm()
|
|
174
|
-
const { waitUntilExit } = await render(
|
|
175
|
-
<ExampleBanner meta={meta} controls="Paste text to see events x clear Esc/q quit">
|
|
176
|
-
<PasteDemo />
|
|
177
|
-
</ExampleBanner>,
|
|
178
|
-
term,
|
|
179
|
-
)
|
|
180
|
-
await waitUntilExit()
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (import.meta.main) {
|
|
184
|
-
await main()
|
|
185
|
-
}
|
package/apps/scroll.tsx
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Scroll Example
|
|
3
|
-
*
|
|
4
|
-
* Demonstrates overflow="scroll" with keyboard navigation.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import React, { useState } from "react"
|
|
8
|
-
import { Box, Text, Kbd, Muted, render, useInput, useApp, createTerm, type Key } from "silvery"
|
|
9
|
-
import { ExampleBanner, type ExampleMeta } from "../_banner.js"
|
|
10
|
-
|
|
11
|
-
export const meta: ExampleMeta = {
|
|
12
|
-
name: "Scroll",
|
|
13
|
-
description: 'Native overflow="scroll" with automatic scroll-to-selected',
|
|
14
|
-
features: ['overflow="scroll"', "scrollTo", "useInput"],
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Generate sample items
|
|
18
|
-
const items = Array.from({ length: 50 }, (_, i) => ({
|
|
19
|
-
id: i,
|
|
20
|
-
title: `Item ${i + 1}`,
|
|
21
|
-
description: `This is the description for item number ${i + 1}`,
|
|
22
|
-
}))
|
|
23
|
-
|
|
24
|
-
export function ScrollExample() {
|
|
25
|
-
const { exit } = useApp()
|
|
26
|
-
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
27
|
-
|
|
28
|
-
useInput((input: string, key: Key) => {
|
|
29
|
-
if (input === "q" || key.escape) {
|
|
30
|
-
exit()
|
|
31
|
-
}
|
|
32
|
-
if (key.upArrow || input === "k") {
|
|
33
|
-
setSelectedIndex((prev) => Math.max(0, prev - 1))
|
|
34
|
-
}
|
|
35
|
-
if (key.downArrow || input === "j") {
|
|
36
|
-
setSelectedIndex((prev) => Math.min(items.length - 1, prev + 1))
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<Box flexDirection="column" padding={1} width={60} height={20}>
|
|
42
|
-
<Box
|
|
43
|
-
flexGrow={1}
|
|
44
|
-
flexDirection="column"
|
|
45
|
-
borderStyle="round"
|
|
46
|
-
borderColor="$primary"
|
|
47
|
-
overflow="scroll"
|
|
48
|
-
scrollTo={selectedIndex}
|
|
49
|
-
height={10}
|
|
50
|
-
>
|
|
51
|
-
{items.map((item, index) => (
|
|
52
|
-
<Box key={item.id} paddingX={1} backgroundColor={index === selectedIndex ? "$primary" : undefined}>
|
|
53
|
-
<Text color={index === selectedIndex ? "$primary-fg" : undefined} bold={index === selectedIndex}>
|
|
54
|
-
{item.title}
|
|
55
|
-
</Text>
|
|
56
|
-
</Box>
|
|
57
|
-
))}
|
|
58
|
-
</Box>
|
|
59
|
-
|
|
60
|
-
<Muted>
|
|
61
|
-
{" "}
|
|
62
|
-
<Kbd>j/k</Kbd> navigate <Kbd>Esc/q</Kbd> quit | Selected: {selectedIndex + 1}/{items.length}
|
|
63
|
-
</Muted>
|
|
64
|
-
</Box>
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export async function main() {
|
|
69
|
-
using term = createTerm()
|
|
70
|
-
await render(
|
|
71
|
-
<ExampleBanner meta={meta} controls="j/k navigate Esc/q quit">
|
|
72
|
-
<ScrollExample />
|
|
73
|
-
</ExampleBanner>,
|
|
74
|
-
term,
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (import.meta.main) {
|
|
79
|
-
await main()
|
|
80
|
-
}
|