@yeshwanthyk/open-tui 0.1.0 → 0.1.1

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 (132) hide show
  1. package/dist/app.d.ts +18 -0
  2. package/dist/app.d.ts.map +1 -0
  3. package/dist/app.jsx +28 -0
  4. package/dist/app.jsx.map +1 -0
  5. package/dist/autocomplete/autocomplete.d.ts +48 -0
  6. package/dist/autocomplete/autocomplete.d.ts.map +1 -0
  7. package/dist/autocomplete/autocomplete.js +391 -0
  8. package/dist/autocomplete/autocomplete.js.map +1 -0
  9. package/dist/autocomplete/file-index.d.ts +36 -0
  10. package/dist/autocomplete/file-index.d.ts.map +1 -0
  11. package/dist/autocomplete/file-index.js +143 -0
  12. package/dist/autocomplete/file-index.js.map +1 -0
  13. package/dist/autocomplete/index.d.ts +3 -0
  14. package/dist/autocomplete/index.d.ts.map +1 -0
  15. package/dist/autocomplete/index.js +3 -0
  16. package/dist/autocomplete/index.js.map +1 -0
  17. package/dist/components/badge.d.ts +11 -0
  18. package/dist/components/badge.d.ts.map +1 -0
  19. package/dist/components/badge.jsx +31 -0
  20. package/dist/components/badge.jsx.map +1 -0
  21. package/dist/components/code-block.d.ts +12 -0
  22. package/dist/components/code-block.d.ts.map +1 -0
  23. package/dist/components/code-block.jsx +34 -0
  24. package/dist/components/code-block.jsx.map +1 -0
  25. package/dist/components/dialog.d.ts +12 -0
  26. package/dist/components/dialog.d.ts.map +1 -0
  27. package/dist/components/dialog.jsx +29 -0
  28. package/dist/components/dialog.jsx.map +1 -0
  29. package/dist/components/diff.d.ts +11 -0
  30. package/dist/components/diff.d.ts.map +1 -0
  31. package/dist/components/diff.jsx +17 -0
  32. package/dist/components/diff.jsx.map +1 -0
  33. package/dist/components/divider.d.ts +9 -0
  34. package/dist/components/divider.d.ts.map +1 -0
  35. package/dist/components/divider.jsx +13 -0
  36. package/dist/components/divider.jsx.map +1 -0
  37. package/dist/components/editor.d.ts +87 -0
  38. package/dist/components/editor.d.ts.map +1 -0
  39. package/dist/components/editor.jsx +146 -0
  40. package/dist/components/editor.jsx.map +1 -0
  41. package/dist/components/image.d.ts +65 -0
  42. package/dist/components/image.d.ts.map +1 -0
  43. package/dist/components/image.jsx +319 -0
  44. package/dist/components/image.jsx.map +1 -0
  45. package/dist/components/loader.d.ts +25 -0
  46. package/dist/components/loader.d.ts.map +1 -0
  47. package/dist/components/loader.jsx +31 -0
  48. package/dist/components/loader.jsx.map +1 -0
  49. package/dist/components/markdown.d.ts +28 -0
  50. package/dist/components/markdown.d.ts.map +1 -0
  51. package/dist/components/markdown.jsx +30 -0
  52. package/dist/components/markdown.jsx.map +1 -0
  53. package/dist/components/panel.d.ts +12 -0
  54. package/dist/components/panel.d.ts.map +1 -0
  55. package/dist/components/panel.jsx +51 -0
  56. package/dist/components/panel.jsx.map +1 -0
  57. package/dist/components/select-list.d.ts +55 -0
  58. package/dist/components/select-list.d.ts.map +1 -0
  59. package/dist/components/select-list.jsx +135 -0
  60. package/dist/components/select-list.jsx.map +1 -0
  61. package/dist/components/spacer.d.ts +25 -0
  62. package/dist/components/spacer.d.ts.map +1 -0
  63. package/dist/components/spacer.jsx +27 -0
  64. package/dist/components/spacer.jsx.map +1 -0
  65. package/dist/components/toast.d.ts +19 -0
  66. package/dist/components/toast.d.ts.map +1 -0
  67. package/dist/components/toast.jsx +52 -0
  68. package/dist/components/toast.jsx.map +1 -0
  69. package/dist/context/terminal.d.ts +7 -0
  70. package/dist/context/terminal.d.ts.map +1 -0
  71. package/dist/context/terminal.jsx +6 -0
  72. package/dist/context/terminal.jsx.map +1 -0
  73. package/dist/context/theme.d.ts +117 -0
  74. package/dist/context/theme.d.ts.map +1 -0
  75. package/dist/context/theme.jsx +650 -0
  76. package/dist/context/theme.jsx.map +1 -0
  77. package/dist/hooks/use-keyboard.d.ts +7 -0
  78. package/dist/hooks/use-keyboard.d.ts.map +1 -0
  79. package/dist/hooks/use-keyboard.js +6 -0
  80. package/dist/hooks/use-keyboard.js.map +1 -0
  81. package/dist/index.d.ts +31 -0
  82. package/dist/index.d.ts.map +1 -0
  83. package/dist/index.js +45 -0
  84. package/dist/index.js.map +1 -0
  85. package/dist/opentui-augmentations.d.ts +9 -0
  86. package/dist/opentui-augmentations.d.ts.map +1 -0
  87. package/dist/opentui-augmentations.js +1 -0
  88. package/dist/opentui-augmentations.js.map +1 -0
  89. package/dist/parsers-config.d.ts +16 -0
  90. package/dist/parsers-config.d.ts.map +1 -0
  91. package/dist/parsers-config.js +119 -0
  92. package/dist/parsers-config.js.map +1 -0
  93. package/dist/themes/aura.json +69 -0
  94. package/dist/themes/ayu.json +80 -0
  95. package/dist/themes/catppuccin-macchiato.json +233 -0
  96. package/dist/themes/catppuccin.json +112 -0
  97. package/dist/themes/cobalt2.json +228 -0
  98. package/dist/themes/dracula.json +219 -0
  99. package/dist/themes/everforest.json +241 -0
  100. package/dist/themes/flexoki.json +237 -0
  101. package/dist/themes/github.json +233 -0
  102. package/dist/themes/gruvbox.json +95 -0
  103. package/dist/themes/kanagawa.json +77 -0
  104. package/dist/themes/lucent-orng.json +227 -0
  105. package/dist/themes/marvin.json +97 -0
  106. package/dist/themes/material.json +235 -0
  107. package/dist/themes/matrix.json +77 -0
  108. package/dist/themes/mercury.json +245 -0
  109. package/dist/themes/monokai.json +221 -0
  110. package/dist/themes/nightowl.json +221 -0
  111. package/dist/themes/nord.json +223 -0
  112. package/dist/themes/one-dark.json +84 -0
  113. package/dist/themes/opencode.json +245 -0
  114. package/dist/themes/orng.json +245 -0
  115. package/dist/themes/palenight.json +222 -0
  116. package/dist/themes/rosepine.json +234 -0
  117. package/dist/themes/solarized.json +223 -0
  118. package/dist/themes/synthwave84.json +226 -0
  119. package/dist/themes/tokyonight.json +243 -0
  120. package/dist/themes/vercel.json +245 -0
  121. package/dist/themes/vesper.json +218 -0
  122. package/dist/themes/zenburn.json +223 -0
  123. package/dist/utils/clipboard.d.ts +12 -0
  124. package/dist/utils/clipboard.d.ts.map +1 -0
  125. package/dist/utils/clipboard.js +52 -0
  126. package/dist/utils/clipboard.js.map +1 -0
  127. package/dist/utils/text-width.d.ts +26 -0
  128. package/dist/utils/text-width.d.ts.map +1 -0
  129. package/dist/utils/text-width.js +101 -0
  130. package/dist/utils/text-width.js.map +1 -0
  131. package/package.json +13 -4
  132. package/src/index.ts +0 -121
