@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
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme System
|
|
3
|
+
*
|
|
4
|
+
* Terminal theme creation, composition, and management utilities.
|
|
5
|
+
* Provides dark/light mode themes with semantic color tokens.
|
|
6
|
+
*
|
|
7
|
+
* All public APIs include runtime validation via Zod schemas.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { ANSI } from './ansi-codes'
|
|
13
|
+
import {
|
|
14
|
+
CreateTerminalThemeOptionsSchema,
|
|
15
|
+
CreateThemeInputSchema,
|
|
16
|
+
} from '../schemas'
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Type Definitions
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Color tokens for a terminal theme.
|
|
24
|
+
*
|
|
25
|
+
* All values are ANSI escape sequences that can be directly concatenated
|
|
26
|
+
* with text to apply styling.
|
|
27
|
+
*/
|
|
28
|
+
export interface TerminalThemeColors {
|
|
29
|
+
/** Primary brand/accent color for key UI elements */
|
|
30
|
+
primary: string
|
|
31
|
+
/** Secondary color for less prominent elements */
|
|
32
|
+
secondary: string
|
|
33
|
+
/** Accent color for highlights and special elements */
|
|
34
|
+
accent: string
|
|
35
|
+
/** Muted/dimmed color for less important text */
|
|
36
|
+
muted: string
|
|
37
|
+
|
|
38
|
+
/** Success state indicator (green) */
|
|
39
|
+
success: string
|
|
40
|
+
/** Warning state indicator (yellow/orange) */
|
|
41
|
+
warning: string
|
|
42
|
+
/** Error state indicator (red) */
|
|
43
|
+
error: string
|
|
44
|
+
/** Informational state indicator (blue) */
|
|
45
|
+
info: string
|
|
46
|
+
|
|
47
|
+
/** Border/divider color */
|
|
48
|
+
border: string
|
|
49
|
+
/** Background color (usually includes background escape) */
|
|
50
|
+
background: string
|
|
51
|
+
/** Default text/foreground color */
|
|
52
|
+
foreground: string
|
|
53
|
+
|
|
54
|
+
/** Selection/highlight background */
|
|
55
|
+
selection: string
|
|
56
|
+
/** Focus indicator color */
|
|
57
|
+
focus: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A complete terminal theme with color mode and color tokens.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* const theme = createTerminalTheme({ mode: 'dark' })
|
|
66
|
+
* console.log(`${theme.colors.primary}Primary text${ANSI.reset}`)
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export interface TerminalTheme {
|
|
70
|
+
/** Color mode: 'dark' or 'light' */
|
|
71
|
+
mode: 'dark' | 'light'
|
|
72
|
+
/** Color token values */
|
|
73
|
+
colors: TerminalThemeColors
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Legacy theme interface for backward compatibility.
|
|
78
|
+
*
|
|
79
|
+
* @deprecated Use {@link TerminalTheme} with {@link createTerminalTheme} instead.
|
|
80
|
+
*/
|
|
81
|
+
export interface LegacyTerminalTheme {
|
|
82
|
+
primary: string
|
|
83
|
+
secondary: string
|
|
84
|
+
accent: string
|
|
85
|
+
muted: string
|
|
86
|
+
success: string
|
|
87
|
+
warning: string
|
|
88
|
+
error: string
|
|
89
|
+
info: string
|
|
90
|
+
border: string
|
|
91
|
+
background: string
|
|
92
|
+
foreground: string
|
|
93
|
+
selection: string
|
|
94
|
+
focus: string
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface LegacyTerminalThemeWithExtras extends LegacyTerminalTheme {
|
|
98
|
+
typography?: {
|
|
99
|
+
headingWeight: string
|
|
100
|
+
bodyWeight: string
|
|
101
|
+
codeFont: string
|
|
102
|
+
}
|
|
103
|
+
spacing?: {
|
|
104
|
+
xs: number
|
|
105
|
+
sm: number
|
|
106
|
+
md: number
|
|
107
|
+
lg: number
|
|
108
|
+
xl: number
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Options for creating a terminal theme.
|
|
114
|
+
*/
|
|
115
|
+
export interface CreateTerminalThemeOptions {
|
|
116
|
+
/** Color mode: 'dark' (default) or 'light' */
|
|
117
|
+
mode?: 'dark' | 'light'
|
|
118
|
+
/** Custom color overrides */
|
|
119
|
+
colors?: Partial<TerminalThemeColors>
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================================================
|
|
123
|
+
// Theme Color Presets
|
|
124
|
+
// ============================================================================
|
|
125
|
+
|
|
126
|
+
const darkThemeColors: TerminalThemeColors = {
|
|
127
|
+
primary: '\x1b[38;5;33m', // Blue
|
|
128
|
+
secondary: '\x1b[38;5;39m', // Lighter blue
|
|
129
|
+
accent: '\x1b[38;5;200m', // Magenta/Pink
|
|
130
|
+
muted: '\x1b[38;5;243m', // Gray
|
|
131
|
+
|
|
132
|
+
success: '\x1b[38;5;71m', // Green
|
|
133
|
+
warning: '\x1b[38;5;220m', // Yellow
|
|
134
|
+
error: '\x1b[38;5;160m', // Red
|
|
135
|
+
info: '\x1b[38;5;33m', // Blue
|
|
136
|
+
|
|
137
|
+
border: '\x1b[38;5;240m', // Dark gray
|
|
138
|
+
background: '\x1b[48;5;234m', // Very dark gray
|
|
139
|
+
foreground: '\x1b[38;5;252m', // Light gray
|
|
140
|
+
|
|
141
|
+
selection: '\x1b[48;5;24m', // Dark blue background
|
|
142
|
+
focus: '\x1b[48;5;33m', // Blue background
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const lightThemeColors: TerminalThemeColors = {
|
|
146
|
+
primary: '\x1b[38;5;25m', // Darker blue for light mode
|
|
147
|
+
secondary: '\x1b[38;5;31m', // Darker cyan
|
|
148
|
+
accent: '\x1b[38;5;127m', // Darker magenta
|
|
149
|
+
muted: '\x1b[38;5;245m', // Medium gray
|
|
150
|
+
|
|
151
|
+
success: '\x1b[38;5;28m', // Darker green
|
|
152
|
+
warning: '\x1b[38;5;172m', // Darker yellow/orange
|
|
153
|
+
error: '\x1b[38;5;124m', // Darker red
|
|
154
|
+
info: '\x1b[38;5;25m', // Darker blue
|
|
155
|
+
|
|
156
|
+
border: '\x1b[38;5;250m', // Light gray
|
|
157
|
+
background: '\x1b[48;5;255m', // White
|
|
158
|
+
foreground: '\x1b[38;5;235m', // Dark gray
|
|
159
|
+
|
|
160
|
+
selection: '\x1b[48;5;153m', // Light blue background
|
|
161
|
+
focus: '\x1b[48;5;33m', // Blue background
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ============================================================================
|
|
165
|
+
// Theme Creation
|
|
166
|
+
// ============================================================================
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Creates a terminal theme with dark or light mode base colors.
|
|
170
|
+
*
|
|
171
|
+
* Provides sensible defaults for all color tokens based on the selected mode,
|
|
172
|
+
* with optional overrides for individual colors.
|
|
173
|
+
*
|
|
174
|
+
* @param options - Theme creation options
|
|
175
|
+
* @returns A complete terminal theme object
|
|
176
|
+
* @throws {ZodError} If options are invalid (invalid mode or color format)
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```tsx
|
|
180
|
+
* // Dark theme with defaults
|
|
181
|
+
* const darkTheme = createTerminalTheme({ mode: 'dark' })
|
|
182
|
+
*
|
|
183
|
+
* // Light theme with custom primary color
|
|
184
|
+
* const customTheme = createTerminalTheme({
|
|
185
|
+
* mode: 'light',
|
|
186
|
+
* colors: { primary: '\x1b[38;5;27m' }
|
|
187
|
+
* })
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
export function createTerminalTheme(
|
|
191
|
+
options: CreateTerminalThemeOptions
|
|
192
|
+
): TerminalTheme {
|
|
193
|
+
// Validate input with Zod schema
|
|
194
|
+
CreateTerminalThemeOptionsSchema.parse(options)
|
|
195
|
+
|
|
196
|
+
const mode = options.mode || 'dark'
|
|
197
|
+
const baseColors = mode === 'dark' ? darkThemeColors : lightThemeColors
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
mode,
|
|
201
|
+
colors: {
|
|
202
|
+
...baseColors,
|
|
203
|
+
...options.colors,
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Gets a color value from a theme by its key name.
|
|
210
|
+
*
|
|
211
|
+
* @param theme - The terminal theme to get the color from
|
|
212
|
+
* @param key - The color key (e.g., 'primary', 'error')
|
|
213
|
+
* @param fallback - Optional fallback if the key is not found
|
|
214
|
+
* @returns The ANSI color escape sequence
|
|
215
|
+
* @throws If key is not found and no fallback is provided
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```tsx
|
|
219
|
+
* const theme = createTerminalTheme({ mode: 'dark' })
|
|
220
|
+
* const errorColor = getThemeColor(theme, 'error')
|
|
221
|
+
* console.log(`${errorColor}Error message${ANSI.reset}`)
|
|
222
|
+
* ```
|
|
223
|
+
*/
|
|
224
|
+
export function getThemeColor(
|
|
225
|
+
theme: TerminalTheme,
|
|
226
|
+
key: keyof TerminalThemeColors,
|
|
227
|
+
fallback?: string
|
|
228
|
+
): string {
|
|
229
|
+
const color = theme.colors[key]
|
|
230
|
+
if (color !== undefined) {
|
|
231
|
+
return color
|
|
232
|
+
}
|
|
233
|
+
if (fallback !== undefined) {
|
|
234
|
+
return fallback
|
|
235
|
+
}
|
|
236
|
+
throw new Error(`Unknown theme color key: ${key}`)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ============================================================================
|
|
240
|
+
// CSS Variable Mapping
|
|
241
|
+
// ============================================================================
|
|
242
|
+
|
|
243
|
+
const CSS_VAR_TO_THEME_KEY: Record<string, keyof TerminalThemeColors> = {
|
|
244
|
+
'--primary': 'primary',
|
|
245
|
+
'--secondary': 'secondary',
|
|
246
|
+
'--accent': 'accent',
|
|
247
|
+
'--muted': 'muted',
|
|
248
|
+
'--muted-foreground': 'muted',
|
|
249
|
+
'--success': 'success',
|
|
250
|
+
'--warning': 'warning',
|
|
251
|
+
'--error': 'error',
|
|
252
|
+
'--destructive': 'error',
|
|
253
|
+
'--info': 'info',
|
|
254
|
+
'--border': 'border',
|
|
255
|
+
'--background': 'background',
|
|
256
|
+
'--foreground': 'foreground',
|
|
257
|
+
'--selection': 'selection',
|
|
258
|
+
'--focus': 'focus',
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Maps a CSS custom property name to its ANSI theme color.
|
|
263
|
+
*
|
|
264
|
+
* Useful for converting web-based CSS variable references to terminal colors.
|
|
265
|
+
*
|
|
266
|
+
* @param cssVar - CSS variable name (e.g., '--primary', '--error')
|
|
267
|
+
* @param theme - The terminal theme to look up colors from
|
|
268
|
+
* @returns ANSI escape sequence, or empty string if not found
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```tsx
|
|
272
|
+
* const theme = createTerminalTheme({ mode: 'dark' })
|
|
273
|
+
* const primary = cssVarToAnsi('--primary', theme)
|
|
274
|
+
* const error = cssVarToAnsi('--destructive', theme) // maps to error
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
export function cssVarToAnsi(cssVar: string, theme: TerminalTheme): string {
|
|
278
|
+
const key = CSS_VAR_TO_THEME_KEY[cssVar]
|
|
279
|
+
if (key && theme.colors[key]) {
|
|
280
|
+
return theme.colors[key]
|
|
281
|
+
}
|
|
282
|
+
return ''
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ============================================================================
|
|
286
|
+
// Theme-Aware Styling
|
|
287
|
+
// ============================================================================
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Applies a theme color to text with automatic reset.
|
|
291
|
+
*
|
|
292
|
+
* A convenience function that wraps text with a theme color and
|
|
293
|
+
* appends the reset code.
|
|
294
|
+
*
|
|
295
|
+
* @param text - The text to style
|
|
296
|
+
* @param theme - The terminal theme to use
|
|
297
|
+
* @param styleKey - The theme color key to apply
|
|
298
|
+
* @returns Styled text with ANSI codes
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* ```tsx
|
|
302
|
+
* const theme = createTerminalTheme({ mode: 'dark' })
|
|
303
|
+
* console.log(applyThemeStyles('Error!', theme, 'error'))
|
|
304
|
+
* console.log(applyThemeStyles('Success!', theme, 'success'))
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
export function applyThemeStyles(
|
|
308
|
+
text: string,
|
|
309
|
+
theme: TerminalTheme,
|
|
310
|
+
styleKey: keyof TerminalThemeColors
|
|
311
|
+
): string {
|
|
312
|
+
const color = theme.colors[styleKey]
|
|
313
|
+
if (!color) {
|
|
314
|
+
return text
|
|
315
|
+
}
|
|
316
|
+
return `${color}${text}${ANSI.reset}`
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ============================================================================
|
|
320
|
+
// Legacy Theme Presets
|
|
321
|
+
// ============================================================================
|
|
322
|
+
|
|
323
|
+
export const defaultTheme: LegacyTerminalThemeWithExtras = {
|
|
324
|
+
primary: ANSI.cyan,
|
|
325
|
+
secondary: ANSI.blue,
|
|
326
|
+
accent: ANSI.magenta,
|
|
327
|
+
muted: ANSI.brightBlack,
|
|
328
|
+
|
|
329
|
+
success: ANSI.green,
|
|
330
|
+
warning: ANSI.yellow,
|
|
331
|
+
error: ANSI.red,
|
|
332
|
+
info: ANSI.blue,
|
|
333
|
+
|
|
334
|
+
border: ANSI.brightBlack,
|
|
335
|
+
background: '',
|
|
336
|
+
foreground: ANSI.white,
|
|
337
|
+
|
|
338
|
+
selection: ANSI.bgBlue,
|
|
339
|
+
focus: ANSI.bgCyan,
|
|
340
|
+
|
|
341
|
+
typography: {
|
|
342
|
+
headingWeight: 'bold',
|
|
343
|
+
bodyWeight: 'normal',
|
|
344
|
+
codeFont: 'monospace',
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
spacing: {
|
|
348
|
+
xs: 1,
|
|
349
|
+
sm: 2,
|
|
350
|
+
md: 4,
|
|
351
|
+
lg: 8,
|
|
352
|
+
xl: 16,
|
|
353
|
+
},
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Named color constants matching theme semantic colors.
|
|
358
|
+
*/
|
|
359
|
+
export const colors = {
|
|
360
|
+
primary: ANSI.cyan,
|
|
361
|
+
secondary: ANSI.blue,
|
|
362
|
+
accent: ANSI.magenta,
|
|
363
|
+
muted: ANSI.brightBlack,
|
|
364
|
+
success: ANSI.green,
|
|
365
|
+
warning: ANSI.yellow,
|
|
366
|
+
error: ANSI.red,
|
|
367
|
+
info: ANSI.blue,
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Theme tokens for semantic access.
|
|
372
|
+
*/
|
|
373
|
+
export const themeTokens = {
|
|
374
|
+
primary: 'primary',
|
|
375
|
+
secondary: 'secondary',
|
|
376
|
+
accent: 'accent',
|
|
377
|
+
muted: 'muted',
|
|
378
|
+
success: 'success',
|
|
379
|
+
warning: 'warning',
|
|
380
|
+
error: 'error',
|
|
381
|
+
info: 'info',
|
|
382
|
+
} as const
|
|
383
|
+
|
|
384
|
+
export const darkTheme: LegacyTerminalThemeWithExtras = {
|
|
385
|
+
...defaultTheme,
|
|
386
|
+
background: ANSI.bgBlack,
|
|
387
|
+
foreground: ANSI.brightWhite,
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export const lightTheme: LegacyTerminalThemeWithExtras = {
|
|
391
|
+
...defaultTheme,
|
|
392
|
+
background: ANSI.bgWhite,
|
|
393
|
+
foreground: ANSI.black,
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export const highContrastTheme: LegacyTerminalThemeWithExtras = {
|
|
397
|
+
...defaultTheme,
|
|
398
|
+
foreground: ANSI.brightWhite,
|
|
399
|
+
background: ANSI.bgBlack,
|
|
400
|
+
primary: ANSI.brightCyan,
|
|
401
|
+
secondary: ANSI.brightBlue,
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export const themePresets = {
|
|
405
|
+
default: defaultTheme,
|
|
406
|
+
dark: darkTheme,
|
|
407
|
+
light: lightTheme,
|
|
408
|
+
highContrast: highContrastTheme,
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// ============================================================================
|
|
412
|
+
// Theme Creation & Composition Utilities
|
|
413
|
+
// ============================================================================
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Deep merge two objects
|
|
417
|
+
*/
|
|
418
|
+
function deepMerge<T extends Record<string, unknown>>(target: T, source: Partial<T>): T {
|
|
419
|
+
const result = { ...target }
|
|
420
|
+
|
|
421
|
+
for (const key of Object.keys(source) as (keyof T)[]) {
|
|
422
|
+
const sourceValue = source[key]
|
|
423
|
+
const targetValue = target[key]
|
|
424
|
+
|
|
425
|
+
if (sourceValue !== undefined) {
|
|
426
|
+
if (
|
|
427
|
+
typeof sourceValue === 'object' &&
|
|
428
|
+
sourceValue !== null &&
|
|
429
|
+
!Array.isArray(sourceValue) &&
|
|
430
|
+
typeof targetValue === 'object' &&
|
|
431
|
+
targetValue !== null &&
|
|
432
|
+
!Array.isArray(targetValue)
|
|
433
|
+
) {
|
|
434
|
+
result[key] = deepMerge(
|
|
435
|
+
targetValue as Record<string, unknown>,
|
|
436
|
+
sourceValue as Record<string, unknown>
|
|
437
|
+
) as T[keyof T]
|
|
438
|
+
} else {
|
|
439
|
+
result[key] = sourceValue as T[keyof T]
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return result
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Creates a legacy theme with partial overrides from defaults.
|
|
449
|
+
*
|
|
450
|
+
* @deprecated Use {@link createTerminalTheme} for new code.
|
|
451
|
+
*
|
|
452
|
+
* @param partial - Partial theme overrides
|
|
453
|
+
* @returns Complete legacy theme with overrides applied
|
|
454
|
+
* @throws {ZodError} If any color value is not a valid ANSI escape sequence
|
|
455
|
+
*
|
|
456
|
+
* @example
|
|
457
|
+
* ```tsx
|
|
458
|
+
* const theme = createTheme({
|
|
459
|
+
* primary: ANSI.brightGreen,
|
|
460
|
+
* error: ANSI.brightRed,
|
|
461
|
+
* })
|
|
462
|
+
* ```
|
|
463
|
+
*/
|
|
464
|
+
export function createTheme(partial: Partial<LegacyTerminalThemeWithExtras>): LegacyTerminalThemeWithExtras {
|
|
465
|
+
// Validate input with Zod schema
|
|
466
|
+
CreateThemeInputSchema.parse(partial)
|
|
467
|
+
|
|
468
|
+
return deepMerge(defaultTheme as unknown as Record<string, unknown>, partial as unknown as Record<string, unknown>) as unknown as LegacyTerminalThemeWithExtras
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Extends a base theme with overrides.
|
|
473
|
+
*
|
|
474
|
+
* Performs a deep merge of the override values onto the base theme,
|
|
475
|
+
* allowing nested properties (like typography, spacing) to be
|
|
476
|
+
* partially overridden.
|
|
477
|
+
*
|
|
478
|
+
* @param base - The base theme to extend
|
|
479
|
+
* @param overrides - Values to override in the base theme
|
|
480
|
+
* @returns New theme with overrides applied
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```tsx
|
|
484
|
+
* const customTheme = extendTheme(defaultTheme, {
|
|
485
|
+
* primary: ANSI.brightMagenta,
|
|
486
|
+
* typography: { headingWeight: 'bold' },
|
|
487
|
+
* })
|
|
488
|
+
* ```
|
|
489
|
+
*/
|
|
490
|
+
export function extendTheme<T extends LegacyTerminalThemeWithExtras>(
|
|
491
|
+
base: T,
|
|
492
|
+
overrides: Partial<T> & Record<string, unknown>
|
|
493
|
+
): T & Record<string, unknown> {
|
|
494
|
+
return deepMerge(base as unknown as Record<string, unknown>, overrides as unknown as Record<string, unknown>) as unknown as T & Record<string, unknown>
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Composes multiple theme objects into one.
|
|
499
|
+
*
|
|
500
|
+
* Later themes in the argument list override values from earlier themes.
|
|
501
|
+
* Useful for building up themes from multiple partial configurations.
|
|
502
|
+
*
|
|
503
|
+
* @param themes - Theme objects to compose (later overrides earlier)
|
|
504
|
+
* @returns Merged theme object
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* ```tsx
|
|
508
|
+
* const theme = composeThemes(
|
|
509
|
+
* baseTheme,
|
|
510
|
+
* brandColorOverrides,
|
|
511
|
+
* userPreferences
|
|
512
|
+
* )
|
|
513
|
+
* ```
|
|
514
|
+
*/
|
|
515
|
+
export function composeThemes<T extends Record<string, unknown>>(...themes: T[]): T {
|
|
516
|
+
return themes.reduce((acc, theme) => deepMerge(acc, theme), {} as T)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Creates a named theme variant from a base theme.
|
|
521
|
+
*
|
|
522
|
+
* Convenience function for creating theme variants. The name parameter
|
|
523
|
+
* is currently unused but reserved for future theme registry features.
|
|
524
|
+
*
|
|
525
|
+
* @param base - The base theme to start from
|
|
526
|
+
* @param _name - Name for the variant (reserved for future use)
|
|
527
|
+
* @param overrides - Values to override in the base theme
|
|
528
|
+
* @returns New theme variant
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* ```tsx
|
|
532
|
+
* const warningTheme = createThemeVariant(defaultTheme, 'warning', {
|
|
533
|
+
* primary: ANSI.yellow,
|
|
534
|
+
* accent: ANSI.brightYellow,
|
|
535
|
+
* })
|
|
536
|
+
* ```
|
|
537
|
+
*/
|
|
538
|
+
export function createThemeVariant(
|
|
539
|
+
base: LegacyTerminalThemeWithExtras,
|
|
540
|
+
_name: string,
|
|
541
|
+
overrides: Partial<LegacyTerminalThemeWithExtras>
|
|
542
|
+
): LegacyTerminalThemeWithExtras {
|
|
543
|
+
return extendTheme(base, overrides)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ============================================================================
|
|
547
|
+
// Color Scheme Detection
|
|
548
|
+
// ============================================================================
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Detect system color scheme preference (dark/light)
|
|
552
|
+
*/
|
|
553
|
+
export function detectColorScheme(): 'dark' | 'light' {
|
|
554
|
+
// Most terminals are dark by default
|
|
555
|
+
if (typeof process !== 'undefined') {
|
|
556
|
+
const colorBg = process.env?.COLORFGBG
|
|
557
|
+
if (colorBg) {
|
|
558
|
+
const parts = colorBg.split(';')
|
|
559
|
+
if (parts.length >= 2) {
|
|
560
|
+
const bg = parseInt(parts[1], 10)
|
|
561
|
+
if (bg > 7 && bg < 16) {
|
|
562
|
+
return 'light'
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return 'dark'
|
|
568
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mdxui/terminal Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Branded types and type utilities for type-safe terminal components.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Branded Types
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Brand symbol for FocusId type.
|
|
15
|
+
* Used to create a nominal/branded type that prevents accidental string usage.
|
|
16
|
+
*/
|
|
17
|
+
declare const FocusIdBrand: unique symbol
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Branded type for focus element IDs.
|
|
21
|
+
*
|
|
22
|
+
* This is a nominal/branded type that appears as a string at runtime but
|
|
23
|
+
* provides compile-time type safety. You cannot accidentally pass an
|
|
24
|
+
* arbitrary string where a FocusId is expected.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* import { createFocusId, type FocusId } from '@mdxui/terminal'
|
|
29
|
+
*
|
|
30
|
+
* // Correct - use createFocusId to get a valid FocusId
|
|
31
|
+
* const id: FocusId = createFocusId('my-element')
|
|
32
|
+
*
|
|
33
|
+
* // Error - cannot assign plain string to FocusId
|
|
34
|
+
* const badId: FocusId = 'some-string' // TypeScript error!
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export type FocusId = string & { readonly [FocusIdBrand]: true }
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Counter for generating unique focus IDs.
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
let focusIdCounter = 0
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates a branded FocusId from an optional string.
|
|
47
|
+
*
|
|
48
|
+
* If no id is provided, generates a unique id like `focus-1`, `focus-2`, etc.
|
|
49
|
+
* The returned value is typed as FocusId, which provides compile-time
|
|
50
|
+
* safety against accidentally using wrong strings.
|
|
51
|
+
*
|
|
52
|
+
* @param id - Optional custom ID string. If not provided, auto-generates one.
|
|
53
|
+
* @returns A branded FocusId that can be used with focus management APIs.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* import { createFocusId } from '@mdxui/terminal'
|
|
58
|
+
*
|
|
59
|
+
* // Auto-generate an ID
|
|
60
|
+
* const autoId = createFocusId() // 'focus-1', 'focus-2', etc.
|
|
61
|
+
*
|
|
62
|
+
* // Use a custom ID
|
|
63
|
+
* const customId = createFocusId('submit-button')
|
|
64
|
+
*
|
|
65
|
+
* // Use with useFocus
|
|
66
|
+
* const focusable = useFocus({ id: 'my-element' })
|
|
67
|
+
* console.log(focusable.id) // FocusId typed
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function createFocusId(id?: string): FocusId {
|
|
71
|
+
return (id ?? `focus-${++focusIdCounter}`) as FocusId
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Type guard to check if a value is a valid FocusId.
|
|
76
|
+
*
|
|
77
|
+
* At runtime, FocusId is just a string, so this check validates that
|
|
78
|
+
* the value is a non-empty string. For compile-time safety, use the
|
|
79
|
+
* branded type system instead.
|
|
80
|
+
*
|
|
81
|
+
* @param value - Value to check
|
|
82
|
+
* @returns True if value could be a valid FocusId
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```tsx
|
|
86
|
+
* const maybeId: unknown = getUserInput()
|
|
87
|
+
* if (isFocusId(maybeId)) {
|
|
88
|
+
* // maybeId is now typed as FocusId
|
|
89
|
+
* focusManager.focusById(maybeId)
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export function isFocusId(value: unknown): value is FocusId {
|
|
94
|
+
return typeof value === 'string' && value.length > 0
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resets the focus ID counter. For testing purposes only.
|
|
99
|
+
* @internal
|
|
100
|
+
*/
|
|
101
|
+
export function __resetFocusIdCounter(): void {
|
|
102
|
+
focusIdCounter = 0
|
|
103
|
+
}
|