@mdxui/terminal 2.0.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.
- package/README.md +571 -0
- package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
- package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
- package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
- package/dist/chunk-3EFDH7PK.js +5235 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-3X5IR6WE.js +884 -0
- package/dist/chunk-4FV5ZDCE.js +5236 -0
- package/dist/chunk-4OVMSF2J.js +243 -0
- package/dist/chunk-63FEETIS.js +4048 -0
- package/dist/chunk-B43KP7XJ.js +884 -0
- package/dist/chunk-BMTJXWUV.js +655 -0
- package/dist/chunk-C3SVH4N7.js +882 -0
- package/dist/chunk-EVWR7Y47.js +874 -0
- package/dist/chunk-F6A5VWUC.js +1285 -0
- package/dist/chunk-FD7KW7GE.js +882 -0
- package/dist/chunk-GBQ6UD6I.js +655 -0
- package/dist/chunk-GMDD3M6U.js +5227 -0
- package/dist/chunk-JBHRXOXM.js +1058 -0
- package/dist/chunk-JFOO3EYO.js +1182 -0
- package/dist/chunk-JQ5H3WXL.js +1291 -0
- package/dist/chunk-JQD5NASE.js +234 -0
- package/dist/chunk-KRHJP5R7.js +592 -0
- package/dist/chunk-KWF6WVJE.js +962 -0
- package/dist/chunk-LHYQVN3H.js +1038 -0
- package/dist/chunk-M3TLQLGC.js +1032 -0
- package/dist/chunk-MVW4Q5OP.js +240 -0
- package/dist/chunk-NXCZSWLU.js +1294 -0
- package/dist/chunk-O25TNRO6.js +607 -0
- package/dist/chunk-PNECDA2I.js +884 -0
- package/dist/chunk-QIHWRLJR.js +962 -0
- package/dist/chunk-QW5YMQ7K.js +882 -0
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/chunk-RP2MVQLR.js +962 -0
- package/dist/chunk-TP6RXGXA.js +1087 -0
- package/dist/chunk-TQQSTITZ.js +655 -0
- package/dist/chunk-X24GWXQV.js +1281 -0
- package/dist/components/index.d.ts +802 -0
- package/dist/components/index.js +149 -0
- package/dist/data/index.d.ts +2554 -0
- package/dist/data/index.js +51 -0
- package/dist/forms/index.d.ts +1596 -0
- package/dist/forms/index.js +464 -0
- package/dist/index-CQRFZntR.d.ts +867 -0
- package/dist/index.d.ts +579 -0
- package/dist/index.js +786 -0
- package/dist/interactive-D0JkWosD.d.ts +217 -0
- package/dist/keyboard/index.d.ts +2 -0
- package/dist/keyboard/index.js +43 -0
- package/dist/renderers/index.d.ts +546 -0
- package/dist/renderers/index.js +2157 -0
- package/dist/storybook/index.d.ts +396 -0
- package/dist/storybook/index.js +641 -0
- package/dist/theme/index.d.ts +1339 -0
- package/dist/theme/index.js +123 -0
- package/dist/types-Bxu5PAgA.d.ts +710 -0
- package/dist/types-CIlop5Ji.d.ts +701 -0
- package/dist/types-Ca8p_p5X.d.ts +710 -0
- package/package.json +90 -0
- package/src/__tests__/components/data/card.test.ts +458 -0
- package/src/__tests__/components/data/list.test.ts +473 -0
- package/src/__tests__/components/data/metrics.test.ts +541 -0
- package/src/__tests__/components/data/table.test.ts +448 -0
- package/src/__tests__/components/input/field.test.ts +555 -0
- package/src/__tests__/components/input/form.test.ts +870 -0
- package/src/__tests__/components/input/search.test.ts +1238 -0
- package/src/__tests__/components/input/select.test.ts +658 -0
- package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
- package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
- package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
- package/src/__tests__/components/navigation/tabs.test.ts +995 -0
- package/src/__tests__/components.test.tsx +1197 -0
- package/src/__tests__/core/compiler.test.ts +986 -0
- package/src/__tests__/core/parser.test.ts +785 -0
- package/src/__tests__/core/tier-switcher.test.ts +1103 -0
- package/src/__tests__/core/types.test.ts +1398 -0
- package/src/__tests__/data/collections.test.ts +1337 -0
- package/src/__tests__/data/db.test.ts +1265 -0
- package/src/__tests__/data/reactive.test.ts +1010 -0
- package/src/__tests__/data/sync.test.ts +1614 -0
- package/src/__tests__/errors.test.ts +660 -0
- package/src/__tests__/forms/integration.test.ts +444 -0
- package/src/__tests__/integration.test.ts +905 -0
- package/src/__tests__/keyboard.test.ts +1791 -0
- package/src/__tests__/renderer.test.ts +489 -0
- package/src/__tests__/renderers/ansi-css.test.ts +948 -0
- package/src/__tests__/renderers/ansi.test.ts +1366 -0
- package/src/__tests__/renderers/ascii.test.ts +1360 -0
- package/src/__tests__/renderers/interactive.test.ts +2353 -0
- package/src/__tests__/renderers/markdown.test.ts +1483 -0
- package/src/__tests__/renderers/text.test.ts +1369 -0
- package/src/__tests__/renderers/unicode.test.ts +1307 -0
- package/src/__tests__/theme.test.ts +639 -0
- package/src/__tests__/utils/assertions.ts +685 -0
- package/src/__tests__/utils/index.ts +115 -0
- package/src/__tests__/utils/test-renderer.ts +381 -0
- package/src/__tests__/utils/utils.test.ts +560 -0
- package/src/components/containers/card.ts +56 -0
- package/src/components/containers/dialog.ts +53 -0
- package/src/components/containers/index.ts +9 -0
- package/src/components/containers/panel.ts +59 -0
- package/src/components/feedback/badge.ts +40 -0
- package/src/components/feedback/index.ts +8 -0
- package/src/components/feedback/spinner.ts +23 -0
- package/src/components/helpers.ts +81 -0
- package/src/components/index.ts +153 -0
- package/src/components/layout/breadcrumb.ts +31 -0
- package/src/components/layout/index.ts +10 -0
- package/src/components/layout/list.ts +29 -0
- package/src/components/layout/sidebar.ts +79 -0
- package/src/components/layout/table.ts +62 -0
- package/src/components/primitives/box.ts +95 -0
- package/src/components/primitives/button.ts +54 -0
- package/src/components/primitives/index.ts +11 -0
- package/src/components/primitives/input.ts +88 -0
- package/src/components/primitives/select.ts +97 -0
- package/src/components/primitives/text.ts +60 -0
- package/src/components/render.ts +155 -0
- package/src/components/templates/app.ts +43 -0
- package/src/components/templates/index.ts +8 -0
- package/src/components/templates/site.ts +54 -0
- package/src/components/types.ts +777 -0
- package/src/core/compiler.ts +718 -0
- package/src/core/parser.ts +127 -0
- package/src/core/tier-switcher.ts +607 -0
- package/src/core/types.ts +672 -0
- package/src/data/collection.ts +316 -0
- package/src/data/collections.ts +50 -0
- package/src/data/context.tsx +174 -0
- package/src/data/db.ts +127 -0
- package/src/data/hooks.ts +532 -0
- package/src/data/index.ts +138 -0
- package/src/data/reactive.ts +1225 -0
- package/src/data/saas-collections.ts +375 -0
- package/src/data/sync.ts +1213 -0
- package/src/data/types.ts +660 -0
- package/src/forms/converters.ts +512 -0
- package/src/forms/index.ts +133 -0
- package/src/forms/schemas.ts +403 -0
- package/src/forms/types.ts +476 -0
- package/src/index.ts +542 -0
- package/src/keyboard/focus.ts +748 -0
- package/src/keyboard/index.ts +96 -0
- package/src/keyboard/integration.ts +371 -0
- package/src/keyboard/manager.ts +377 -0
- package/src/keyboard/presets.ts +90 -0
- package/src/renderers/ansi-css.ts +576 -0
- package/src/renderers/ansi.ts +802 -0
- package/src/renderers/ascii.ts +680 -0
- package/src/renderers/breadcrumb.ts +480 -0
- package/src/renderers/command-palette.ts +802 -0
- package/src/renderers/components/field.ts +210 -0
- package/src/renderers/components/form.ts +327 -0
- package/src/renderers/components/index.ts +21 -0
- package/src/renderers/components/search.ts +449 -0
- package/src/renderers/components/select.ts +222 -0
- package/src/renderers/index.ts +101 -0
- package/src/renderers/interactive/component-handlers.ts +622 -0
- package/src/renderers/interactive/cursor-manager.ts +147 -0
- package/src/renderers/interactive/focus-manager.ts +279 -0
- package/src/renderers/interactive/index.ts +661 -0
- package/src/renderers/interactive/input-handler.ts +164 -0
- package/src/renderers/interactive/keyboard-handler.ts +212 -0
- package/src/renderers/interactive/mouse-handler.ts +167 -0
- package/src/renderers/interactive/state-manager.ts +109 -0
- package/src/renderers/interactive/types.ts +338 -0
- package/src/renderers/interactive-string.ts +299 -0
- package/src/renderers/interactive.ts +59 -0
- package/src/renderers/markdown.ts +950 -0
- package/src/renderers/sidebar.ts +549 -0
- package/src/renderers/tabs.ts +682 -0
- package/src/renderers/text.ts +791 -0
- package/src/renderers/unicode.ts +917 -0
- package/src/renderers/utils.ts +942 -0
- package/src/router/adapters.ts +383 -0
- package/src/router/types.ts +140 -0
- package/src/router/utils.ts +452 -0
- package/src/schemas.ts +205 -0
- package/src/storybook/index.ts +91 -0
- package/src/storybook/interactive-decorator.tsx +659 -0
- package/src/storybook/keyboard-simulator.ts +501 -0
- package/src/theme/ansi-codes.ts +80 -0
- package/src/theme/box-drawing.ts +132 -0
- package/src/theme/color-convert.ts +254 -0
- package/src/theme/color-support.ts +321 -0
- package/src/theme/index.ts +134 -0
- package/src/theme/strip-ansi.ts +50 -0
- package/src/theme/tailwind-map.ts +469 -0
- package/src/theme/text-styles.ts +206 -0
- package/src/theme/theme-system.ts +568 -0
- package/src/types.ts +103 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mdxui/terminal
|
|
3
|
+
*
|
|
4
|
+
* OpenTUI renderer for MDXUI primitives.
|
|
5
|
+
* Build CLI dashboards with the same component contracts as web.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { CLI, Box, Text } from '@mdxui/terminal'
|
|
10
|
+
*
|
|
11
|
+
* async function main() {
|
|
12
|
+
* const cli = await CLI()
|
|
13
|
+
* cli.render(
|
|
14
|
+
* <Box border="single">
|
|
15
|
+
* <Text color="cyan">Hello Terminal!</Text>
|
|
16
|
+
* </Box>
|
|
17
|
+
* )
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* main()
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* // For more control, use the lower-level API
|
|
26
|
+
* import { createCliRenderer, createRoot } from '@mdxui/terminal'
|
|
27
|
+
*
|
|
28
|
+
* async function main() {
|
|
29
|
+
* const renderer = await createCliRenderer()
|
|
30
|
+
* const root = createRoot(renderer)
|
|
31
|
+
* root.render(<App />)
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import React, { createContext, useContext, useState, useEffect, type ReactNode } from 'react'
|
|
37
|
+
import { detectColorSupport } from './theme'
|
|
38
|
+
|
|
39
|
+
// Re-export components mapped to OpenTUI
|
|
40
|
+
export * from './components'
|
|
41
|
+
export * from './theme'
|
|
42
|
+
export * from './keyboard'
|
|
43
|
+
export * from './types'
|
|
44
|
+
export * from './data'
|
|
45
|
+
|
|
46
|
+
// Core compiler
|
|
47
|
+
export * from './core/compiler'
|
|
48
|
+
|
|
49
|
+
// Core parser
|
|
50
|
+
export { parseUINode, parseUINodeFromJSON, ParseError } from './core/parser'
|
|
51
|
+
|
|
52
|
+
// Core types (selective export to avoid conflicts with components/types RenderContext)
|
|
53
|
+
export type { UINode, RenderTier, ThemeTokens } from './core/types'
|
|
54
|
+
export type { RenderContext as CoreRenderContext } from './core/types'
|
|
55
|
+
export { UINodeSchema, RenderTierSchema, ThemeTokensSchema, RenderContextSchema } from './core/types'
|
|
56
|
+
|
|
57
|
+
// Renderers
|
|
58
|
+
export { renderText } from './renderers/text'
|
|
59
|
+
export type { TextRenderOptions } from './renderers/text'
|
|
60
|
+
|
|
61
|
+
// ANSI to CSS conversion for web rendering
|
|
62
|
+
export {
|
|
63
|
+
ansiToCSS,
|
|
64
|
+
ansiToHTML,
|
|
65
|
+
parseAnsiToSpans,
|
|
66
|
+
spanToInlineStyle,
|
|
67
|
+
} from './renderers/ansi-css'
|
|
68
|
+
export type {
|
|
69
|
+
CSSStyleProperties,
|
|
70
|
+
StyledSpan,
|
|
71
|
+
ANSIToCSSResult,
|
|
72
|
+
} from './renderers/ansi-css'
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// Renderer Setup
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* CLI renderer configuration options
|
|
80
|
+
*/
|
|
81
|
+
export interface CliRendererConfig {
|
|
82
|
+
/** Input stream (defaults to process.stdin) */
|
|
83
|
+
stdin?: NodeJS.ReadStream
|
|
84
|
+
/** Output stream (defaults to process.stdout) */
|
|
85
|
+
stdout?: NodeJS.WriteStream
|
|
86
|
+
/** Exit on Ctrl+C (defaults to true) */
|
|
87
|
+
exitOnCtrlC?: boolean
|
|
88
|
+
/** Target frames per second */
|
|
89
|
+
targetFps?: number
|
|
90
|
+
/** Use alternate screen buffer */
|
|
91
|
+
useAlternateScreen?: boolean
|
|
92
|
+
/** Enable mouse support */
|
|
93
|
+
useMouse?: boolean
|
|
94
|
+
/** Background color */
|
|
95
|
+
backgroundColor?: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* CLI renderer instance
|
|
100
|
+
*/
|
|
101
|
+
export interface CliRenderer {
|
|
102
|
+
/** Terminal width in columns */
|
|
103
|
+
width: number
|
|
104
|
+
/** Terminal height in rows */
|
|
105
|
+
height: number
|
|
106
|
+
/** Start the render loop */
|
|
107
|
+
start(): void
|
|
108
|
+
/** Stop the render loop */
|
|
109
|
+
stop(): void
|
|
110
|
+
/** Destroy the renderer and clean up */
|
|
111
|
+
destroy(): void
|
|
112
|
+
/** Request a render update */
|
|
113
|
+
requestRender(): void
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Root interface for React rendering
|
|
118
|
+
*/
|
|
119
|
+
export interface Root {
|
|
120
|
+
/** Render a React element */
|
|
121
|
+
render(element: ReactNode): void
|
|
122
|
+
/** Unmount and clean up */
|
|
123
|
+
unmount(): void
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Create an OpenTUI CLI renderer for terminal rendering.
|
|
128
|
+
*
|
|
129
|
+
* @param config - Optional configuration options
|
|
130
|
+
* @returns Promise resolving to a CLI renderer instance
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```tsx
|
|
134
|
+
* import { createCliRenderer, createRoot } from '@mdxui/terminal'
|
|
135
|
+
*
|
|
136
|
+
* async function main() {
|
|
137
|
+
* const renderer = await createCliRenderer()
|
|
138
|
+
* const root = createRoot(renderer)
|
|
139
|
+
* root.render(<App />)
|
|
140
|
+
* }
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export async function createCliRenderer(config?: CliRendererConfig): Promise<CliRenderer> {
|
|
144
|
+
const { createCliRenderer: opentuiCreateCliRenderer } = await import('@opentui/core')
|
|
145
|
+
return opentuiCreateCliRenderer(config)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Create a root for rendering a React tree with the given CLI renderer.
|
|
150
|
+
*
|
|
151
|
+
* @param renderer - The CLI renderer to use (from createCliRenderer)
|
|
152
|
+
* @returns A root object with render and unmount methods
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```tsx
|
|
156
|
+
* const renderer = await createCliRenderer()
|
|
157
|
+
* const root = createRoot(renderer)
|
|
158
|
+
* root.render(<App />)
|
|
159
|
+
*
|
|
160
|
+
* // Later, to clean up:
|
|
161
|
+
* root.unmount()
|
|
162
|
+
* renderer.destroy()
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
export function createRoot(renderer: CliRenderer): Root {
|
|
166
|
+
// Dynamic import to avoid issues in non-terminal environments
|
|
167
|
+
let rootInstance: Root | null = null
|
|
168
|
+
let initPromise: Promise<Root> | null = null
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
render: async (element: ReactNode) => {
|
|
172
|
+
// Guard against race condition: if render() is called twice before
|
|
173
|
+
// the async import completes, we reuse the same initialization promise
|
|
174
|
+
// to ensure only one root instance is created
|
|
175
|
+
if (!initPromise) {
|
|
176
|
+
initPromise = import('@opentui/react').then(({ createRoot: opentuiCreateRoot }) => {
|
|
177
|
+
rootInstance = opentuiCreateRoot(renderer as any)
|
|
178
|
+
return rootInstance
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
const root = await initPromise
|
|
182
|
+
root.render(element)
|
|
183
|
+
},
|
|
184
|
+
unmount: () => {
|
|
185
|
+
if (rootInstance) {
|
|
186
|
+
rootInstance.unmount()
|
|
187
|
+
rootInstance = null
|
|
188
|
+
}
|
|
189
|
+
initPromise = null
|
|
190
|
+
},
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// High-Level CLI API
|
|
196
|
+
// ============================================================================
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Options for the CLI() function
|
|
200
|
+
*/
|
|
201
|
+
export interface CLIOptions {
|
|
202
|
+
/** Color support level. Auto-detected if not specified. */
|
|
203
|
+
colorSupport?: 'none' | '16' | '256' | 'truecolor'
|
|
204
|
+
/** Input stream (defaults to process.stdin) */
|
|
205
|
+
stdin?: NodeJS.ReadStream
|
|
206
|
+
/** Output stream (defaults to process.stdout) */
|
|
207
|
+
stdout?: NodeJS.WriteStream
|
|
208
|
+
/** Exit on Ctrl+C (defaults to true) */
|
|
209
|
+
exitOnCtrlC?: boolean
|
|
210
|
+
/** Target frames per second */
|
|
211
|
+
targetFps?: number
|
|
212
|
+
/** Use alternate screen buffer */
|
|
213
|
+
useAlternateScreen?: boolean
|
|
214
|
+
/** Enable mouse support */
|
|
215
|
+
useMouse?: boolean
|
|
216
|
+
/** Background color */
|
|
217
|
+
backgroundColor?: string
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* CLI instance returned by CLI()
|
|
222
|
+
*/
|
|
223
|
+
export interface CLIInstance {
|
|
224
|
+
/** Terminal width in columns */
|
|
225
|
+
readonly width: number
|
|
226
|
+
/** Terminal height in rows */
|
|
227
|
+
readonly height: number
|
|
228
|
+
/** Render a React element to the terminal */
|
|
229
|
+
render: (element: ReactNode) => void
|
|
230
|
+
/** Clear the terminal */
|
|
231
|
+
clear: () => void
|
|
232
|
+
/** Destroy the CLI instance and clean up resources */
|
|
233
|
+
destroy: () => void
|
|
234
|
+
/** Request a render update */
|
|
235
|
+
requestRender: () => void
|
|
236
|
+
/** The underlying renderer instance */
|
|
237
|
+
readonly renderer: CliRenderer
|
|
238
|
+
/** The underlying root instance */
|
|
239
|
+
readonly root: Root
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Create a high-level CLI instance for terminal rendering.
|
|
244
|
+
*
|
|
245
|
+
* This is the recommended entry point for building terminal UIs with @mdxui/terminal.
|
|
246
|
+
* It combines `createCliRenderer` and `createRoot` into a single, easy-to-use API.
|
|
247
|
+
*
|
|
248
|
+
* @param options - Optional configuration options
|
|
249
|
+
* @returns Promise resolving to a CLI instance with render, clear, and destroy methods
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```tsx
|
|
253
|
+
* import { CLI, Box, Text } from '@mdxui/terminal'
|
|
254
|
+
*
|
|
255
|
+
* async function main() {
|
|
256
|
+
* const cli = await CLI()
|
|
257
|
+
* cli.render(
|
|
258
|
+
* <Box border="single">
|
|
259
|
+
* <Text color="cyan">Hello Terminal!</Text>
|
|
260
|
+
* </Box>
|
|
261
|
+
* )
|
|
262
|
+
* }
|
|
263
|
+
*
|
|
264
|
+
* main()
|
|
265
|
+
* ```
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```tsx
|
|
269
|
+
* // With custom options
|
|
270
|
+
* const cli = await CLI({
|
|
271
|
+
* useAlternateScreen: true,
|
|
272
|
+
* useMouse: true,
|
|
273
|
+
* exitOnCtrlC: true,
|
|
274
|
+
* })
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
export async function CLI(options?: CLIOptions): Promise<CLIInstance> {
|
|
278
|
+
// Detect color support if not explicitly specified
|
|
279
|
+
const colorSupport = options?.colorSupport ?? detectColorSupport()
|
|
280
|
+
|
|
281
|
+
// Map our simplified color support to OpenTUI's config
|
|
282
|
+
// OpenTUI doesn't have a direct colorSupport option, but we can use it
|
|
283
|
+
// for future color adaptation in our components
|
|
284
|
+
|
|
285
|
+
// Create the underlying renderer - use dynamic import to get the real OpenTUI renderer
|
|
286
|
+
const { createCliRenderer: opentuiCreateCliRenderer } = await import('@opentui/core')
|
|
287
|
+
const { createRoot: opentuiCreateRoot } = await import('@opentui/react')
|
|
288
|
+
|
|
289
|
+
const opentuiRenderer = await opentuiCreateCliRenderer({
|
|
290
|
+
stdin: options?.stdin,
|
|
291
|
+
stdout: options?.stdout,
|
|
292
|
+
exitOnCtrlC: options?.exitOnCtrlC,
|
|
293
|
+
targetFps: options?.targetFps,
|
|
294
|
+
useAlternateScreen: options?.useAlternateScreen,
|
|
295
|
+
useMouse: options?.useMouse,
|
|
296
|
+
backgroundColor: options?.backgroundColor,
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
// Create the React root with the actual OpenTUI renderer
|
|
300
|
+
const root = opentuiCreateRoot(opentuiRenderer)
|
|
301
|
+
|
|
302
|
+
// Create a simplified interface that exposes what consumers need
|
|
303
|
+
const renderer: CliRenderer = {
|
|
304
|
+
get width() {
|
|
305
|
+
return opentuiRenderer.width
|
|
306
|
+
},
|
|
307
|
+
get height() {
|
|
308
|
+
return opentuiRenderer.height
|
|
309
|
+
},
|
|
310
|
+
start: () => opentuiRenderer.start(),
|
|
311
|
+
stop: () => opentuiRenderer.stop(),
|
|
312
|
+
destroy: () => opentuiRenderer.destroy(),
|
|
313
|
+
requestRender: () => opentuiRenderer.requestRender(),
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
get width() {
|
|
318
|
+
return opentuiRenderer.width
|
|
319
|
+
},
|
|
320
|
+
get height() {
|
|
321
|
+
return opentuiRenderer.height
|
|
322
|
+
},
|
|
323
|
+
render: (element: ReactNode) => {
|
|
324
|
+
root.render(element)
|
|
325
|
+
},
|
|
326
|
+
clear: () => {
|
|
327
|
+
// Render empty to clear
|
|
328
|
+
root.render(null)
|
|
329
|
+
},
|
|
330
|
+
destroy: () => {
|
|
331
|
+
root.unmount()
|
|
332
|
+
opentuiRenderer.destroy()
|
|
333
|
+
},
|
|
334
|
+
requestRender: () => {
|
|
335
|
+
opentuiRenderer.requestRender()
|
|
336
|
+
},
|
|
337
|
+
renderer,
|
|
338
|
+
root,
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ============================================================================
|
|
343
|
+
// Terminal-Specific Hooks
|
|
344
|
+
// ============================================================================
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Get the current terminal dimensions, with automatic updates on resize.
|
|
348
|
+
*
|
|
349
|
+
* Returns `{ width, height }` representing the terminal's column and row count.
|
|
350
|
+
*
|
|
351
|
+
* **Behavior by environment:**
|
|
352
|
+
* - **Node.js TTY**: Uses `process.stdout.columns/rows` and subscribes to resize events
|
|
353
|
+
* - **Browser/Non-TTY**: Falls back to 80x24 defaults
|
|
354
|
+
*
|
|
355
|
+
* @returns Current terminal width and height in columns/rows
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* ```tsx
|
|
359
|
+
* function MyComponent() {
|
|
360
|
+
* const { width, height } = useTerminalSize()
|
|
361
|
+
* return <Text>Terminal: {width}x{height}</Text>
|
|
362
|
+
* }
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
export function useTerminalSize(): { width: number; height: number } {
|
|
366
|
+
// Helper to get current dimensions
|
|
367
|
+
const getSize = (): { width: number; height: number } => {
|
|
368
|
+
if (typeof process !== 'undefined' && process.stdout?.columns && process.stdout?.rows) {
|
|
369
|
+
return {
|
|
370
|
+
width: process.stdout.columns,
|
|
371
|
+
height: process.stdout.rows,
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// Default fallback for non-terminal environments (e.g., browser, tests)
|
|
375
|
+
return { width: 80, height: 24 }
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const [size, setSize] = useState(getSize)
|
|
379
|
+
|
|
380
|
+
useEffect(() => {
|
|
381
|
+
// Only subscribe to resize events in Node.js TTY environment
|
|
382
|
+
if (typeof process === 'undefined' || !process.stdout?.on) {
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const handleResize = () => {
|
|
387
|
+
setSize(getSize())
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Subscribe to resize events
|
|
391
|
+
process.stdout.on('resize', handleResize)
|
|
392
|
+
|
|
393
|
+
// Sync with current size in case it changed between initial render and effect
|
|
394
|
+
const currentSize = getSize()
|
|
395
|
+
if (currentSize.width !== size.width || currentSize.height !== size.height) {
|
|
396
|
+
setSize(currentSize)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Cleanup listener on unmount
|
|
400
|
+
return () => {
|
|
401
|
+
process.stdout.off('resize', handleResize)
|
|
402
|
+
}
|
|
403
|
+
}, []) // Empty deps - we only want to set up listener once
|
|
404
|
+
|
|
405
|
+
return size
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Access terminal context and capabilities.
|
|
410
|
+
*
|
|
411
|
+
* Provides terminal dimensions plus color support detection and raw mode status.
|
|
412
|
+
* Uses @opentui/react for dimensions and our color-support module for capabilities.
|
|
413
|
+
*
|
|
414
|
+
* @returns Terminal state including dimensions, color support level, and raw mode
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* ```tsx
|
|
418
|
+
* function StatusBar() {
|
|
419
|
+
* const { width, height, colorSupport } = useTerminal()
|
|
420
|
+
* return (
|
|
421
|
+
* <Box>
|
|
422
|
+
* <Text>Size: {width}x{height}</Text>
|
|
423
|
+
* <Text>Colors: {colorSupport}</Text>
|
|
424
|
+
* </Box>
|
|
425
|
+
* )
|
|
426
|
+
* }
|
|
427
|
+
* ```
|
|
428
|
+
*/
|
|
429
|
+
export function useTerminal(): {
|
|
430
|
+
width: number
|
|
431
|
+
height: number
|
|
432
|
+
colorSupport: 'none' | 'basic' | '256' | 'truecolor'
|
|
433
|
+
isRaw: boolean
|
|
434
|
+
} {
|
|
435
|
+
const { width, height } = useTerminalSize()
|
|
436
|
+
|
|
437
|
+
// Detect color support using our existing utility
|
|
438
|
+
// detectColorSupport returns 'none' | '16' | '256' | 'truecolor'
|
|
439
|
+
// We map '16' to 'basic' for a more intuitive API
|
|
440
|
+
const detected = detectColorSupport()
|
|
441
|
+
const colorSupport: 'none' | 'basic' | '256' | 'truecolor' =
|
|
442
|
+
detected === '16' ? 'basic' : detected
|
|
443
|
+
|
|
444
|
+
// Check raw mode from process.stdin
|
|
445
|
+
const isRaw = typeof process !== 'undefined' && process.stdin?.isRaw === true
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
width,
|
|
449
|
+
height,
|
|
450
|
+
colorSupport,
|
|
451
|
+
isRaw,
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// useFocus is exported from ./keyboard
|
|
456
|
+
// useKeyboard and keyboard bindings are exported from ./keyboard
|
|
457
|
+
// For hotkey functionality, use createKeyboardManager with VIM_BINDINGS or COMMON_BINDINGS
|
|
458
|
+
|
|
459
|
+
// ============================================================================
|
|
460
|
+
// Theme Context
|
|
461
|
+
// ============================================================================
|
|
462
|
+
|
|
463
|
+
interface ThemeContextValue {
|
|
464
|
+
theme: 'light' | 'dark'
|
|
465
|
+
setTheme: (theme: 'light' | 'dark') => void
|
|
466
|
+
colors: Record<string, string>
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const ThemeContext = createContext<ThemeContextValue | null>(null)
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Access current theme and colors
|
|
473
|
+
*/
|
|
474
|
+
export function useTheme(): ThemeContextValue {
|
|
475
|
+
const context = useContext(ThemeContext)
|
|
476
|
+
if (!context) {
|
|
477
|
+
// Return default theme if not in provider
|
|
478
|
+
return {
|
|
479
|
+
theme: 'dark',
|
|
480
|
+
setTheme: () => {},
|
|
481
|
+
colors: {},
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return context
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ============================================================================
|
|
488
|
+
// Providers
|
|
489
|
+
// ============================================================================
|
|
490
|
+
|
|
491
|
+
export interface TerminalProviderProps {
|
|
492
|
+
children?: ReactNode
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Terminal context provider - wraps app with terminal capabilities
|
|
497
|
+
*/
|
|
498
|
+
export function TerminalProvider(props: TerminalProviderProps): React.ReactElement {
|
|
499
|
+
return React.createElement('terminal-provider', props)
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export interface ThemeProviderProps {
|
|
503
|
+
children?: ReactNode
|
|
504
|
+
theme?: 'light' | 'dark'
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Theme provider - provides color scheme to components
|
|
509
|
+
*/
|
|
510
|
+
export function ThemeProvider(props: ThemeProviderProps): React.ReactElement {
|
|
511
|
+
const value: ThemeContextValue = {
|
|
512
|
+
theme: props.theme || 'dark',
|
|
513
|
+
setTheme: () => {},
|
|
514
|
+
colors: {},
|
|
515
|
+
}
|
|
516
|
+
return React.createElement(
|
|
517
|
+
ThemeContext.Provider,
|
|
518
|
+
{ value },
|
|
519
|
+
props.children
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// FocusProvider is exported from ./keyboard
|
|
524
|
+
|
|
525
|
+
// ============================================================================
|
|
526
|
+
// App Shell
|
|
527
|
+
// ============================================================================
|
|
528
|
+
|
|
529
|
+
export interface TerminalAppProps {
|
|
530
|
+
children: React.ReactNode
|
|
531
|
+
/** Show status bar at bottom */
|
|
532
|
+
statusBar?: React.ReactNode
|
|
533
|
+
/** Handle quit (Ctrl+C, q) */
|
|
534
|
+
onQuit?: () => void
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Terminal app shell with standard layout
|
|
539
|
+
*/
|
|
540
|
+
export function TerminalApp(props: TerminalAppProps): React.ReactElement {
|
|
541
|
+
return React.createElement('terminal-app', props)
|
|
542
|
+
}
|