@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,254 @@
1
+ /**
2
+ * Color Conversion Utilities
3
+ *
4
+ * Functions for converting between color formats:
5
+ * - RGB to ANSI 256 color codes
6
+ * - Hex to RGB
7
+ * - True color (24-bit) escape sequences
8
+ *
9
+ * All public APIs include runtime validation via Zod schemas.
10
+ *
11
+ * @module
12
+ */
13
+
14
+ import {
15
+ Ansi256CodeSchema,
16
+ HexColorSchema,
17
+ RgbComponentSchema,
18
+ } from '../schemas'
19
+
20
+ // ============================================================================
21
+ // Internal Helpers
22
+ // ============================================================================
23
+
24
+ /**
25
+ * Convert RGB values to the nearest ANSI 256 color code
26
+ */
27
+ function rgbToAnsi256(r: number, g: number, b: number): number {
28
+ // Check for exact black
29
+ if (r === 0 && g === 0 && b === 0) {
30
+ return 0
31
+ }
32
+
33
+ // Check for exact white
34
+ if (r === 255 && g === 255 && b === 255) {
35
+ return 15
36
+ }
37
+
38
+ // Check for grayscale (when r, g, b are close to each other)
39
+ if (Math.abs(r - g) < 10 && Math.abs(g - b) < 10 && Math.abs(r - b) < 10) {
40
+ const avg = (r + g + b) / 3
41
+ if (avg < 8) return 0 // black
42
+ if (avg > 248) return 15 // white
43
+ // Grayscale range: 232-255 (24 shades)
44
+ const grayIndex = Math.round((avg - 8) / 10)
45
+ return Math.min(255, Math.max(232, 232 + grayIndex))
46
+ }
47
+
48
+ // Convert to 6x6x6 color cube (colors 16-231)
49
+ const toColorCubeIndex = (v: number): number => {
50
+ if (v < 48) return 0
51
+ if (v < 115) return 1
52
+ return Math.min(5, Math.floor((v - 35) / 40))
53
+ }
54
+
55
+ const ri = toColorCubeIndex(r)
56
+ const gi = toColorCubeIndex(g)
57
+ const bi = toColorCubeIndex(b)
58
+
59
+ return 16 + 36 * ri + 6 * gi + bi
60
+ }
61
+
62
+ /**
63
+ * Parse hex color to RGB values.
64
+ *
65
+ * Handles edge cases:
66
+ * - Empty string returns black (0, 0, 0)
67
+ * - Invalid hex returns black (0, 0, 0)
68
+ * - Supports with/without # prefix
69
+ * - Supports 3-character shorthand (#fff)
70
+ */
71
+ export function hexToRgb(hex: string): { r: number; g: number; b: number } {
72
+ // Handle empty or null input
73
+ if (!hex || typeof hex !== 'string') {
74
+ return { r: 0, g: 0, b: 0 }
75
+ }
76
+
77
+ // Remove # prefix if present
78
+ hex = hex.replace(/^#/, '')
79
+
80
+ // Handle empty after removing #
81
+ if (hex.length === 0) {
82
+ return { r: 0, g: 0, b: 0 }
83
+ }
84
+
85
+ // Handle shorthand hex (e.g., #fff -> #ffffff)
86
+ if (hex.length === 3) {
87
+ hex = hex
88
+ .split('')
89
+ .map((c) => c + c)
90
+ .join('')
91
+ }
92
+
93
+ // Validate hex string
94
+ if (!/^[0-9a-fA-F]{6}$/.test(hex)) {
95
+ return { r: 0, g: 0, b: 0 }
96
+ }
97
+
98
+ const num = parseInt(hex, 16)
99
+ return {
100
+ r: (num >> 16) & 255,
101
+ g: (num >> 8) & 255,
102
+ b: num & 255,
103
+ }
104
+ }
105
+
106
+ // ============================================================================
107
+ // Public API
108
+ // ============================================================================
109
+
110
+ /**
111
+ * Options for RGB to ANSI conversion.
112
+ */
113
+ export interface RgbToAnsiOptions {
114
+ /** If true, generates background color code; otherwise foreground */
115
+ background?: boolean
116
+ }
117
+
118
+ /**
119
+ * Converts RGB color values to an ANSI 256 escape sequence.
120
+ *
121
+ * @param r - Red component (0-255)
122
+ * @param g - Green component (0-255)
123
+ * @param b - Blue component (0-255)
124
+ * @param opts - Optional settings for background/foreground
125
+ * @returns ANSI escape sequence string
126
+ *
127
+ * @example
128
+ * ```tsx
129
+ * // Coral foreground color
130
+ * console.log(`${rgbToAnsi(255, 127, 80)}Coral text${ANSI.reset}`)
131
+ *
132
+ * // Blue background
133
+ * console.log(`${rgbToAnsi(0, 0, 255, { background: true })}Blue bg${ANSI.reset}`)
134
+ * ```
135
+ */
136
+ export function rgbToAnsi(
137
+ r: number,
138
+ g: number,
139
+ b: number,
140
+ opts?: RgbToAnsiOptions
141
+ ): string {
142
+ const code = rgbToAnsi256(r, g, b)
143
+ const prefix = opts?.background ? '48' : '38'
144
+ return `\x1b[${prefix};5;${code}m`
145
+ }
146
+
147
+ /**
148
+ * Converts a hex color string to an ANSI 256 escape sequence.
149
+ *
150
+ * Supports both 3-character (#RGB) and 6-character (#RRGGBB) hex formats.
151
+ * The '#' prefix is optional.
152
+ *
153
+ * @param hex - Hex color string (e.g., '#ff7f50', 'ff7f50', '#f00')
154
+ * @param opts - Optional settings for background/foreground
155
+ * @returns ANSI escape sequence string
156
+ *
157
+ * @example
158
+ * ```tsx
159
+ * // Tomato red foreground
160
+ * console.log(`${hexToAnsi('#ff6347')}Tomato${ANSI.reset}`)
161
+ *
162
+ * // Short-form hex with background
163
+ * console.log(`${hexToAnsi('#f00', { background: true })}Red bg${ANSI.reset}`)
164
+ * ```
165
+ */
166
+ export function hexToAnsi(hex: string, opts?: RgbToAnsiOptions): string {
167
+ const { r, g, b } = hexToRgb(hex)
168
+ return rgbToAnsi(r, g, b, opts)
169
+ }
170
+
171
+ /**
172
+ * Generate ANSI 256-color foreground escape code.
173
+ *
174
+ * @param n - ANSI 256 color code (0-255)
175
+ * @returns ANSI escape sequence string
176
+ * @throws {ZodError} If n is not a valid ANSI 256 code (0-255)
177
+ */
178
+ export function ansi256(n: number): string {
179
+ Ansi256CodeSchema.parse(n)
180
+ return `\x1b[38;5;${n}m`
181
+ }
182
+
183
+ /**
184
+ * Generate ANSI 256-color background escape code.
185
+ *
186
+ * @param n - ANSI 256 color code (0-255)
187
+ * @returns ANSI escape sequence string
188
+ * @throws {ZodError} If n is not a valid ANSI 256 code (0-255)
189
+ */
190
+ export function ansi256Bg(n: number): string {
191
+ Ansi256CodeSchema.parse(n)
192
+ return `\x1b[48;5;${n}m`
193
+ }
194
+
195
+ /**
196
+ * Generate true color (24-bit) foreground escape code.
197
+ *
198
+ * @param r - Red component (0-255)
199
+ * @param g - Green component (0-255)
200
+ * @param b - Blue component (0-255)
201
+ * @returns ANSI escape sequence string
202
+ * @throws {ZodError} If any component is not in range 0-255
203
+ */
204
+ export function rgb(r: number, g: number, b: number): string {
205
+ RgbComponentSchema.parse(r)
206
+ RgbComponentSchema.parse(g)
207
+ RgbComponentSchema.parse(b)
208
+ return `\x1b[38;2;${r};${g};${b}m`
209
+ }
210
+
211
+ /**
212
+ * Generate true color (24-bit) background escape code.
213
+ *
214
+ * @param r - Red component (0-255)
215
+ * @param g - Green component (0-255)
216
+ * @param b - Blue component (0-255)
217
+ * @returns ANSI escape sequence string
218
+ * @throws {ZodError} If any component is not in range 0-255
219
+ */
220
+ export function rgbBg(r: number, g: number, b: number): string {
221
+ RgbComponentSchema.parse(r)
222
+ RgbComponentSchema.parse(g)
223
+ RgbComponentSchema.parse(b)
224
+ return `\x1b[48;2;${r};${g};${b}m`
225
+ }
226
+
227
+ /**
228
+ * Generate true color foreground from hex color.
229
+ *
230
+ * @param color - Hex color string (#RGB, RGB, #RRGGBB, or RRGGBB)
231
+ * @returns ANSI escape sequence string
232
+ * @throws {ZodError} If color is not a valid hex color format
233
+ */
234
+ export function hex(color: string): string {
235
+ HexColorSchema.parse(color)
236
+ const { r, g, b } = hexToRgb(color)
237
+ return `\x1b[38;2;${r};${g};${b}m`
238
+ }
239
+
240
+ /**
241
+ * Generate true color background from hex color.
242
+ *
243
+ * @param color - Hex color string (#RGB, RGB, #RRGGBB, or RRGGBB)
244
+ * @returns ANSI escape sequence string
245
+ * @throws {ZodError} If color is not a valid hex color format
246
+ */
247
+ export function hexBg(color: string): string {
248
+ HexColorSchema.parse(color)
249
+ const { r, g, b } = hexToRgb(color)
250
+ return `\x1b[48;2;${r};${g};${b}m`
251
+ }
252
+
253
+ // Export the internal helper for use by other modules
254
+ export { rgbToAnsi256 }
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Color Support Detection and Degradation
3
+ *
4
+ * Utilities for detecting terminal color capabilities and
5
+ * gracefully degrading colors for limited terminals.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import { rgbToAnsi256 } from './color-convert'
11
+
12
+ // ============================================================================
13
+ // Type Definitions
14
+ // ============================================================================
15
+
16
+ /**
17
+ * Level of color support in the terminal.
18
+ *
19
+ * - `'none'` - No color support (monochrome)
20
+ * - `'16'` - Basic 16 ANSI colors
21
+ * - `'256'` - Extended 256 color palette
22
+ * - `'truecolor'` - Full 24-bit RGB color support
23
+ */
24
+ export type ColorSupport = 'none' | '16' | '256' | 'truecolor'
25
+
26
+ // ============================================================================
27
+ // Color Support Detection
28
+ // ============================================================================
29
+
30
+ /**
31
+ * Detects the terminal's color support level.
32
+ *
33
+ * Checks environment variables in this order:
34
+ * 1. `NO_COLOR` - Forces no color support
35
+ * 2. `FORCE_COLOR` - Forces a specific color level (0-3)
36
+ * 3. `COLORTERM` - Checks for truecolor support
37
+ * 4. `TERM` - Checks terminal type for color hints
38
+ *
39
+ * @returns The detected color support level
40
+ *
41
+ * @example
42
+ * ```tsx
43
+ * const support = detectColorSupport()
44
+ * if (support === 'truecolor') {
45
+ * // Use full RGB colors
46
+ * } else if (support === '256') {
47
+ * // Use 256 color palette
48
+ * }
49
+ * ```
50
+ */
51
+ export function detectColorSupport(): ColorSupport {
52
+ // NO_COLOR takes precedence
53
+ if (process.env.NO_COLOR) {
54
+ return 'none'
55
+ }
56
+
57
+ // FORCE_COLOR can override
58
+ if (process.env.FORCE_COLOR) {
59
+ const level = parseInt(process.env.FORCE_COLOR, 10)
60
+ if (level === 0) return 'none'
61
+ if (level === 1) return '16'
62
+ if (level === 2) return '256'
63
+ if (level >= 3) return 'truecolor'
64
+ }
65
+
66
+ // Check COLORTERM for truecolor
67
+ const colorTerm = process.env.COLORTERM
68
+ if (colorTerm === 'truecolor' || colorTerm === '24bit') {
69
+ return 'truecolor'
70
+ }
71
+
72
+ // Check TERM for color support
73
+ const term = process.env.TERM || ''
74
+
75
+ if (term === 'dumb') {
76
+ return 'none'
77
+ }
78
+
79
+ if (term.includes('256color')) {
80
+ return '256'
81
+ }
82
+
83
+ if (term.includes('color') || term.includes('xterm') || term.includes('vt100')) {
84
+ return '16'
85
+ }
86
+
87
+ // Default to 256 for most modern terminals
88
+ return '256'
89
+ }
90
+
91
+ // ============================================================================
92
+ // Color Degradation
93
+ // ============================================================================
94
+
95
+ /**
96
+ * Map ANSI 256 color to basic 16 color
97
+ */
98
+ function ansi256To16(code: number): number {
99
+ // Standard colors 0-7 map to 30-37
100
+ if (code < 8) {
101
+ return 30 + code
102
+ }
103
+
104
+ // Bright colors 8-15 map to 90-97
105
+ if (code < 16) {
106
+ return 90 + (code - 8)
107
+ }
108
+
109
+ // Color cube (16-231) and grayscale (232-255)
110
+ // Need to find the closest basic color
111
+
112
+ let r: number, g: number, b: number
113
+
114
+ if (code >= 232) {
115
+ // Grayscale
116
+ const gray = (code - 232) * 10 + 8
117
+ r = g = b = gray
118
+ } else {
119
+ // Color cube
120
+ const cubeIndex = code - 16
121
+ r = Math.floor(cubeIndex / 36) * 51
122
+ g = Math.floor((cubeIndex % 36) / 6) * 51
123
+ b = (cubeIndex % 6) * 51
124
+ }
125
+
126
+ // Find the closest basic color
127
+ // Basic colors: black, red, green, yellow, blue, magenta, cyan, white
128
+ const basicColors = [
129
+ { r: 0, g: 0, b: 0, code: 30 }, // black
130
+ { r: 170, g: 0, b: 0, code: 31 }, // red
131
+ { r: 0, g: 170, b: 0, code: 32 }, // green
132
+ { r: 170, g: 170, b: 0, code: 33 }, // yellow
133
+ { r: 0, g: 0, b: 170, code: 34 }, // blue
134
+ { r: 170, g: 0, b: 170, code: 35 }, // magenta
135
+ { r: 0, g: 170, b: 170, code: 36 }, // cyan
136
+ { r: 170, g: 170, b: 170, code: 37 }, // white
137
+ { r: 85, g: 85, b: 85, code: 90 }, // bright black
138
+ { r: 255, g: 85, b: 85, code: 91 }, // bright red
139
+ { r: 85, g: 255, b: 85, code: 92 }, // bright green
140
+ { r: 255, g: 255, b: 85, code: 93 }, // bright yellow
141
+ { r: 85, g: 85, b: 255, code: 94 }, // bright blue
142
+ { r: 255, g: 85, b: 255, code: 95 }, // bright magenta
143
+ { r: 85, g: 255, b: 255, code: 96 }, // bright cyan
144
+ { r: 255, g: 255, b: 255, code: 97 }, // bright white
145
+ ]
146
+
147
+ let minDist = Infinity
148
+ let closestCode = 37 // default to white
149
+
150
+ for (const bc of basicColors) {
151
+ const dist = Math.sqrt(
152
+ Math.pow(r - bc.r, 2) + Math.pow(g - bc.g, 2) + Math.pow(b - bc.b, 2)
153
+ )
154
+ if (dist < minDist) {
155
+ minDist = dist
156
+ closestCode = bc.code
157
+ }
158
+ }
159
+
160
+ return closestCode
161
+ }
162
+
163
+ /**
164
+ * Parse ANSI escape code to extract type and color code
165
+ */
166
+ function parseAnsiCode(ansi: string): {
167
+ type: 'fg' | 'bg' | 'style'
168
+ format: '16' | '256' | 'truecolor'
169
+ code?: number
170
+ rgb?: { r: number; g: number; b: number }
171
+ } | null {
172
+ // Match ANSI 256 foreground: \x1b[38;5;Nm
173
+ const fg256Match = ansi.match(/\x1b\[38;5;(\d+)m/)
174
+ if (fg256Match) {
175
+ return { type: 'fg', format: '256', code: parseInt(fg256Match[1], 10) }
176
+ }
177
+
178
+ // Match ANSI 256 background: \x1b[48;5;Nm
179
+ const bg256Match = ansi.match(/\x1b\[48;5;(\d+)m/)
180
+ if (bg256Match) {
181
+ return { type: 'bg', format: '256', code: parseInt(bg256Match[1], 10) }
182
+ }
183
+
184
+ // Match truecolor foreground: \x1b[38;2;R;G;Bm
185
+ const fgTrueMatch = ansi.match(/\x1b\[38;2;(\d+);(\d+);(\d+)m/)
186
+ if (fgTrueMatch) {
187
+ return {
188
+ type: 'fg',
189
+ format: 'truecolor',
190
+ rgb: {
191
+ r: parseInt(fgTrueMatch[1], 10),
192
+ g: parseInt(fgTrueMatch[2], 10),
193
+ b: parseInt(fgTrueMatch[3], 10),
194
+ },
195
+ }
196
+ }
197
+
198
+ // Match truecolor background: \x1b[48;2;R;G;Bm
199
+ const bgTrueMatch = ansi.match(/\x1b\[48;2;(\d+);(\d+);(\d+)m/)
200
+ if (bgTrueMatch) {
201
+ return {
202
+ type: 'bg',
203
+ format: 'truecolor',
204
+ rgb: {
205
+ r: parseInt(bgTrueMatch[1], 10),
206
+ g: parseInt(bgTrueMatch[2], 10),
207
+ b: parseInt(bgTrueMatch[3], 10),
208
+ },
209
+ }
210
+ }
211
+
212
+ // Match basic 16 color codes
213
+ const basicMatch = ansi.match(/\x1b\[(\d+)m/)
214
+ if (basicMatch) {
215
+ const code = parseInt(basicMatch[1], 10)
216
+ if (code >= 30 && code <= 37) {
217
+ return { type: 'fg', format: '16', code }
218
+ }
219
+ if (code >= 90 && code <= 97) {
220
+ return { type: 'fg', format: '16', code }
221
+ }
222
+ if (code >= 40 && code <= 47) {
223
+ return { type: 'bg', format: '16', code }
224
+ }
225
+ if (code >= 100 && code <= 107) {
226
+ return { type: 'bg', format: '16', code }
227
+ }
228
+ // Style code
229
+ return { type: 'style', format: '16', code }
230
+ }
231
+
232
+ return null
233
+ }
234
+
235
+ /**
236
+ * Degrades an ANSI color to work with limited terminal color support.
237
+ *
238
+ * Automatically converts colors down to simpler formats:
239
+ * - Truecolor to 256 colors
240
+ * - 256 colors to 16 basic colors
241
+ * - Any color to nothing if support is 'none'
242
+ *
243
+ * @param ansi - Original ANSI escape sequence
244
+ * @param support - Target color support level
245
+ * @returns Degraded ANSI escape sequence, or empty string for 'none'
246
+ *
247
+ * @example
248
+ * ```tsx
249
+ * const trueColorBlue = '\x1b[38;2;100;150;200m'
250
+ * const support = detectColorSupport()
251
+ *
252
+ * // Automatically degrade for the current terminal
253
+ * const blue = degradeColor(trueColorBlue, support)
254
+ * ```
255
+ */
256
+ export function degradeColor(ansi: string, support: ColorSupport): string {
257
+ if (support === 'none') {
258
+ return ''
259
+ }
260
+
261
+ if (support === 'truecolor') {
262
+ // Truecolor supports everything
263
+ return ansi
264
+ }
265
+
266
+ const parsed = parseAnsiCode(ansi)
267
+ if (!parsed) {
268
+ return ansi
269
+ }
270
+
271
+ if (support === '256') {
272
+ // Convert truecolor to 256
273
+ if (parsed.format === 'truecolor' && parsed.rgb) {
274
+ const code = rgbToAnsi256(parsed.rgb.r, parsed.rgb.g, parsed.rgb.b)
275
+ const prefix = parsed.type === 'bg' ? '48' : '38'
276
+ return `\x1b[${prefix};5;${code}m`
277
+ }
278
+ return ansi
279
+ }
280
+
281
+ if (support === '16') {
282
+ // Convert 256 or truecolor to 16
283
+ if (parsed.format === '16') {
284
+ return ansi
285
+ }
286
+
287
+ let code256: number
288
+ if (parsed.format === 'truecolor' && parsed.rgb) {
289
+ code256 = rgbToAnsi256(parsed.rgb.r, parsed.rgb.g, parsed.rgb.b)
290
+ } else if (parsed.code !== undefined) {
291
+ code256 = parsed.code
292
+ } else {
293
+ return ansi
294
+ }
295
+
296
+ const code16 = ansi256To16(code256)
297
+ return `\x1b[${code16}m`
298
+ }
299
+
300
+ return ansi
301
+ }
302
+
303
+ /**
304
+ * Adapts a color based on terminal support level.
305
+ *
306
+ * Alias for {@link degradeColor} - use this when the intent is to
307
+ * "adapt" colors for the current terminal rather than "degrade" them.
308
+ *
309
+ * @param color - ANSI color escape sequence
310
+ * @param level - Target color support level
311
+ * @returns Adapted color escape sequence
312
+ *
313
+ * @example
314
+ * ```tsx
315
+ * const support = detectColorSupport()
316
+ * const color = adaptColor(theme.primary, support)
317
+ * ```
318
+ */
319
+ export function adaptColor(color: string, level: ColorSupport): string {
320
+ return degradeColor(color, level)
321
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * @mdxui/terminal/theme
3
+ *
4
+ * Terminal theming system for mapping web design tokens to ANSI colors.
5
+ *
6
+ * This module provides:
7
+ * - ANSI escape code constants for styling terminal output
8
+ * - Tailwind CSS to ANSI color conversion
9
+ * - Theme creation and composition utilities
10
+ * - Color support detection and degradation
11
+ * - Box drawing character sets for UI borders
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * import { ANSI, styled, createTheme } from '@mdxui/terminal'
16
+ *
17
+ * // Apply styles to text
18
+ * console.log(styled('Hello', ANSI.bold, ANSI.cyan))
19
+ *
20
+ * // Create a custom theme
21
+ * const theme = createTheme({
22
+ * primary: ANSI.brightGreen,
23
+ * error: ANSI.brightRed,
24
+ * })
25
+ * ```
26
+ *
27
+ * @packageDocumentation
28
+ */
29
+
30
+ // ANSI escape code constants
31
+ export { ANSI } from './ansi-codes'
32
+
33
+ // Color conversion utilities
34
+ export {
35
+ hexToRgb,
36
+ rgbToAnsi,
37
+ hexToAnsi,
38
+ ansi256,
39
+ ansi256Bg,
40
+ rgb,
41
+ rgbBg,
42
+ hex,
43
+ hexBg,
44
+ type RgbToAnsiOptions,
45
+ } from './color-convert'
46
+
47
+ // Tailwind to ANSI mapping
48
+ export {
49
+ TAILWIND_COLORS,
50
+ tailwindToAnsi,
51
+ tailwindToTerminal,
52
+ } from './tailwind-map'
53
+
54
+ // Theme system
55
+ export {
56
+ createTerminalTheme,
57
+ getThemeColor,
58
+ cssVarToAnsi,
59
+ applyThemeStyles,
60
+ defaultTheme,
61
+ colors,
62
+ themeTokens,
63
+ darkTheme,
64
+ lightTheme,
65
+ highContrastTheme,
66
+ themePresets,
67
+ createTheme,
68
+ extendTheme,
69
+ composeThemes,
70
+ createThemeVariant,
71
+ detectColorScheme,
72
+ type TerminalThemeColors,
73
+ type TerminalTheme,
74
+ type LegacyTerminalTheme,
75
+ type LegacyTerminalThemeWithExtras,
76
+ type CreateTerminalThemeOptions,
77
+ } from './theme-system'
78
+
79
+ // Text styling utilities
80
+ export {
81
+ fg,
82
+ bg,
83
+ bold,
84
+ dim,
85
+ italic,
86
+ underline,
87
+ strikethrough,
88
+ style,
89
+ styled,
90
+ } from './text-styles'
91
+
92
+ // Color support detection and degradation
93
+ export {
94
+ detectColorSupport,
95
+ degradeColor,
96
+ adaptColor,
97
+ type ColorSupport,
98
+ } from './color-support'
99
+
100
+ // ANSI stripping utilities
101
+ export {
102
+ stripAnsi,
103
+ visibleLength,
104
+ } from './strip-ansi'
105
+
106
+ // Box drawing characters
107
+ export {
108
+ boxChars,
109
+ drawBox,
110
+ type BoxStyle,
111
+ } from './box-drawing'
112
+
113
+ // Zod schemas for runtime validation
114
+ export {
115
+ HexColorSchema,
116
+ RgbComponentSchema,
117
+ RgbColorSchema,
118
+ Ansi256CodeSchema,
119
+ AnsiEscapeSchema,
120
+ ThemeModeSchema,
121
+ TerminalThemeColorsSchema,
122
+ CreateTerminalThemeOptionsSchema,
123
+ LegacyTerminalThemeSchema,
124
+ LegacyTerminalThemeWithExtrasSchema,
125
+ CreateThemeInputSchema,
126
+ RgbToAnsiOptionsSchema,
127
+ ColorSupportSchema,
128
+ type HexColor,
129
+ type RgbColor,
130
+ type Ansi256Code,
131
+ type ThemeMode,
132
+ type CreateTerminalThemeOptionsInput,
133
+ type CreateThemeInput,
134
+ } from '../schemas'