@silvery/ui 0.3.0

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 (87) hide show
  1. package/package.json +71 -0
  2. package/src/animation/easing.ts +38 -0
  3. package/src/animation/index.ts +18 -0
  4. package/src/animation/useAnimation.ts +143 -0
  5. package/src/animation/useInterval.ts +39 -0
  6. package/src/animation/useLatest.ts +35 -0
  7. package/src/animation/useTimeout.ts +65 -0
  8. package/src/animation/useTransition.ts +110 -0
  9. package/src/animation.ts +24 -0
  10. package/src/ansi/index.ts +43 -0
  11. package/src/canvas/index.ts +169 -0
  12. package/src/cli/ansi.ts +85 -0
  13. package/src/cli/index.ts +39 -0
  14. package/src/cli/multi-progress.ts +340 -0
  15. package/src/cli/progress-bar.ts +222 -0
  16. package/src/cli/spinner.ts +275 -0
  17. package/src/components/Badge.tsx +54 -0
  18. package/src/components/Breadcrumb.tsx +72 -0
  19. package/src/components/Button.tsx +73 -0
  20. package/src/components/CommandPalette.tsx +186 -0
  21. package/src/components/Console.tsx +79 -0
  22. package/src/components/CursorLine.tsx +71 -0
  23. package/src/components/Divider.tsx +67 -0
  24. package/src/components/EditContextDisplay.tsx +164 -0
  25. package/src/components/ErrorBoundary.tsx +179 -0
  26. package/src/components/Form.tsx +86 -0
  27. package/src/components/GridCell.tsx +42 -0
  28. package/src/components/HorizontalVirtualList.tsx +375 -0
  29. package/src/components/ModalDialog.tsx +179 -0
  30. package/src/components/PickerDialog.tsx +208 -0
  31. package/src/components/PickerList.tsx +93 -0
  32. package/src/components/ProgressBar.tsx +126 -0
  33. package/src/components/Screen.tsx +78 -0
  34. package/src/components/ScrollbackList.tsx +92 -0
  35. package/src/components/ScrollbackView.tsx +390 -0
  36. package/src/components/SelectList.tsx +176 -0
  37. package/src/components/Skeleton.tsx +87 -0
  38. package/src/components/Spinner.tsx +64 -0
  39. package/src/components/SplitView.tsx +199 -0
  40. package/src/components/Table.tsx +139 -0
  41. package/src/components/Tabs.tsx +203 -0
  42. package/src/components/TextArea.tsx +264 -0
  43. package/src/components/TextInput.tsx +240 -0
  44. package/src/components/Toast.tsx +216 -0
  45. package/src/components/Toggle.tsx +73 -0
  46. package/src/components/Tooltip.tsx +60 -0
  47. package/src/components/TreeView.tsx +212 -0
  48. package/src/components/Typography.tsx +233 -0
  49. package/src/components/VirtualList.tsx +318 -0
  50. package/src/components/VirtualView.tsx +221 -0
  51. package/src/components/useReadline.ts +213 -0
  52. package/src/components/useTextArea.ts +648 -0
  53. package/src/components.ts +133 -0
  54. package/src/display/Table.tsx +179 -0
  55. package/src/display/index.ts +13 -0
  56. package/src/hooks/useTea.ts +133 -0
  57. package/src/image/Image.tsx +187 -0
  58. package/src/image/index.ts +15 -0
  59. package/src/image/kitty-graphics.ts +161 -0
  60. package/src/image/sixel-encoder.ts +194 -0
  61. package/src/images.ts +22 -0
  62. package/src/index.ts +34 -0
  63. package/src/input/Select.tsx +155 -0
  64. package/src/input/TextInput.tsx +227 -0
  65. package/src/input/index.ts +25 -0
  66. package/src/progress/als-context.ts +160 -0
  67. package/src/progress/declarative.ts +519 -0
  68. package/src/progress/index.ts +54 -0
  69. package/src/progress/step-node.ts +152 -0
  70. package/src/progress/steps.ts +425 -0
  71. package/src/progress/task.ts +138 -0
  72. package/src/progress/tasks.ts +216 -0
  73. package/src/react/ProgressBar.tsx +146 -0
  74. package/src/react/Spinner.tsx +74 -0
  75. package/src/react/Tasks.tsx +144 -0
  76. package/src/react/context.tsx +145 -0
  77. package/src/react/index.ts +30 -0
  78. package/src/types.ts +252 -0
  79. package/src/utils/eta.ts +155 -0
  80. package/src/utils/index.ts +13 -0
  81. package/src/wrappers/index.ts +36 -0
  82. package/src/wrappers/with-progress.ts +250 -0
  83. package/src/wrappers/with-select.ts +194 -0
  84. package/src/wrappers/with-spinner.ts +108 -0
  85. package/src/wrappers/with-text-input.ts +388 -0
  86. package/src/wrappers/wrap-emitter.ts +158 -0
  87. package/src/wrappers/wrap-generator.ts +143 -0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * React context for progress state management