@@ -0,0 +1,650 @@
1
+ /**
2
+ * Theme context - provides theming for TUI components
3
+ */
4
+ import { parseColor, RGBA, SyntaxStyle } from "@opentui/core";
5
+ import { createContext, createEffect, createMemo, useContext } from "solid-js";
6
+ import { createStore } from "solid-js/store";
7
+ // Theme JSON imports
8
+ import marvin from "../themes/marvin.json";
9
+ import aura from "../themes/aura.json";
10
+ import ayu from "../themes/ayu.json";
11
+ import catppuccin from "../themes/catppuccin.json";
12
+ import catppuccinMacchiato from "../themes/catppuccin-macchiato.json";
13
+ import cobalt2 from "../themes/cobalt2.json";
14
+ import dracula from "../themes/dracula.json";
15
+ import everforest from "../themes/everforest.json";
16
+ import flexoki from "../themes/flexoki.json";
17
+ import github from "../themes/github.json";
18
+ import gruvbox from "../themes/gruvbox.json";
19
+ import kanagawa from "../themes/kanagawa.json";
20
+ import lucentOrng from "../themes/lucent-orng.json";
21
+ import material from "../themes/material.json";
22
+ import matrix from "../themes/matrix.json";
23
+ import mercury from "../themes/mercury.json";
24
+ import monokai from "../themes/monokai.json";
25
+ import nightowl from "../themes/nightowl.json";
26
+ import nord from "../themes/nord.json";
27
+ import onedark from "../themes/one-dark.json";
28
+ import opencode from "../themes/opencode.json";
29
+ import orng from "../themes/orng.json";
30
+ import palenight from "../themes/palenight.json";
31
+ import rosepine from "../themes/rosepine.json";
32
+ import solarized from "../themes/solarized.json";
33
+ import synthwave84 from "../themes/synthwave84.json";
34
+ import tokyonight from "../themes/tokyonight.json";
35
+ import vercel from "../themes/vercel.json";
36
+ import vesper from "../themes/vesper.json";
37
+ import zenburn from "../themes/zenburn.json";
38
+ export const BUILTIN_THEMES = {
39
+ marvin: marvin,
40
+ aura: aura,
41
+ ayu: ayu,
42
+ catppuccin: catppuccin,
43
+ "catppuccin-macchiato": catppuccinMacchiato,
44
+ cobalt2: cobalt2,
45
+ dracula: dracula,
46
+ everforest: everforest,
47
+ flexoki: flexoki,
48
+ github: github,
49
+ gruvbox: gruvbox,
50
+ kanagawa: kanagawa,
51
+ "lucent-orng": lucentOrng,
52
+ material: material,
53
+ matrix: matrix,
54
+ mercury: mercury,
55
+ monokai: monokai,
56
+ nightowl: nightowl,
57
+ nord: nord,
58
+ "one-dark": onedark,
59
+ opencode: opencode,
60
+ orng: orng,
61
+ palenight: palenight,
62
+ rosepine: rosepine,
63
+ solarized: solarized,
64
+ synthwave84: synthwave84,
65
+ tokyonight: tokyonight,
66
+ vercel: vercel,
67
+ vesper: vesper,
68
+ zenburn: zenburn,
69
+ };
70
+ /**
71
+ * Default dark theme colors - soft contrast, minimal aesthetic
72
+ */
73
+ const defaultDarkTheme = {
74
+ // Muted, desaturated primaries
75
+ primary: parseColor("#d4a373"), // warm muted tan
76
+ secondary: parseColor("#7d9bba"), // soft steel blue
77
+ accent: parseColor("#87a987"), // sage green
78
+ error: parseColor("#c47a7a"), // soft coral
79
+ warning: parseColor("#d4b483"), // muted gold
80
+ success: parseColor("#87a987"), // sage green
81
+ info: parseColor("#7d9bba"), // soft steel blue
82
+ text: parseColor("#c8c8c8"), // soft white
83
+ textMuted: parseColor("#6b6b6b"), // medium gray
84
+ background: parseColor("#161616"), // near black
85
+ backgroundPanel: parseColor("#1a1a1a"), // slightly lifted
86
+ backgroundElement: parseColor("#222222"), // subtle contrast
87
+ backgroundMenu: parseColor("#131313"),
88
+ border: parseColor("#2a2a2a"), // very subtle
89
+ borderSubtle: parseColor("#222222"),
90
+ borderActive: parseColor("#7d9bba"),
91
+ selectionBg: parseColor("#333333"),
92
+ selectionFg: parseColor("#e0e0e0"),
93
+ // Softer diff colors
94
+ diffAdded: parseColor("#87a987"),
95
+ diffRemoved: parseColor("#c47a7a"),
96
+ diffContext: parseColor("#6b6b6b"),
97
+ diffAddedBg: parseColor("#1a2a1a"),
98
+ diffRemovedBg: parseColor("#2a1a1a"),
99
+ diffContextBg: parseColor("transparent"),
100
+ diffLineNumberFg: parseColor("#4a4a4a"),
101
+ diffLineNumberBg: parseColor("transparent"),
102
+ diffAddedLineNumberBg: parseColor("#1a2a1a"),
103
+ diffRemovedLineNumberBg: parseColor("#2a1a1a"),
104
+ diffAddedSign: parseColor("#6b9b6b"),
105
+ diffRemovedSign: parseColor("#b06060"),
106
+ diffHighlightAddedBg: parseColor("#253525"),
107
+ diffHighlightRemovedBg: parseColor("#352525"),
108
+ // Markdown - muted
109
+ markdownText: parseColor("#c8c8c8"),
110
+ markdownHeading: parseColor("#a0a0a0"),
111
+ markdownLink: parseColor("#9090a0"),
112
+ markdownLinkUrl: parseColor("#606060"),
113
+ markdownCode: parseColor("#b0a090"),
114
+ markdownCodeBlock: parseColor("#b0b0b0"),
115
+ markdownCodeBlockBorder: parseColor("#2a2a2a"),
116
+ markdownBlockQuote: parseColor("#707070"),
117
+ markdownBlockQuoteBorder: parseColor("#303030"),
118
+ markdownHr: parseColor("#303030"),
119
+ markdownListBullet: parseColor("#707070"),
120
+ markdownStrong: parseColor("#c8c8c8"),
121
+ markdownEmph: parseColor("#d4c48a"),
122
+ markdownListEnumeration: parseColor("#7d9bba"),
123
+ markdownImage: parseColor("#9090a0"),
124
+ markdownStrikethrough: parseColor("#6b6b6b"),
125
+ // Syntax - soft but readable contrast
126
+ syntaxComment: parseColor("#5a5a5a"),
127
+ syntaxString: parseColor("#98b998"), // sage green, slightly brighter
128
+ syntaxKeyword: parseColor("#b09cc0"), // soft lavender
129
+ syntaxFunction: parseColor("#8aafc8"), // steel blue
130
+ syntaxVariable: parseColor("#c0c0c0"),
131
+ syntaxType: parseColor("#d4c48a"), // warm gold
132
+ syntaxNumber: parseColor("#d4a87a"), // soft orange
133
+ syntaxConstant: parseColor("#d4a87a"),
134
+ syntaxOperator: parseColor("#a0a0a0"),
135
+ syntaxPunctuation: parseColor("#909090"),
136
+ syntaxProperty: parseColor("#8ac0b0"), // teal
137
+ syntaxTag: parseColor("#c09090"), // dusty rose
138
+ syntaxAttribute: parseColor("#d4c48a"),
139
+ };
140
+ /**
141
+ * Default light theme colors - optimized for high contrast on light backgrounds
142
+ */
143
+ const defaultLightTheme = {
144
+ primary: parseColor("#b06000"), // darker orange for better contrast
145
+ secondary: parseColor("#0550ae"), // darker blue
146
+ accent: parseColor("#1a7f37"), // darker green for tool labels
147
+ error: parseColor("#c21f3a"),
148
+ warning: parseColor("#9a6700"), // darker gold/amber
149
+ success: parseColor("#1a7f37"),
150
+ info: parseColor("#0550ae"),
151
+ text: parseColor("#1f2328"), // near-black for main text
152
+ textMuted: parseColor("#656d76"), // darker gray for better readability
153
+ background: parseColor("#ffffff"),
154
+ backgroundPanel: parseColor("#f6f8fa"),
155
+ backgroundElement: parseColor("#eaeef2"),
156
+ backgroundMenu: parseColor("#f6f8fa"),
157
+ border: parseColor("#d0d7de"),
158
+ borderSubtle: parseColor("#e6e9ef"),
159
+ borderActive: parseColor("#0550ae"),
160
+ selectionBg: parseColor("#ddf4ff"),
161
+ selectionFg: parseColor("#1f2328"),
162
+ diffAdded: parseColor("#1a7f37"),
163
+ diffRemoved: parseColor("#c21f3a"),
164
+ diffContext: parseColor("#656d76"),
165
+ diffAddedBg: parseColor("#dafbe1"),
166
+ diffRemovedBg: parseColor("#ffebe9"),
167
+ diffContextBg: parseColor("transparent"),
168
+ diffLineNumberFg: parseColor("#656d76"),
169
+ diffLineNumberBg: parseColor("transparent"),
170
+ diffAddedLineNumberBg: parseColor("#dafbe1"),
171
+ diffRemovedLineNumberBg: parseColor("#ffebe9"),
172
+ diffAddedSign: parseColor("#1a7f37"),
173
+ diffRemovedSign: parseColor("#c21f3a"),
174
+ diffHighlightAddedBg: parseColor("#aceebb"),
175
+ diffHighlightRemovedBg: parseColor("#ffcecb"),
176
+ markdownText: parseColor("#1f2328"),
177
+ markdownHeading: parseColor("#0550ae"),
178
+ markdownLink: parseColor("#8250df"), // purple for links
179
+ markdownLinkUrl: parseColor("#656d76"),
180
+ markdownCode: parseColor("#9a6700"), // darker gold
181
+ markdownCodeBlock: parseColor("#1f2328"),
182
+ markdownCodeBlockBorder: parseColor("#d0d7de"),
183
+ markdownBlockQuote: parseColor("#656d76"),
184
+ markdownBlockQuoteBorder: parseColor("#d0d7de"),
185
+ markdownHr: parseColor("#d0d7de"),
186
+ markdownListBullet: parseColor("#1a7f37"),
187
+ markdownStrong: parseColor("#1f2328"),
188
+ markdownEmph: parseColor("#9a6700"),
189
+ markdownListEnumeration: parseColor("#0550ae"),
190
+ markdownImage: parseColor("#8250df"),
191
+ markdownStrikethrough: parseColor("#656d76"),
192
+ syntaxComment: parseColor("#6e7781"), // darker gray
193
+ syntaxString: parseColor("#0a3069"), // dark blue for strings
194
+ syntaxKeyword: parseColor("#8250df"), // purple
195
+ syntaxFunction: parseColor("#8250df"), // purple for functions
196
+ syntaxVariable: parseColor("#1f2328"),
197
+ syntaxType: parseColor("#953800"), // dark orange
198
+ syntaxNumber: parseColor("#0550ae"), // blue for numbers
199
+ syntaxConstant: parseColor("#0550ae"),
200
+ syntaxOperator: parseColor("#1f2328"),
201
+ syntaxPunctuation: parseColor("#1f2328"),
202
+ syntaxProperty: parseColor("#0550ae"),
203
+ syntaxTag: parseColor("#1a7f37"), // green for tags
204
+ syntaxAttribute: parseColor("#953800"),
205
+ };
206
+ function clamp01(value) {
207
+ if (value < 0)
208
+ return 0;
209
+ if (value > 1)
210
+ return 1;
211
+ return value;
212
+ }
213
+ function srgbToLinear(channel) {
214
+ if (channel <= 0.04045)
215
+ return channel / 12.92;
216
+ return Math.pow((channel + 0.055) / 1.055, 2.4);
217
+ }
218
+ function relativeLuminance(color) {
219
+ const r = srgbToLinear(clamp01(color.r));
220
+ const g = srgbToLinear(clamp01(color.g));
221
+ const b = srgbToLinear(clamp01(color.b));
222
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
223
+ }
224
+ function contrastRatio(a, b) {
225
+ const l1 = relativeLuminance(a);
226
+ const l2 = relativeLuminance(b);
227
+ const lighter = Math.max(l1, l2);
228
+ const darker = Math.min(l1, l2);
229
+ return (lighter + 0.05) / (darker + 0.05);
230
+ }
231
+ function compositeOver(background, overlay) {
232
+ if (overlay.a >= 0.99)
233
+ return overlay;
234
+ if (overlay.a <= 0.01)
235
+ return background;
236
+ const inv = 1 - overlay.a;
237
+ return RGBA.fromValues(overlay.r * overlay.a + background.r * inv, overlay.g * overlay.a + background.g * inv, overlay.b * overlay.a + background.b * inv, 1);
238
+ }
239
+ function contrastAgainst(background, foreground) {
240
+ const effective = compositeOver(background, foreground);
241
+ return contrastRatio(effective, background);
242
+ }
243
+ function generateGrayScale(background, isDark) {
244
+ const grays = {};
245
+ const bgR = background.r * 255;
246
+ const bgG = background.g * 255;
247
+ const bgB = background.b * 255;
248
+ const luminance = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB;
249
+ for (let i = 1; i <= 12; i += 1) {
250
+ const factor = i / 12.0;
251
+ let newR = 0;
252
+ let newG = 0;
253
+ let newB = 0;
254
+ if (isDark) {
255
+ if (luminance < 10) {
256
+ const grayValue = Math.floor(factor * 0.4 * 255);
257
+ newR = grayValue;
258
+ newG = grayValue;
259
+ newB = grayValue;
260
+ }
261
+ else {
262
+ const newLum = luminance + (255 - luminance) * factor * 0.4;
263
+ const ratio = newLum / luminance;
264
+ newR = Math.min(bgR * ratio, 255);
265
+ newG = Math.min(bgG * ratio, 255);
266
+ newB = Math.min(bgB * ratio, 255);
267
+ }
268
+ }
269
+ else if (luminance > 245) {
270
+ const grayValue = Math.floor(255 - factor * 0.4 * 255);
271
+ newR = grayValue;
272
+ newG = grayValue;
273
+ newB = grayValue;
274
+ }
275
+ else {
276
+ const newLum = luminance * (1 - factor * 0.4);
277
+ const ratio = newLum / luminance;
278
+ newR = Math.max(bgR * ratio, 0);
279
+ newG = Math.max(bgG * ratio, 0);
280
+ newB = Math.max(bgB * ratio, 0);
281
+ }
282
+ grays[i] = RGBA.fromInts(Math.floor(newR), Math.floor(newG), Math.floor(newB));
283
+ }
284
+ return grays;
285
+ }
286
+ function getGray(grays, index, fallback) {
287
+ const value = grays[index];
288
+ return value === undefined ? fallback : value;
289
+ }
290
+ function generateMutedTextColor(background, isDark) {
291
+ const bgR = background.r * 255;
292
+ const bgG = background.g * 255;
293
+ const bgB = background.b * 255;
294
+ const luminance = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB;
295
+ let grayValue = 0;
296
+ if (isDark) {
297
+ if (luminance < 10) {
298
+ grayValue = 180;
299
+ }
300
+ else {
301
+ grayValue = Math.min(Math.floor(160 + luminance * 0.3), 200);
302
+ }
303
+ }
304
+ else if (luminance > 245) {
305
+ grayValue = 75;
306
+ }
307
+ else {
308
+ grayValue = Math.max(Math.floor(100 - (255 - luminance) * 0.2), 60);
309
+ }
310
+ return RGBA.fromInts(grayValue, grayValue, grayValue);
311
+ }
312
+ function ensureLightModeContrast(theme) {
313
+ const effectiveBackground = theme.background.a >= 0.99 ? theme.background : defaultLightTheme.background;
314
+ const overrides = {};
315
+ const selectionBgMatchesElement = theme.selectionBg === theme.backgroundElement;
316
+ const muted = generateMutedTextColor(effectiveBackground, false);
317
+ if (theme.background.a >= 0.99) {
318
+ const grays = generateGrayScale(effectiveBackground, false);
319
+ const panelFallback = getGray(grays, 2, theme.backgroundPanel);
320
+ const panel = contrastAgainst(effectiveBackground, theme.backgroundPanel) >= 1.04 ? theme.backgroundPanel : panelFallback;
321
+ if (panel !== theme.backgroundPanel)
322
+ overrides.backgroundPanel = panel;
323
+ const elementFallback = getGray(grays, 3, theme.backgroundElement);
324
+ const element = contrastAgainst(effectiveBackground, theme.backgroundElement) >= 1.08 ? theme.backgroundElement : elementFallback;
325
+ if (element !== theme.backgroundElement)
326
+ overrides.backgroundElement = element;
327
+ const menuFallback = getGray(grays, 3, theme.backgroundMenu);
328
+ const menu = contrastAgainst(effectiveBackground, theme.backgroundMenu) >= 1.08 ? theme.backgroundMenu : menuFallback;
329
+ if (menu !== theme.backgroundMenu)
330
+ overrides.backgroundMenu = menu;
331
+ const borderSubtleFallback = getGray(grays, 6, theme.borderSubtle);
332
+ const borderSubtle = contrastAgainst(effectiveBackground, theme.borderSubtle) >= 1.12 ? theme.borderSubtle : borderSubtleFallback;
333
+ if (borderSubtle !== theme.borderSubtle)
334
+ overrides.borderSubtle = borderSubtle;
335
+ const borderFallback = getGray(grays, 7, theme.border);
336
+ const border = contrastAgainst(effectiveBackground, theme.border) >= 1.2 ? theme.border : borderFallback;
337
+ if (border !== theme.border)
338
+ overrides.border = border;
339
+ }
340
+ const textMuted = contrastAgainst(effectiveBackground, theme.textMuted) >= 3 ? theme.textMuted : muted;
341
+ if (textMuted !== theme.textMuted)
342
+ overrides.textMuted = textMuted;
343
+ const backgroundElementOverride = overrides.backgroundElement;
344
+ if (selectionBgMatchesElement && backgroundElementOverride !== undefined) {
345
+ overrides.selectionBg = backgroundElementOverride;
346
+ }
347
+ const selectionBg = overrides.selectionBg ?? theme.selectionBg;
348
+ const selectionTarget = compositeOver(effectiveBackground, selectionBg);
349
+ const currentSelection = contrastAgainst(selectionTarget, theme.selectionFg);
350
+ if (currentSelection < 3) {
351
+ const textRatio = contrastAgainst(selectionTarget, theme.text);
352
+ const backgroundRatio = contrastAgainst(selectionTarget, theme.background);
353
+ let best = theme.text;
354
+ let bestRatio = textRatio;
355
+ if (backgroundRatio > bestRatio) {
356
+ best = theme.background;
357
+ bestRatio = backgroundRatio;
358
+ }
359
+ if (bestRatio < 3) {
360
+ const black = RGBA.fromInts(0, 0, 0);
361
+ const white = RGBA.fromInts(255, 255, 255);
362
+ best = contrastAgainst(selectionTarget, black) >= contrastAgainst(selectionTarget, white) ? black : white;
363
+ }
364
+ overrides.selectionFg = best;
365
+ }
366
+ if (Object.keys(overrides).length === 0)
367
+ return theme;
368
+ return { ...theme, ...overrides };
369
+ }
370
+ /**
371
+ * Resolve a ThemeJson to concrete RGBA colors for a given mode
372
+ */
373
+ function resolveThemeJson(themeJson, mode) {
374
+ const defs = themeJson.defs ?? {};
375
+ function resolveColor(c) {
376
+ if (c instanceof RGBA)
377
+ return c;
378
+ if (typeof c === "string") {
379
+ if (c === "transparent" || c === "none")
380
+ return parseColor("transparent");
381
+ if (c.startsWith("#"))
382
+ return parseColor(c);
383
+ // Reference to defs
384
+ if (defs[c] != null)
385
+ return resolveColor(defs[c]);
386
+ // Reference to another theme key
387
+ if (themeJson.theme[c] !== undefined)
388
+ return resolveColor(themeJson.theme[c]);
389
+ // Unknown reference - return magenta as debug indicator
390
+ console.warn(`Unknown color reference: ${c}`);
391
+ return parseColor("#ff00ff");
392
+ }
393
+ // Variant object { dark: ..., light: ... }
394
+ if (typeof c === "object" && c !== null && "dark" in c && "light" in c) {
395
+ return resolveColor(c[mode]);
396
+ }
397
+ // Unknown - return magenta
398
+ return parseColor("#ff00ff");
399
+ }
400
+ const resolved = {};
401
+ for (const [key, value] of Object.entries(themeJson.theme)) {
402
+ if (key === "$schema")
403
+ continue;
404
+ resolved[key] = resolveColor(value);
405
+ }
406
+ return resolved;
407
+ }
408
+ /**
409
+ * Map resolved opencode theme colors to marvin ThemeColors with fallbacks
410
+ */
411
+ function mapToThemeColors(resolved, mode) {
412
+ const base = mode === "dark" ? defaultDarkTheme : defaultLightTheme;
413
+ // Helper to get color with fallback
414
+ const get = (key, ...fallbacks) => {
415
+ const direct = resolved[key];
416
+ if (direct !== undefined)
417
+ return direct;
418
+ for (const fb of fallbacks) {
419
+ const fallback = resolved[fb];
420
+ if (fallback !== undefined)
421
+ return fallback;
422
+ }
423
+ return base[key] ?? base.text;
424
+ };
425
+ return {
426
+ primary: get("primary"),
427
+ secondary: get("secondary"),
428
+ accent: get("accent"),
429
+ error: get("error"),
430
+ warning: get("warning"),
431
+ success: get("success"),
432
+ info: get("info"),
433
+ text: get("text"),
434
+ textMuted: get("textMuted"),
435
+ background: get("background"),
436
+ backgroundPanel: get("backgroundPanel"),
437
+ backgroundElement: get("backgroundElement"),
438
+ backgroundMenu: get("backgroundMenu", "backgroundElement"),
439
+ border: get("border"),
440
+ borderSubtle: get("borderSubtle"),
441
+ borderActive: get("borderActive"),
442
+ selectionBg: get("selectionBg", "backgroundElement"),
443
+ selectionFg: get("selectionFg", "text"),
444
+ // Diff colors - map from opencode names
445
+ diffAdded: get("diffAdded"),
446
+ diffRemoved: get("diffRemoved"),
447
+ diffContext: get("diffContext"),
448
+ diffAddedBg: get("diffAddedBg"),
449
+ diffRemovedBg: get("diffRemovedBg"),
450
+ diffContextBg: get("diffContextBg"),
451
+ diffLineNumberFg: get("diffLineNumber", "textMuted"),
452
+ diffLineNumberBg: get("diffLineNumberBg", "background"),
453
+ diffAddedLineNumberBg: get("diffAddedLineNumberBg", "diffAddedBg"),
454
+ diffRemovedLineNumberBg: get("diffRemovedLineNumberBg", "diffRemovedBg"),
455
+ diffAddedSign: get("diffAddedSign", "diffAdded"),
456
+ diffRemovedSign: get("diffRemovedSign", "diffRemoved"),
457
+ diffHighlightAddedBg: get("diffHighlightAddedBg", "diffAddedBg"),
458
+ diffHighlightRemovedBg: get("diffHighlightRemovedBg", "diffRemovedBg"),
459
+ // Markdown colors
460
+ markdownText: get("markdownText", "text"),
461
+ markdownHeading: get("markdownHeading", "primary"),
462
+ markdownLink: get("markdownLink", "accent"),
463
+ markdownLinkUrl: get("markdownLinkUrl", "markdownLinkText", "textMuted"),
464
+ markdownCode: get("markdownCode", "success"),
465
+ markdownCodeBlock: get("markdownCodeBlock", "text"),
466
+ markdownCodeBlockBorder: get("markdownCodeBlockBorder", "border"),
467
+ markdownBlockQuote: get("markdownBlockQuote", "textMuted"),
468
+ markdownBlockQuoteBorder: get("markdownBlockQuoteBorder", "border"),
469
+ markdownHr: get("markdownHorizontalRule", "border"),
470
+ markdownListBullet: get("markdownListBullet", "markdownListItem", "accent"),
471
+ markdownStrong: get("markdownStrong", "text"),
472
+ markdownEmph: get("markdownEmph", "warning"),
473
+ markdownListEnumeration: get("markdownListEnumeration", "markdownListBullet"),
474
+ markdownImage: get("markdownImage", "markdownLink"),
475
+ markdownStrikethrough: get("markdownStrikethrough", "textMuted"),
476
+ // Syntax colors
477
+ syntaxComment: get("syntaxComment"),
478
+ syntaxString: get("syntaxString"),
479
+ syntaxKeyword: get("syntaxKeyword"),
480
+ syntaxFunction: get("syntaxFunction"),
481
+ syntaxVariable: get("syntaxVariable"),
482
+ syntaxType: get("syntaxType"),
483
+ syntaxNumber: get("syntaxNumber"),
484
+ syntaxConstant: get("syntaxConstant", "syntaxNumber"),
485
+ syntaxOperator: get("syntaxOperator"),
486
+ syntaxPunctuation: get("syntaxPunctuation"),
487
+ syntaxProperty: get("syntaxProperty", "syntaxVariable"),
488
+ syntaxTag: get("syntaxTag", "syntaxKeyword"),
489
+ syntaxAttribute: get("syntaxAttribute", "syntaxProperty"),
490
+ };
491
+ }
492
+ export function createSyntaxStyle(theme, variant = "normal") {
493
+ const rules = getSyntaxRules(theme);
494
+ if (variant === "subtle") {
495
+ return SyntaxStyle.fromTheme(rules.map((rule) => ({
496
+ ...rule,
497
+ style: { ...rule.style, dim: true },
498
+ })));
499
+ }
500
+ return SyntaxStyle.fromTheme(rules);
501
+ }
502
+ function getSyntaxRules(theme) {
503
+ return [
504
+ // Default text
505
+ { scope: ["default"], style: { foreground: theme.text } },
506
+ // Comments
507
+ { scope: ["comment", "comment.documentation"], style: { foreground: theme.syntaxComment, italic: true } },
508
+ // Strings
509
+ { scope: ["string", "symbol"], style: { foreground: theme.syntaxString } },
510
+ { scope: ["string.escape", "string.regexp"], style: { foreground: theme.syntaxKeyword } },
511
+ { scope: ["character", "character.special"], style: { foreground: theme.syntaxString } },
512
+ // Numbers and constants
513
+ { scope: ["number", "boolean", "float"], style: { foreground: theme.syntaxNumber } },
514
+ { scope: ["constant", "constant.builtin"], style: { foreground: theme.syntaxConstant } },
515
+ // Keywords
516
+ { scope: ["keyword"], style: { foreground: theme.syntaxKeyword, italic: true } },
517
+ {
518
+ scope: ["keyword.function", "keyword.return", "keyword.conditional", "keyword.repeat"],
519
+ style: { foreground: theme.syntaxKeyword, italic: true },
520
+ },
521
+ { scope: ["keyword.operator", "operator"], style: { foreground: theme.syntaxOperator } },
522
+ { scope: ["keyword.import", "keyword.export"], style: { foreground: theme.syntaxKeyword } },
523
+ { scope: ["keyword.type"], style: { foreground: theme.syntaxType, bold: true, italic: true } },
524
+ // Functions
525
+ {
526
+ scope: ["function", "function.call", "function.method", "function.method.call", "function.builtin"],
527
+ style: { foreground: theme.syntaxFunction },
528
+ },
529
+ { scope: ["constructor"], style: { foreground: theme.syntaxFunction } },
530
+ // Variables and parameters
531
+ { scope: ["variable", "variable.parameter", "parameter"], style: { foreground: theme.syntaxVariable } },
532
+ { scope: ["variable.member", "property", "field"], style: { foreground: theme.syntaxProperty } },
533
+ { scope: ["variable.builtin", "variable.super"], style: { foreground: theme.error } },
534
+ // Types
535
+ { scope: ["type", "type.builtin", "type.definition"], style: { foreground: theme.syntaxType } },
536
+ { scope: ["class", "module", "namespace"], style: { foreground: theme.syntaxType } },
537
+ // Punctuation
538
+ { scope: ["punctuation", "punctuation.bracket", "punctuation.delimiter"], style: { foreground: theme.syntaxPunctuation } },
539
+ { scope: ["punctuation.special"], style: { foreground: theme.syntaxOperator } },
540
+ // Tags (HTML/XML)
541
+ { scope: ["tag"], style: { foreground: theme.syntaxTag } },
542
+ { scope: ["tag.attribute"], style: { foreground: theme.syntaxAttribute } },
543
+ { scope: ["tag.delimiter"], style: { foreground: theme.syntaxOperator } },
544
+ // Attributes and annotations
545
+ { scope: ["attribute", "annotation"], style: { foreground: theme.warning } },
546
+ // Markdown specific
547
+ {
548
+ scope: ["markup.heading", "markup.heading.1", "markup.heading.2", "markup.heading.3", "markup.heading.4", "markup.heading.5", "markup.heading.6"],
549
+ style: { foreground: theme.markdownHeading, bold: true },
550
+ },
551
+ { scope: ["markup.bold", "markup.strong"], style: { foreground: theme.markdownStrong, bold: true } },
552
+ { scope: ["markup.italic"], style: { foreground: theme.markdownEmph, italic: true } },
553
+ { scope: ["markup.strikethrough"], style: { foreground: theme.markdownStrikethrough } },
554
+ { scope: ["markup.link", "markup.link.url"], style: { foreground: theme.markdownLink, underline: true } },
555
+ { scope: ["markup.link.label", "label"], style: { foreground: theme.markdownLinkUrl } },
556
+ { scope: ["markup.raw", "markup.raw.inline", "markup.raw.block"], style: { foreground: theme.markdownCode } },
557
+ { scope: ["markup.list"], style: { foreground: theme.markdownListBullet } },
558
+ { scope: ["markup.list.checked"], style: { foreground: theme.success } },
559
+ { scope: ["markup.list.unchecked"], style: { foreground: theme.textMuted } },
560
+ { scope: ["markup.quote"], style: { foreground: theme.markdownBlockQuote, italic: true } },
561
+ // Diff
562
+ { scope: ["diff.plus"], style: { foreground: theme.diffAdded, background: theme.diffAddedBg } },
563
+ { scope: ["diff.minus"], style: { foreground: theme.diffRemoved, background: theme.diffRemovedBg } },
564
+ { scope: ["diff.delta"], style: { foreground: theme.diffContext, background: theme.diffContextBg } },
565
+ // Conceal (for hidden markdown syntax)
566
+ { scope: ["conceal"], style: { foreground: theme.textMuted } },
567
+ // Misc
568
+ { scope: ["spell", "nospell"], style: { foreground: theme.text } },
569
+ { scope: ["error"], style: { foreground: theme.error, bold: true } },
570
+ { scope: ["warning"], style: { foreground: theme.warning, bold: true } },
571
+ { scope: ["info"], style: { foreground: theme.info } },
572
+ ];
573
+ }
574
+ const ThemeContext = createContext();
575
+ export function ThemeProvider(props) {
576
+ const [store, setStore] = createStore({
577
+ mode: props.mode ?? "dark",
578
+ themeName: props.themeName ?? "marvin",
579
+ });
580
+ // Sync themeName prop changes to store (for external control)
581
+ createEffect(() => {
582
+ if (props.themeName !== undefined && props.themeName !== store.themeName) {
583
+ setStore("themeName", props.themeName);
584
+ }
585
+ });
586
+ // Sync mode prop changes to store (for external light/dark toggle)
587
+ createEffect(() => {
588
+ if (props.mode !== undefined && props.mode !== store.mode) {
589
+ setStore("mode", props.mode);
590
+ }
591
+ });
592
+ const resolvedTheme = createMemo(() => {
593
+ const name = store.themeName;
594
+ const mode = store.mode;
595
+ // Fallback to defaults for unknown themes
596
+ if (!BUILTIN_THEMES[name]) {
597
+ const base = mode === "dark" ? defaultDarkTheme : defaultLightTheme;
598
+ const merged = { ...base, ...props.customTheme };
599
+ return mode === "light" ? ensureLightModeContrast(merged) : merged;
600
+ }
601
+ // Resolve named theme (including marvin)
602
+ const themeJson = BUILTIN_THEMES[name];
603
+ const resolved = resolveThemeJson(themeJson, mode);
604
+ const mapped = mapToThemeColors(resolved, mode);
605
+ const merged = { ...mapped, ...props.customTheme };
606
+ return mode === "light" ? ensureLightModeContrast(merged) : merged;
607
+ });
608
+ // Use createMemo for syntax styles - they'll recompute when theme changes
609
+ const syntaxStyle = createMemo(() => createSyntaxStyle(resolvedTheme(), "normal"));
610
+ const subtleSyntaxStyle = createMemo(() => createSyntaxStyle(resolvedTheme(), "subtle"));
611
+ // Note: SyntaxStyle cleanup is handled internally by opentui when memos recompute
612
+ const value = {
613
+ get theme() {
614
+ return resolvedTheme();
615
+ },
616
+ mode: () => store.mode,
617
+ setMode: (mode) => {
618
+ setStore("mode", mode);
619
+ },
620
+ get syntaxStyle() {
621
+ return syntaxStyle();
622
+ },
623
+ get subtleSyntaxStyle() {
624
+ return subtleSyntaxStyle();
625
+ },
626
+ themeName: () => store.themeName,
627
+ setTheme: (name) => {
628
+ setStore("themeName", name);
629
+ props.onThemeChange?.(name);
630
+ },
631
+ availableThemes: () => Object.keys(BUILTIN_THEMES),
632
+ };
633
+ return <ThemeContext.Provider value={value}>{props.children}</ThemeContext.Provider>;
634
+ }
635
+ export function useTheme() {
636
+ const context = useContext(ThemeContext);
637
+ if (!context) {
638
+ throw new Error("useTheme must be used within a ThemeProvider");
639
+ }
640
+ return context;
641
+ }
642
+ /**
643
+ * Parse a color input to RGBA
644
+ */
645
+ export function toRGBA(color) {
646
+ return parseColor(color);
647
+ }
648
+ // Re-export RGBA for convenience
649
+ export { RGBA } from "@opentui/core";
650
+ //# sourceMappingURL=theme.jsx.map