@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.
Files changed (191) hide show
  1. package/README.md +571 -0
  2. package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
  3. package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
  4. package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
  5. package/dist/chunk-3EFDH7PK.js +5235 -0
  6. package/dist/chunk-3RG5ZIWI.js +10 -0
  7. package/dist/chunk-3X5IR6WE.js +884 -0
  8. package/dist/chunk-4FV5ZDCE.js +5236 -0
  9. package/dist/chunk-4OVMSF2J.js +243 -0
  10. package/dist/chunk-63FEETIS.js +4048 -0
  11. package/dist/chunk-B43KP7XJ.js +884 -0
  12. package/dist/chunk-BMTJXWUV.js +655 -0
  13. package/dist/chunk-C3SVH4N7.js +882 -0
  14. package/dist/chunk-EVWR7Y47.js +874 -0
  15. package/dist/chunk-F6A5VWUC.js +1285 -0
  16. package/dist/chunk-FD7KW7GE.js +882 -0
  17. package/dist/chunk-GBQ6UD6I.js +655 -0
  18. package/dist/chunk-GMDD3M6U.js +5227 -0
  19. package/dist/chunk-JBHRXOXM.js +1058 -0
  20. package/dist/chunk-JFOO3EYO.js +1182 -0
  21. package/dist/chunk-JQ5H3WXL.js +1291 -0
  22. package/dist/chunk-JQD5NASE.js +234 -0
  23. package/dist/chunk-KRHJP5R7.js +592 -0
  24. package/dist/chunk-KWF6WVJE.js +962 -0
  25. package/dist/chunk-LHYQVN3H.js +1038 -0
  26. package/dist/chunk-M3TLQLGC.js +1032 -0
  27. package/dist/chunk-MVW4Q5OP.js +240 -0
  28. package/dist/chunk-NXCZSWLU.js +1294 -0
  29. package/dist/chunk-O25TNRO6.js +607 -0
  30. package/dist/chunk-PNECDA2I.js +884 -0
  31. package/dist/chunk-QIHWRLJR.js +962 -0
  32. package/dist/chunk-QW5YMQ7K.js +882 -0
  33. package/dist/chunk-R5U7XKVJ.js +16 -0
  34. package/dist/chunk-RP2MVQLR.js +962 -0
  35. package/dist/chunk-TP6RXGXA.js +1087 -0
  36. package/dist/chunk-TQQSTITZ.js +655 -0
  37. package/dist/chunk-X24GWXQV.js +1281 -0
  38. package/dist/components/index.d.ts +802 -0
  39. package/dist/components/index.js +149 -0
  40. package/dist/data/index.d.ts +2554 -0
  41. package/dist/data/index.js +51 -0
  42. package/dist/forms/index.d.ts +1596 -0
  43. package/dist/forms/index.js +464 -0
  44. package/dist/index-CQRFZntR.d.ts +867 -0
  45. package/dist/index.d.ts +579 -0
  46. package/dist/index.js +786 -0
  47. package/dist/interactive-D0JkWosD.d.ts +217 -0
  48. package/dist/keyboard/index.d.ts +2 -0
  49. package/dist/keyboard/index.js +43 -0
  50. package/dist/renderers/index.d.ts +546 -0
  51. package/dist/renderers/index.js +2157 -0
  52. package/dist/storybook/index.d.ts +396 -0
  53. package/dist/storybook/index.js +641 -0
  54. package/dist/theme/index.d.ts +1339 -0
  55. package/dist/theme/index.js +123 -0
  56. package/dist/types-Bxu5PAgA.d.ts +710 -0
  57. package/dist/types-CIlop5Ji.d.ts +701 -0
  58. package/dist/types-Ca8p_p5X.d.ts +710 -0
  59. package/package.json +90 -0
  60. package/src/__tests__/components/data/card.test.ts +458 -0
  61. package/src/__tests__/components/data/list.test.ts +473 -0
  62. package/src/__tests__/components/data/metrics.test.ts +541 -0
  63. package/src/__tests__/components/data/table.test.ts +448 -0
  64. package/src/__tests__/components/input/field.test.ts +555 -0
  65. package/src/__tests__/components/input/form.test.ts +870 -0
  66. package/src/__tests__/components/input/search.test.ts +1238 -0
  67. package/src/__tests__/components/input/select.test.ts +658 -0
  68. package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
  69. package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
  70. package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
  71. package/src/__tests__/components/navigation/tabs.test.ts +995 -0
  72. package/src/__tests__/components.test.tsx +1197 -0
  73. package/src/__tests__/core/compiler.test.ts +986 -0
  74. package/src/__tests__/core/parser.test.ts +785 -0
  75. package/src/__tests__/core/tier-switcher.test.ts +1103 -0
  76. package/src/__tests__/core/types.test.ts +1398 -0
  77. package/src/__tests__/data/collections.test.ts +1337 -0
  78. package/src/__tests__/data/db.test.ts +1265 -0
  79. package/src/__tests__/data/reactive.test.ts +1010 -0
  80. package/src/__tests__/data/sync.test.ts +1614 -0
  81. package/src/__tests__/errors.test.ts +660 -0
  82. package/src/__tests__/forms/integration.test.ts +444 -0
  83. package/src/__tests__/integration.test.ts +905 -0
  84. package/src/__tests__/keyboard.test.ts +1791 -0
  85. package/src/__tests__/renderer.test.ts +489 -0
  86. package/src/__tests__/renderers/ansi-css.test.ts +948 -0
  87. package/src/__tests__/renderers/ansi.test.ts +1366 -0
  88. package/src/__tests__/renderers/ascii.test.ts +1360 -0
  89. package/src/__tests__/renderers/interactive.test.ts +2353 -0
  90. package/src/__tests__/renderers/markdown.test.ts +1483 -0
  91. package/src/__tests__/renderers/text.test.ts +1369 -0
  92. package/src/__tests__/renderers/unicode.test.ts +1307 -0
  93. package/src/__tests__/theme.test.ts +639 -0
  94. package/src/__tests__/utils/assertions.ts +685 -0
  95. package/src/__tests__/utils/index.ts +115 -0
  96. package/src/__tests__/utils/test-renderer.ts +381 -0
  97. package/src/__tests__/utils/utils.test.ts +560 -0
  98. package/src/components/containers/card.ts +56 -0
  99. package/src/components/containers/dialog.ts +53 -0
  100. package/src/components/containers/index.ts +9 -0
  101. package/src/components/containers/panel.ts +59 -0
  102. package/src/components/feedback/badge.ts +40 -0
  103. package/src/components/feedback/index.ts +8 -0
  104. package/src/components/feedback/spinner.ts +23 -0
  105. package/src/components/helpers.ts +81 -0
  106. package/src/components/index.ts +153 -0
  107. package/src/components/layout/breadcrumb.ts +31 -0
  108. package/src/components/layout/index.ts +10 -0
  109. package/src/components/layout/list.ts +29 -0
  110. package/src/components/layout/sidebar.ts +79 -0
  111. package/src/components/layout/table.ts +62 -0
  112. package/src/components/primitives/box.ts +95 -0
  113. package/src/components/primitives/button.ts +54 -0
  114. package/src/components/primitives/index.ts +11 -0
  115. package/src/components/primitives/input.ts +88 -0
  116. package/src/components/primitives/select.ts +97 -0
  117. package/src/components/primitives/text.ts +60 -0
  118. package/src/components/render.ts +155 -0
  119. package/src/components/templates/app.ts +43 -0
  120. package/src/components/templates/index.ts +8 -0
  121. package/src/components/templates/site.ts +54 -0
  122. package/src/components/types.ts +777 -0
  123. package/src/core/compiler.ts +718 -0
  124. package/src/core/parser.ts +127 -0
  125. package/src/core/tier-switcher.ts +607 -0
  126. package/src/core/types.ts +672 -0
  127. package/src/data/collection.ts +316 -0
  128. package/src/data/collections.ts +50 -0
  129. package/src/data/context.tsx +174 -0
  130. package/src/data/db.ts +127 -0
  131. package/src/data/hooks.ts +532 -0
  132. package/src/data/index.ts +138 -0
  133. package/src/data/reactive.ts +1225 -0
  134. package/src/data/saas-collections.ts +375 -0
  135. package/src/data/sync.ts +1213 -0
  136. package/src/data/types.ts +660 -0
  137. package/src/forms/converters.ts +512 -0
  138. package/src/forms/index.ts +133 -0
  139. package/src/forms/schemas.ts +403 -0
  140. package/src/forms/types.ts +476 -0
  141. package/src/index.ts +542 -0
  142. package/src/keyboard/focus.ts +748 -0
  143. package/src/keyboard/index.ts +96 -0
  144. package/src/keyboard/integration.ts +371 -0
  145. package/src/keyboard/manager.ts +377 -0
  146. package/src/keyboard/presets.ts +90 -0
  147. package/src/renderers/ansi-css.ts +576 -0
  148. package/src/renderers/ansi.ts +802 -0
  149. package/src/renderers/ascii.ts +680 -0
  150. package/src/renderers/breadcrumb.ts +480 -0
  151. package/src/renderers/command-palette.ts +802 -0
  152. package/src/renderers/components/field.ts +210 -0
  153. package/src/renderers/components/form.ts +327 -0
  154. package/src/renderers/components/index.ts +21 -0
  155. package/src/renderers/components/search.ts +449 -0
  156. package/src/renderers/components/select.ts +222 -0
  157. package/src/renderers/index.ts +101 -0
  158. package/src/renderers/interactive/component-handlers.ts +622 -0
  159. package/src/renderers/interactive/cursor-manager.ts +147 -0
  160. package/src/renderers/interactive/focus-manager.ts +279 -0
  161. package/src/renderers/interactive/index.ts +661 -0
  162. package/src/renderers/interactive/input-handler.ts +164 -0
  163. package/src/renderers/interactive/keyboard-handler.ts +212 -0
  164. package/src/renderers/interactive/mouse-handler.ts +167 -0
  165. package/src/renderers/interactive/state-manager.ts +109 -0
  166. package/src/renderers/interactive/types.ts +338 -0
  167. package/src/renderers/interactive-string.ts +299 -0
  168. package/src/renderers/interactive.ts +59 -0
  169. package/src/renderers/markdown.ts +950 -0
  170. package/src/renderers/sidebar.ts +549 -0
  171. package/src/renderers/tabs.ts +682 -0
  172. package/src/renderers/text.ts +791 -0
  173. package/src/renderers/unicode.ts +917 -0
  174. package/src/renderers/utils.ts +942 -0
  175. package/src/router/adapters.ts +383 -0
  176. package/src/router/types.ts +140 -0
  177. package/src/router/utils.ts +452 -0
  178. package/src/schemas.ts +205 -0
  179. package/src/storybook/index.ts +91 -0
  180. package/src/storybook/interactive-decorator.tsx +659 -0
  181. package/src/storybook/keyboard-simulator.ts +501 -0
  182. package/src/theme/ansi-codes.ts +80 -0
  183. package/src/theme/box-drawing.ts +132 -0
  184. package/src/theme/color-convert.ts +254 -0
  185. package/src/theme/color-support.ts +321 -0
  186. package/src/theme/index.ts +134 -0
  187. package/src/theme/strip-ansi.ts +50 -0
  188. package/src/theme/tailwind-map.ts +469 -0
  189. package/src/theme/text-styles.ts +206 -0
  190. package/src/theme/theme-system.ts +568 -0
  191. 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
+ }