3
+ */
4
+
5
+ import React, { createContext, useContext, useState, useCallback } from "react"
6
+ import type { SpinnerStyle } from "../types.js"
7
+ import { Spinner } from "./Spinner"
8
+
9
+ /** Progress context state */
10
+ interface ProgressContextState {
11
+ /** Currently showing a spinner */
12
+ isLoading: boolean
13
+ /** Loading message */
14
+ loadingText: string
15
+ /** Spinner style */
16
+ spinnerStyle: SpinnerStyle
17
+
18
+ /** Show a spinner with message */
19
+ showSpinner: (text: string, style?: SpinnerStyle) => void
20
+ /** Hide the spinner */
21
+ hideSpinner: () => void
22
+
23
+ /** Progress bar state */
24
+ progress: { current: number; total: number } | null
25
+ /** Update progress */
26
+ updateProgress: (current: number, total?: number) => void
27
+ /** Clear progress */
28
+ clearProgress: () => void
29
+ }
30
+
31
+ const ProgressContext = createContext<ProgressContextState | null>(null)
32
+
33
+ /**
34
+ * Progress context provider
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * function App() {
39
+ * return (
40
+ * <ProgressProvider>
41
+ * <MyApp />
42
+ * </ProgressProvider>
43
+ * );
44
+ * }
45
+ *
46
+ * function DeepComponent() {
47
+ * const { showSpinner, hideSpinner } = useProgress();
48
+ *
49
+ * const handleLoad = async () => {
50
+ * showSpinner("Loading...");
51
+ * await loadData();
52
+ * hideSpinner();
53
+ * };
54
+ * }
55
+ * ```
56
+ */
57
+ export function ProgressProvider({ children }: { children: React.ReactNode }): React.ReactElement {
58
+ const [isLoading, setIsLoading] = useState(false)
59
+ const [loadingText, setLoadingText] = useState("")
60
+ const [spinnerStyle, setSpinnerStyle] = useState<SpinnerStyle>("dots")
61
+ const [progress, setProgress] = useState<{
62
+ current: number
63
+ total: number
64
+ } | null>(null)
65
+
66
+ const showSpinner = useCallback((text: string, style: SpinnerStyle = "dots") => {
67
+ setLoadingText(text)
68
+ setSpinnerStyle(style)
69
+ setIsLoading(true)
70
+ }, [])
71
+
72
+ const hideSpinner = useCallback(() => {
73
+ setIsLoading(false)
74
+ setLoadingText("")
75
+ }, [])
76
+
77
+ const updateProgress = useCallback((current: number, total?: number) => {
78
+ setProgress((prev) => ({
79
+ current,
80
+ total: total ?? prev?.total ?? 100,
81
+ }))
82
+ }, [])
83
+
84
+ const clearProgress = useCallback(() => {
85
+ setProgress(null)
86
+ }, [])
87
+
88
+ const value: ProgressContextState = {
89
+ isLoading,
90
+ loadingText,
91
+ spinnerStyle,
92
+ showSpinner,
93
+ hideSpinner,
94
+ progress,
95
+ updateProgress,
96
+ clearProgress,
97
+ }
98
+
99
+ return <ProgressContext.Provider value={value}>{children}</ProgressContext.Provider>
100
+ }
101
+
102
+ /**
103
+ * Hook to access progress context
104
+ *
105
+ * @example
106
+ * ```tsx
107
+ * function LoadingOverlay() {
108
+ * const { isLoading, loadingText, spinnerStyle } = useProgress();
109
+ *
110
+ * if (!isLoading) return null;
111
+ *
112
+ * return <Spinner label={loadingText} style={spinnerStyle} />;
113
+ * }
114
+ * ```
115
+ */
116
+ export function useProgress(): ProgressContextState {
117
+ const context = useContext(ProgressContext)
118
+
119
+ if (!context) {
120
+ throw new Error("useProgress must be used within a ProgressProvider")
121
+ }
122
+
123
+ return context
124
+ }
125
+
126
+ /**
127
+ * Component that renders spinner when loading
128
+ *
129
+ * @example
130
+ * ```tsx
131
+ * <ProgressProvider>
132
+ * <ProgressIndicator />
133
+ * <MainContent />
134
+ * </ProgressProvider>
135
+ * ```
136
+ */
137
+ export function ProgressIndicator(): React.ReactElement | null {
138
+ const { isLoading, loadingText, spinnerStyle } = useProgress()
139
+
140
+ if (!isLoading) {
141
+ return null
142
+ }
143
+
144
+ return <Spinner label={loadingText} style={spinnerStyle} />
145
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * React components for TUI progress indicators
3
+ *
4
+ * @example
5
+ * ```tsx
6
+ * import { Spinner, ProgressBar, Tasks, Task, useProgress } from "@silvery/ui/react";
7
+ *
8
+ * // Spinner
9
+ * <Spinner label="Loading..." style="dots" />
10
+ *
11
+ * // Progress bar
12
+ * <ProgressBar value={50} total={100} showPercentage showETA />
13
+ *
14
+ * // Task list
15
+ * <Tasks>
16
+ * <Task title="Scanning" status="completed" />
17
+ * <Task title="Processing" status="running" />
18
+ * </Tasks>
19
+ *
20
+ * // Context for nested components
21
+ * <ProgressProvider>
22
+ * <App />
23
+ * </ProgressProvider>
24
+ * ```
25
+ */
26
+
27
+ export { Spinner, useSpinnerFrame } from "./Spinner"
28
+ export { ProgressBar, useProgressBar } from "./ProgressBar"
29
+ export { Task, Tasks, useTasks } from "./Tasks"
30
+ export { ProgressProvider, useProgress, ProgressIndicator } from "./context"
package/src/types.ts ADDED
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Core types for silvery-ui progress components
3
+ */
4
+
5
+ /** Progress info passed to callbacks (legacy, use StepProgress for steps()) */
6
+ export interface ProgressInfo {
7
+ phase?: string
8
+ current: number
9
+ total: number
10
+ detail?: string
11
+ }
12
+
13
+ /**
14
+ * Progress info yielded from step generators
15
+ *
16
+ * Yield with `label` to create/update a sub-step:
17
+ * ```typescript
18
+ * function* loadRepo() {
19
+ * yield { label: "Discovering files" };
20
+ * // ... do work ...
21
+ * yield { label: "Parsing markdown", current: 0, total: 100 };
22
+ * // ... do work ...
23
+ * yield { label: "Parsing markdown", current: 100, total: 100 };
24
+ * }
25
+ * ```
26
+ */
27
+ export interface StepProgress {
28
+ /** Display label for sub-step (changing label creates new sub-step) */
29
+ label?: string
30
+ /** Current progress count */
31
+ current?: number
32
+ /** Total count for progress display */
33
+ total?: number
34
+ }
35
+
36
+ /** Callback signature for progress reporting */
37
+ export type ProgressCallback = (info: ProgressInfo) => void
38
+
39
+ /** Generator that yields progress info */
40
+ export type ProgressGenerator<T = void> = Generator<{ current: number; total: number }, T, unknown>
41
+
42
+ /** Spinner animation styles */
43
+ export type SpinnerStyle = "dots" | "line" | "arc" | "bounce" | "pulse"
44
+
45
+ /** Task status for multi-task display */
46
+ export type TaskStatus = "pending" | "running" | "completed" | "failed" | "skipped"
47
+
48
+ /** Options for Spinner class */
49
+ export interface SpinnerOptions {
50
+ /** Initial text to display */
51
+ text?: string
52
+ /** Animation style */
53
+ style?: SpinnerStyle
54
+ /** Spinner color (chalk color name) */
55
+ color?: string
56
+ /** Output stream (default: process.stdout) */
57
+ stream?: NodeJS.WriteStream
58
+ /** Hide cursor during spinner (default: true) */
59
+ hideCursor?: boolean
60
+ /** Animation interval in ms (default: 80) */
61
+ interval?: number
62
+ }
63
+
64
+ /** Options for ProgressBar class */
65
+ export interface ProgressBarOptions {
66
+ /** Total value for progress calculation */
67
+ total?: number
68
+ /** Format string with placeholders: :bar :percent :current :total :eta :rate :phase */
69
+ format?: string
70
+ /** Width of the progress bar in characters (default: 40) */
71
+ width?: number
72
+ /** Character for completed portion (default: "█") */
73
+ complete?: string
74
+ /** Character for incomplete portion (default: "░") */
75
+ incomplete?: string
76
+ /** Show percentage (default: true) */
77
+ showPercentage?: boolean
78
+ /** Show ETA (default: true) */
79
+ showETA?: boolean
80
+ /** Output stream (default: process.stdout) */
81
+ stream?: NodeJS.WriteStream
82
+ /** Hide cursor during progress (default: true) */
83
+ hideCursor?: boolean
84
+ /** Phase names for multi-phase progress */
85
+ phases?: Record<string, string>
86
+ }
87
+
88
+ /** Options for withSpinner wrapper */
89
+ export interface WithSpinnerOptions {
90
+ /** Spinner style */
91
+ style?: SpinnerStyle
92
+ /** Clear the spinner output on completion */
93
+ clearOnComplete?: boolean
94
+ /** Color for the spinner */
95
+ color?: string
96
+ }
97
+
98
+ /** Options for withProgress wrapper */
99
+ export interface WithProgressOptions {
100
+ /** Map of phase keys to display names */
101
+ phases?: Record<string, string>
102
+ /** Format string for progress bar */
103
+ format?: string
104
+ /** Clear output on completion */
105
+ clearOnComplete?: boolean
106
+ /** Show initial loading message after this many ms (default: 1000). Set to 0 to show immediately. */
107
+ showAfter?: number
108
+ /** Initial loading message to show before progress starts (default: "Loading...") */
109
+ initialMessage?: string
110
+ }
111
+
112
+ /** Task state for Tasks component */
113
+ export interface TaskState {
114
+ id: string
115
+ title: string
116
+ status: TaskStatus
117
+ progress?: { current: number; total: number }
118
+ error?: Error
119
+ children?: TaskState[]
120
+ }
121
+
122
+ /** Props for React Spinner component */
123
+ export interface SpinnerProps {
124
+ /** Label text to display */
125
+ label?: string
126
+ /** Animation style */
127
+ style?: SpinnerStyle
128
+ /** Spinner color */
129
+ color?: string
130
+ }
131
+
132
+ /** Props for React ProgressBar component */
133
+ export interface ProgressBarProps {
134
+ /** Current value */
135
+ value: number
136
+ /** Total value */
137
+ total: number
138
+ /** Width in characters */
139
+ width?: number
140
+ /** Show percentage */
141
+ showPercentage?: boolean
142
+ /** Show ETA */
143
+ showETA?: boolean
144
+ /** Label text */
145
+ label?: string
146
+ /** Color for completed portion */
147
+ color?: string
148
+ }
149
+
150
+ /** Props for React Task component */
151
+ export interface TaskProps {
152
+ /** Task title */
153
+ title: string
154
+ /** Task status */
155
+ status: TaskStatus
156
+ /** Children (e.g., nested progress bar) */
157
+ children?: React.ReactNode
158
+ }
159
+
160
+ /** Props for React TextInput component */
161
+ export interface TextInputProps {
162
+ /** Current input value (controlled) */
163
+ value: string
164
+ /** Called when value changes */
165
+ onChange: (value: string) => void
166
+ /** Placeholder text when empty */
167
+ placeholder?: string
168
+ /** Mask character for password input (e.g., "*") */
169
+ mask?: string
170
+ /** Autocomplete suggestions */
171
+ autocomplete?: string[]
172
+ /** Called when autocomplete suggestion is selected */
173
+ onAutocomplete?: (suggestion: string) => void
174
+ /** Called when Enter is pressed */
175
+ onSubmit?: (value: string) => void
176
+ /** Cursor position (for rendering) */
177
+ cursorPosition?: number
178
+ /** Whether input is focused */
179
+ focused?: boolean
180
+ }
181
+
182
+ /** Options for withTextInput CLI wrapper */
183
+ export interface TextInputOptions {
184
+ /** Placeholder text shown when input is empty */
185
+ placeholder?: string
186
+ /** Mask character for password input (e.g., "*") */
187
+ mask?: string
188
+ /** Validation function - return error message or undefined if valid */
189
+ validate?: (value: string) => string | undefined
190
+ /** Autocomplete suggestions */
191
+ autocomplete?: string[]
192
+ /** Default value */
193
+ defaultValue?: string
194
+ /** Output stream (default: process.stdout) */
195
+ stream?: NodeJS.WriteStream
196
+ /** Input stream (default: process.stdin) */
197
+ inputStream?: NodeJS.ReadStream
198
+ }
199
+
200
+ /** Column definition for Table component */
201
+ export interface TableColumn {
202
+ /** Key in data objects to display */
203
+ key: string
204
+ /** Header text */
205
+ header: string
206
+ /** Fixed column width (auto-calculated from content if not specified) */
207
+ width?: number
208
+ /** Text alignment within the column */
209
+ align?: "left" | "center" | "right"
210
+ }
211
+
212
+ /** Props for React Table component */
213
+ export interface TableProps {
214
+ /** Column definitions */
215
+ columns: TableColumn[]
216
+ /** Data rows to display */
217
+ data: Array<Record<string, unknown>>
218
+ /** Show box borders around cells */
219
+ border?: boolean
220
+ }
221
+
222
+ /** Option for Select component */
223
+ export interface SelectOption<T> {
224
+ /** Display text for this option */
225
+ label: string
226
+ /** Value returned when selected */
227
+ value: T
228
+ }
229
+
230
+ /** Props for React Select component */
231
+ export interface SelectProps<T> {
232
+ /** Available options */
233
+ options: SelectOption<T>[]
234
+ /** Currently selected value */
235
+ value?: T
236
+ /** Called when selection changes */
237
+ onChange?: (value: T) => void
238
+ /** Maximum number of visible options (default: 10) */
239
+ maxVisible?: number
240
+ /** Controlled highlight index for keyboard navigation */
241
+ highlightIndex?: number
242
+ /** Called when highlight index changes */
243
+ onHighlightChange?: (index: number) => void
244
+ }
245
+
246
+ /** Options for withSelect CLI wrapper */
247
+ export interface WithSelectOptions {
248
+ /** Initial highlighted index (default: 0) */
249
+ initial?: number
250
+ /** Maximum number of visible options (default: 10) */
251
+ maxVisible?: number
252
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Shared ETA calculation utilities
3
+ */
4
+
5
+ /** Sample point for ETA calculation */
6
+ export interface ETASample {
7
+ time: number
8
+ value: number
9
+ }
10
+
11
+ /** ETA calculation result */
12
+ export interface ETAResult {
13
+ /** Estimated seconds remaining, or null if insufficient data */
14
+ seconds: number | null
15
+ /** Formatted ETA string (e.g., "1:30", "2:15:30", "--:--") */
16
+ formatted: string
17
+ }
18
+
19
+ /**
20
+ * Calculate ETA from a buffer of samples
21
+ *
22
+ * @param buffer - Array of {time, value} samples
23
+ * @param current - Current progress value
24
+ * @param total - Total target value
25
+ * @returns ETA in seconds (null if insufficient data)
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const buffer = [
30
+ * { time: 1000, value: 0 },
31
+ * { time: 2000, value: 10 },
32
+ * ];
33
+ * const eta = calculateETA(buffer, 10, 100);
34
+ * // eta = 9 (9 seconds remaining at 10 items/sec)
35
+ * ```
36
+ */
37
+ export function calculateETA(buffer: ETASample[], current: number, total: number): number | null {
38
+ if (buffer.length < 2) {
39
+ return null
40
+ }
41
+
42
+ const first = buffer[0]!
43
+ const last = buffer[buffer.length - 1]!
44
+
45
+ const elapsed = (last.time - first.time) / 1000 // seconds
46
+ const progress = last.value - first.value
47
+
48
+ if (elapsed <= 0 || progress <= 0) {
49
+ return null
50
+ }
51
+
52
+ const rate = progress / elapsed // items per second
53
+ const remaining = total - current
54
+
55
+ return remaining / rate
56
+ }
57
+
58
+ /**
59
+ * Format ETA seconds as human-readable string
60
+ *
61
+ * @param eta - ETA in seconds (null for unknown)
62
+ * @returns Formatted string (e.g., "1:30", "2:15:30", "--:--", ">1d")
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * formatETA(90) // "1:30"
67
+ * formatETA(3665) // "1:01:05"
68
+ * formatETA(null) // "--:--"
69
+ * formatETA(100000) // ">1d"
70
+ * ```
71
+ */
72
+ export function formatETA(eta: number | null): string {
73
+ if (eta === null || !isFinite(eta)) {
74
+ return "--:--"
75
+ }
76
+
77
+ if (eta > 86400) {
78
+ // > 24 hours
79
+ return ">1d"
80
+ }
81
+
82
+ const hours = Math.floor(eta / 3600)
83
+ const minutes = Math.floor((eta % 3600) / 60)
84
+ const seconds = Math.floor(eta % 60)
85
+
86
+ if (hours > 0) {
87
+ return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`
88
+ }
89
+
90
+ return `${minutes}:${seconds.toString().padStart(2, "0")}`
91
+ }
92
+
93
+ /**
94
+ * Calculate and format ETA in one call
95
+ *
96
+ * @param buffer - Array of {time, value} samples
97
+ * @param current - Current progress value
98
+ * @param total - Total target value
99
+ * @returns Object with seconds (number|null) and formatted string
100
+ */
101
+ export function getETA(buffer: ETASample[], current: number, total: number): ETAResult {
102
+ const seconds = calculateETA(buffer, current, total)
103
+ return {
104
+ seconds,
105
+ formatted: formatETA(seconds),
106
+ }
107
+ }
108
+
109
+ /** Default buffer size for ETA smoothing */
110
+ export const DEFAULT_ETA_BUFFER_SIZE = 10
111
+
112
+ /**
113
+ * Create an ETA tracker with automatic buffer management
114
+ *
115
+ * @param bufferSize - Number of samples to keep (default: 10)
116
+ * @returns ETA tracker object
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * const tracker = createETATracker();
121
+ * tracker.record(0);
122
+ * // ... later ...
123
+ * tracker.record(50);
124
+ * const eta = tracker.getETA(50, 100);
125
+ * console.log(eta.formatted); // "0:30"
126
+ * ```
127
+ */
128
+ export function createETATracker(bufferSize = DEFAULT_ETA_BUFFER_SIZE) {
129
+ const buffer: ETASample[] = []
130
+
131
+ return {
132
+ /** Record a new sample */
133
+ record(value: number): void {
134
+ buffer.push({ time: Date.now(), value })
135
+ if (buffer.length > bufferSize) {
136
+ buffer.shift()
137
+ }
138
+ },
139
+
140
+ /** Get current ETA */
141
+ getETA(current: number, total: number): ETAResult {
142
+ return getETA(buffer, current, total)
143
+ },
144
+
145
+ /** Reset the buffer */
146
+ reset(): void {
147
+ buffer.length = 0
148
+ },
149
+
150
+ /** Get buffer for external use */
151
+ getBuffer(): readonly ETASample[] {
152
+ return buffer
153
+ },
154
+ }
155
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shared utilities
3
+ */
4
+
5
+ export {
6
+ calculateETA,
7
+ formatETA,
8
+ getETA,
9
+ createETATracker,
10
+ DEFAULT_ETA_BUFFER_SIZE,
11
+ type ETASample,
12
+ type ETAResult,
13
+ } from "./eta"
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Progress wrappers - Adapt existing async patterns
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import {
7
+ * withSpinner,
8
+ * withProgress,
9
+ * wrapGenerator,
10
+ * wrapEmitter
11
+ * } from "@silvery/ui/wrappers";
12
+ *
13
+ * // Wrap any promise
14
+ * const data = await withSpinner(fetchData(), "Loading...");
15
+ *
16
+ * // Wrap callback-based APIs (like km sync)
17
+ * await withProgress(
18
+ * (onProgress) => manager.syncFromFs(onProgress),
19
+ * { phases: { scanning: "Scanning", reconciling: "Reconciling" } }
20
+ * );
21
+ *
22
+ * // Wrap generators
23
+ * await wrapGenerator(evaluateAllRules(), "Evaluating rules");
24
+ *
25
+ * // Track EventEmitter state
26
+ * wrapEmitter(manager, { events: { ready: { succeed: true } } });
27
+ * ```
28
+ */
29
+
30
+ export { withSpinner, attachSpinner } from "./with-spinner"
31
+ export { withProgress, createProgressCallback } from "./with-progress"
32
+ export { wrapGenerator, withIterableProgress } from "./wrap-generator"
33
+ export { wrapEmitter, waitForEvent } from "./wrap-emitter"
34
+ export { withSelect, createSelect } from "./with-select"
35
+ export { withTextInput, createTextInput } from "./with-text-input"
36
+ export type { ProgressCallback, ProgressInfo, TextInputOptions } from "../types.js"