@textui/core 0.1.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 (323) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +67 -0
  3. package/dist/adapters/index.d.ts +12 -0
  4. package/dist/adapters/index.d.ts.map +1 -0
  5. package/dist/adapters/index.js +11 -0
  6. package/dist/app/app.d.ts +200 -0
  7. package/dist/app/app.d.ts.map +1 -0
  8. package/dist/app/app.js +980 -0
  9. package/dist/core/animation.d.ts +34 -0
  10. package/dist/core/animation.d.ts.map +1 -0
  11. package/dist/core/animation.js +125 -0
  12. package/dist/core/clipboard.d.ts +29 -0
  13. package/dist/core/clipboard.d.ts.map +1 -0
  14. package/dist/core/clipboard.js +30 -0
  15. package/dist/core/commands.d.ts +42 -0
  16. package/dist/core/commands.d.ts.map +1 -0
  17. package/dist/core/commands.js +146 -0
  18. package/dist/core/components.d.ts +27 -0
  19. package/dist/core/components.d.ts.map +1 -0
  20. package/dist/core/components.js +78 -0
  21. package/dist/core/events.d.ts +23 -0
  22. package/dist/core/events.d.ts.map +1 -0
  23. package/dist/core/events.js +77 -0
  24. package/dist/core/focus.d.ts +69 -0
  25. package/dist/core/focus.d.ts.map +1 -0
  26. package/dist/core/focus.js +336 -0
  27. package/dist/core/i18n.d.ts +27 -0
  28. package/dist/core/i18n.d.ts.map +1 -0
  29. package/dist/core/i18n.js +88 -0
  30. package/dist/core/keybindings.d.ts +53 -0
  31. package/dist/core/keybindings.d.ts.map +1 -0
  32. package/dist/core/keybindings.js +163 -0
  33. package/dist/core/layers.d.ts +28 -0
  34. package/dist/core/layers.d.ts.map +1 -0
  35. package/dist/core/layers.js +84 -0
  36. package/dist/core/manifest.d.ts +22 -0
  37. package/dist/core/manifest.d.ts.map +1 -0
  38. package/dist/core/manifest.js +85 -0
  39. package/dist/core/navigation.d.ts +47 -0
  40. package/dist/core/navigation.d.ts.map +1 -0
  41. package/dist/core/navigation.js +110 -0
  42. package/dist/core/resources.d.ts +115 -0
  43. package/dist/core/resources.d.ts.map +1 -0
  44. package/dist/core/resources.js +321 -0
  45. package/dist/core/services.d.ts +21 -0
  46. package/dist/core/services.d.ts.map +1 -0
  47. package/dist/core/services.js +63 -0
  48. package/dist/core/store.d.ts +60 -0
  49. package/dist/core/store.d.ts.map +1 -0
  50. package/dist/core/store.js +593 -0
  51. package/dist/core/surfaces.d.ts +81 -0
  52. package/dist/core/surfaces.d.ts.map +1 -0
  53. package/dist/core/surfaces.js +237 -0
  54. package/dist/core/syntax.d.ts +49 -0
  55. package/dist/core/syntax.d.ts.map +1 -0
  56. package/dist/core/syntax.js +172 -0
  57. package/dist/core/when.d.ts +15 -0
  58. package/dist/core/when.d.ts.map +1 -0
  59. package/dist/core/when.js +224 -0
  60. package/dist/index.d.ts +49 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +54 -0
  63. package/dist/jsx/factory.d.ts +27 -0
  64. package/dist/jsx/factory.d.ts.map +1 -0
  65. package/dist/jsx/factory.js +102 -0
  66. package/dist/jsx/intrinsics.d.ts +96 -0
  67. package/dist/jsx/intrinsics.d.ts.map +1 -0
  68. package/dist/jsx/intrinsics.js +1 -0
  69. package/dist/jsx/jsx-dev-runtime.d.ts +13 -0
  70. package/dist/jsx/jsx-dev-runtime.d.ts.map +1 -0
  71. package/dist/jsx/jsx-dev-runtime.js +12 -0
  72. package/dist/jsx/jsx-runtime.d.ts +42 -0
  73. package/dist/jsx/jsx-runtime.d.ts.map +1 -0
  74. package/dist/jsx/jsx-runtime.js +16 -0
  75. package/dist/render/buffer.d.ts +77 -0
  76. package/dist/render/buffer.d.ts.map +1 -0
  77. package/dist/render/buffer.js +275 -0
  78. package/dist/render/color.d.ts +35 -0
  79. package/dist/render/color.d.ts.map +1 -0
  80. package/dist/render/color.js +160 -0
  81. package/dist/render/diff.d.ts +37 -0
  82. package/dist/render/diff.d.ts.map +1 -0
  83. package/dist/render/diff.js +66 -0
  84. package/dist/render/layout.d.ts +72 -0
  85. package/dist/render/layout.d.ts.map +1 -0
  86. package/dist/render/layout.js +633 -0
  87. package/dist/render/static.d.ts +63 -0
  88. package/dist/render/static.d.ts.map +1 -0
  89. package/dist/render/static.js +209 -0
  90. package/dist/runtime/bindings.d.ts +46 -0
  91. package/dist/runtime/bindings.d.ts.map +1 -0
  92. package/dist/runtime/bindings.js +116 -0
  93. package/dist/runtime/hooks.d.ts +284 -0
  94. package/dist/runtime/hooks.d.ts.map +1 -0
  95. package/dist/runtime/hooks.js +846 -0
  96. package/dist/runtime/instance.d.ts +106 -0
  97. package/dist/runtime/instance.d.ts.map +1 -0
  98. package/dist/runtime/instance.js +181 -0
  99. package/dist/runtime/paint.d.ts +27 -0
  100. package/dist/runtime/paint.d.ts.map +1 -0
  101. package/dist/runtime/paint.js +567 -0
  102. package/dist/runtime/reconcile.d.ts +23 -0
  103. package/dist/runtime/reconcile.d.ts.map +1 -0
  104. package/dist/runtime/reconcile.js +260 -0
  105. package/dist/runtime/runtime.d.ts +42 -0
  106. package/dist/runtime/runtime.d.ts.map +1 -0
  107. package/dist/runtime/runtime.js +1 -0
  108. package/dist/runtime/style.d.ts +55 -0
  109. package/dist/runtime/style.d.ts.map +1 -0
  110. package/dist/runtime/style.js +143 -0
  111. package/dist/themes/borders.d.ts +10 -0
  112. package/dist/themes/borders.d.ts.map +1 -0
  113. package/dist/themes/borders.js +88 -0
  114. package/dist/themes/builtin.d.ts +31 -0
  115. package/dist/themes/builtin.d.ts.map +1 -0
  116. package/dist/themes/builtin.js +291 -0
  117. package/dist/themes/glyphs.d.ts +15 -0
  118. package/dist/themes/glyphs.d.ts.map +1 -0
  119. package/dist/themes/glyphs.js +97 -0
  120. package/dist/themes/index.d.ts +5 -0
  121. package/dist/themes/index.d.ts.map +1 -0
  122. package/dist/themes/index.js +4 -0
  123. package/dist/themes/registry.d.ts +20 -0
  124. package/dist/themes/registry.d.ts.map +1 -0
  125. package/dist/themes/registry.js +204 -0
  126. package/dist/types/adapter.d.ts +38 -0
  127. package/dist/types/adapter.d.ts.map +1 -0
  128. package/dist/types/adapter.js +1 -0
  129. package/dist/types/animation.d.ts +31 -0
  130. package/dist/types/animation.d.ts.map +1 -0
  131. package/dist/types/animation.js +1 -0
  132. package/dist/types/app.d.ts +148 -0
  133. package/dist/types/app.d.ts.map +1 -0
  134. package/dist/types/app.js +1 -0
  135. package/dist/types/async.d.ts +30 -0
  136. package/dist/types/async.d.ts.map +1 -0
  137. package/dist/types/async.js +1 -0
  138. package/dist/types/capabilities.d.ts +44 -0
  139. package/dist/types/capabilities.d.ts.map +1 -0
  140. package/dist/types/capabilities.js +33 -0
  141. package/dist/types/cells.d.ts +72 -0
  142. package/dist/types/cells.d.ts.map +1 -0
  143. package/dist/types/cells.js +10 -0
  144. package/dist/types/command.d.ts +127 -0
  145. package/dist/types/command.d.ts.map +1 -0
  146. package/dist/types/command.js +1 -0
  147. package/dist/types/component-registry.d.ts +77 -0
  148. package/dist/types/component-registry.d.ts.map +1 -0
  149. package/dist/types/component-registry.js +1 -0
  150. package/dist/types/disposable.d.ts +8 -0
  151. package/dist/types/disposable.d.ts.map +1 -0
  152. package/dist/types/disposable.js +1 -0
  153. package/dist/types/focus.d.ts +72 -0
  154. package/dist/types/focus.d.ts.map +1 -0
  155. package/dist/types/focus.js +1 -0
  156. package/dist/types/geometry.d.ts +28 -0
  157. package/dist/types/geometry.d.ts.map +1 -0
  158. package/dist/types/geometry.js +14 -0
  159. package/dist/types/graph.d.ts +129 -0
  160. package/dist/types/graph.d.ts.map +1 -0
  161. package/dist/types/graph.js +31 -0
  162. package/dist/types/i18n.d.ts +23 -0
  163. package/dist/types/i18n.d.ts.map +1 -0
  164. package/dist/types/i18n.js +1 -0
  165. package/dist/types/index.d.ts +32 -0
  166. package/dist/types/index.d.ts.map +1 -0
  167. package/dist/types/index.js +31 -0
  168. package/dist/types/input.d.ts +65 -0
  169. package/dist/types/input.d.ts.map +1 -0
  170. package/dist/types/input.js +1 -0
  171. package/dist/types/keybinding.d.ts +49 -0
  172. package/dist/types/keybinding.d.ts.map +1 -0
  173. package/dist/types/keybinding.js +1 -0
  174. package/dist/types/layer.d.ts +54 -0
  175. package/dist/types/layer.d.ts.map +1 -0
  176. package/dist/types/layer.js +1 -0
  177. package/dist/types/manifest.d.ts +114 -0
  178. package/dist/types/manifest.d.ts.map +1 -0
  179. package/dist/types/manifest.js +1 -0
  180. package/dist/types/markdown.d.ts +58 -0
  181. package/dist/types/markdown.d.ts.map +1 -0
  182. package/dist/types/markdown.js +1 -0
  183. package/dist/types/navigation.d.ts +40 -0
  184. package/dist/types/navigation.d.ts.map +1 -0
  185. package/dist/types/navigation.js +1 -0
  186. package/dist/types/render.d.ts +72 -0
  187. package/dist/types/render.d.ts.map +1 -0
  188. package/dist/types/render.js +1 -0
  189. package/dist/types/resource.d.ts +170 -0
  190. package/dist/types/resource.d.ts.map +1 -0
  191. package/dist/types/resource.js +1 -0
  192. package/dist/types/services.d.ts +24 -0
  193. package/dist/types/services.d.ts.map +1 -0
  194. package/dist/types/services.js +3 -0
  195. package/dist/types/shell.d.ts +32 -0
  196. package/dist/types/shell.d.ts.map +1 -0
  197. package/dist/types/shell.js +1 -0
  198. package/dist/types/store.d.ts +124 -0
  199. package/dist/types/store.d.ts.map +1 -0
  200. package/dist/types/store.js +1 -0
  201. package/dist/types/stream.d.ts +18 -0
  202. package/dist/types/stream.d.ts.map +1 -0
  203. package/dist/types/stream.js +1 -0
  204. package/dist/types/style.d.ts +166 -0
  205. package/dist/types/style.d.ts.map +1 -0
  206. package/dist/types/style.js +1 -0
  207. package/dist/types/surface.d.ts +97 -0
  208. package/dist/types/surface.d.ts.map +1 -0
  209. package/dist/types/surface.js +1 -0
  210. package/dist/types/syntax.d.ts +68 -0
  211. package/dist/types/syntax.d.ts.map +1 -0
  212. package/dist/types/syntax.js +5 -0
  213. package/dist/types/terminal.d.ts +52 -0
  214. package/dist/types/terminal.d.ts.map +1 -0
  215. package/dist/types/terminal.js +1 -0
  216. package/dist/types/theme.d.ts +117 -0
  217. package/dist/types/theme.d.ts.map +1 -0
  218. package/dist/types/theme.js +1 -0
  219. package/dist/types/when.d.ts +19 -0
  220. package/dist/types/when.d.ts.map +1 -0
  221. package/dist/types/when.js +1 -0
  222. package/dist/ui/primitives.d.ts +56 -0
  223. package/dist/ui/primitives.d.ts.map +1 -0
  224. package/dist/ui/primitives.js +108 -0
  225. package/dist/ui/screen.d.ts +23 -0
  226. package/dist/ui/screen.d.ts.map +1 -0
  227. package/dist/ui/screen.js +18 -0
  228. package/dist/util/disposable.d.ts +6 -0
  229. package/dist/util/disposable.d.ts.map +1 -0
  230. package/dist/util/disposable.js +49 -0
  231. package/dist/util/markdown.d.ts +24 -0
  232. package/dist/util/markdown.d.ts.map +1 -0
  233. package/dist/util/markdown.js +220 -0
  234. package/dist/util/paths.d.ts +47 -0
  235. package/dist/util/paths.d.ts.map +1 -0
  236. package/dist/util/paths.js +134 -0
  237. package/dist/util/stream.d.ts +32 -0
  238. package/dist/util/stream.d.ts.map +1 -0
  239. package/dist/util/stream.js +206 -0
  240. package/dist/util/text.d.ts +65 -0
  241. package/dist/util/text.d.ts.map +1 -0
  242. package/dist/util/text.js +419 -0
  243. package/package.json +70 -0
  244. package/src/adapters/index.ts +11 -0
  245. package/src/app/app.ts +1096 -0
  246. package/src/core/animation.ts +144 -0
  247. package/src/core/clipboard.ts +40 -0
  248. package/src/core/commands.ts +169 -0
  249. package/src/core/components.ts +91 -0
  250. package/src/core/events.ts +95 -0
  251. package/src/core/focus.ts +358 -0
  252. package/src/core/i18n.ts +107 -0
  253. package/src/core/keybindings.ts +184 -0
  254. package/src/core/layers.ts +94 -0
  255. package/src/core/manifest.ts +84 -0
  256. package/src/core/navigation.ts +135 -0
  257. package/src/core/resources.ts +362 -0
  258. package/src/core/services.ts +69 -0
  259. package/src/core/store.ts +640 -0
  260. package/src/core/surfaces.ts +292 -0
  261. package/src/core/syntax.ts +200 -0
  262. package/src/core/when.ts +238 -0
  263. package/src/index.ts +76 -0
  264. package/src/jsx/factory.ts +124 -0
  265. package/src/jsx/intrinsics.ts +99 -0
  266. package/src/jsx/jsx-dev-runtime.ts +23 -0
  267. package/src/jsx/jsx-runtime.ts +76 -0
  268. package/src/render/buffer.ts +318 -0
  269. package/src/render/color.ts +180 -0
  270. package/src/render/diff.ts +99 -0
  271. package/src/render/layout.ts +764 -0
  272. package/src/render/static.ts +290 -0
  273. package/src/runtime/bindings.ts +150 -0
  274. package/src/runtime/hooks.ts +1057 -0
  275. package/src/runtime/instance.ts +284 -0
  276. package/src/runtime/paint.ts +737 -0
  277. package/src/runtime/reconcile.ts +335 -0
  278. package/src/runtime/runtime.ts +47 -0
  279. package/src/runtime/style.ts +205 -0
  280. package/src/themes/borders.ts +95 -0
  281. package/src/themes/builtin.ts +301 -0
  282. package/src/themes/glyphs.ts +101 -0
  283. package/src/themes/index.ts +4 -0
  284. package/src/themes/registry.ts +224 -0
  285. package/src/types/adapter.ts +41 -0
  286. package/src/types/animation.ts +34 -0
  287. package/src/types/app.ts +149 -0
  288. package/src/types/async.ts +35 -0
  289. package/src/types/capabilities.ts +79 -0
  290. package/src/types/cells.ts +79 -0
  291. package/src/types/command.ts +139 -0
  292. package/src/types/component-registry.ts +82 -0
  293. package/src/types/disposable.ts +8 -0
  294. package/src/types/focus.ts +77 -0
  295. package/src/types/geometry.ts +50 -0
  296. package/src/types/graph.ts +174 -0
  297. package/src/types/i18n.ts +25 -0
  298. package/src/types/index.ts +31 -0
  299. package/src/types/input.ts +81 -0
  300. package/src/types/keybinding.ts +51 -0
  301. package/src/types/layer.ts +46 -0
  302. package/src/types/manifest.ts +101 -0
  303. package/src/types/markdown.ts +47 -0
  304. package/src/types/navigation.ts +42 -0
  305. package/src/types/render.ts +86 -0
  306. package/src/types/resource.ts +185 -0
  307. package/src/types/services.ts +28 -0
  308. package/src/types/shell.ts +30 -0
  309. package/src/types/store.ts +148 -0
  310. package/src/types/stream.ts +24 -0
  311. package/src/types/style.ts +205 -0
  312. package/src/types/surface.ts +119 -0
  313. package/src/types/syntax.ts +93 -0
  314. package/src/types/terminal.ts +58 -0
  315. package/src/types/theme.ts +121 -0
  316. package/src/types/when.ts +21 -0
  317. package/src/ui/primitives.ts +118 -0
  318. package/src/ui/screen.ts +41 -0
  319. package/src/util/disposable.ts +49 -0
  320. package/src/util/markdown.ts +225 -0
  321. package/src/util/paths.ts +138 -0
  322. package/src/util/stream.ts +213 -0
  323. package/src/util/text.ts +428 -0
@@ -0,0 +1,301 @@
1
+ import type { ThemeDefinition } from '../types/theme.js';
2
+
3
+ /**
4
+ * The built-in themes.
5
+ *
6
+ * `dark` and `light` are the two bases; everything else extends one of them
7
+ * and restates only what it changes. The three that follow exist because the
8
+ * same application should be able to look like a dense operator console, an
9
+ * airy report or a workbench without a component knowing which - so the
10
+ * difference lives here, in border style and density, not in the catalog.
11
+ */
12
+
13
+ export const DARK: ThemeDefinition = {
14
+ id: 'dark',
15
+ name: 'Dark',
16
+ appearance: 'dark',
17
+ border: 'single',
18
+ density: 'normal',
19
+ colors: {
20
+ canvas: '#0d1117',
21
+ surface: '#161b22',
22
+ surfaceAlt: '#1c2128',
23
+ overlay: '#1c2128',
24
+ border: '#30363d',
25
+ borderStrong: '#484f58',
26
+ borderSubtle: '#21262d',
27
+ text: '#e6edf3',
28
+ muted: '#8b949e',
29
+ subtle: '#6e7681',
30
+ inverted: '#0d1117',
31
+ accent: '#58a6ff',
32
+ primary: '#388bfd',
33
+ secondary: '#a371f7',
34
+ success: '#3fb950',
35
+ warning: '#d29922',
36
+ danger: '#f85149',
37
+ info: '#58a6ff',
38
+ onAccent: '#0d1117',
39
+ onDefault: '#0d1117',
40
+ onPrimary: '#0d1117',
41
+ onSecondary: '#0d1117',
42
+ onMuted: '#0d1117',
43
+ onSuccess: '#0d1117',
44
+ onInfo: '#0d1117',
45
+ onWarning: '#0d1117',
46
+ onDanger: '#0d1117',
47
+ hover: '#1f2937',
48
+ active: '#264466',
49
+ selected: '#1f6feb',
50
+ focus: '#58a6ff',
51
+ disabled: '#484f58',
52
+ scrim: '#010409',
53
+ cursor: '#58a6ff',
54
+ shadow: '#010409',
55
+ },
56
+ spacing: { none: 0, xs: 0, sm: 1, md: 1, lg: 2, xl: 3 },
57
+ };
58
+
59
+ export const LIGHT: ThemeDefinition = {
60
+ id: 'light',
61
+ name: 'Light',
62
+ appearance: 'light',
63
+ border: 'single',
64
+ density: 'normal',
65
+ colors: {
66
+ canvas: '#ffffff',
67
+ surface: '#f6f8fa',
68
+ surfaceAlt: '#eaeef2',
69
+ overlay: '#ffffff',
70
+ border: '#d0d7de',
71
+ borderStrong: '#8c959f',
72
+ borderSubtle: '#eaeef2',
73
+ text: '#1f2328',
74
+ muted: '#656d76',
75
+ subtle: '#8c959f',
76
+ inverted: '#ffffff',
77
+ accent: '#0969da',
78
+ primary: '#0969da',
79
+ secondary: '#8250df',
80
+ success: '#1a7f37',
81
+ warning: '#faca00',
82
+ danger: '#e95c5e',
83
+ info: '#0969da',
84
+ onAccent: '#ffffff',
85
+ onDefault: '#ffffff',
86
+ onPrimary: '#ffffff',
87
+ onSecondary: '#ffffff',
88
+ onMuted: '#ffffff',
89
+ onSuccess: '#ffffff',
90
+ onInfo: '#ffffff',
91
+ onWarning: '#ffffff',
92
+ onDanger: '#ffffff',
93
+ hover: '#eaeef2',
94
+ active: '#dbeafe',
95
+ selected: '#0969da',
96
+ focus: '#0969da',
97
+ disabled: '#8c959f',
98
+ scrim: '#8c959f',
99
+ cursor: '#0969da',
100
+ shadow: '#d0d7de',
101
+ },
102
+ spacing: { none: 0, xs: 0, sm: 1, md: 1, lg: 2, xl: 3 },
103
+ };
104
+
105
+ /** Dense, bordered, high contrast. Every region is a labelled box. */
106
+ export const CONSOLE: ThemeDefinition = {
107
+ id: 'console',
108
+ name: 'Console',
109
+ appearance: 'dark',
110
+ extends: 'dark',
111
+ border: 'single',
112
+ density: 'compact',
113
+ colors: {
114
+ canvas: '#000000',
115
+ surface: '#0a0e14',
116
+ border: '#3b4252',
117
+ borderStrong: '#5e81ac',
118
+ accent: '#88c0d0',
119
+ text: '#d8dee9',
120
+ muted: '#616e88',
121
+ // The accent is teal, so the things made of it are too. Inheriting these
122
+ // from `dark` is what left a teal underline over a blue focus ring and a
123
+ // blue selected row - one theme wearing two accents.
124
+ //
125
+ // The two selection backgrounds are picked rather than derived: `selected`
126
+ // carries `inverted` text so it has to be light, and `active` carries
127
+ // `text` so it has to be dark. Same hue, opposite ends.
128
+ primary: '#88c0d0',
129
+ info: '#88c0d0',
130
+ selected: '#6ba3b2',
131
+ active: '#1e3d47',
132
+ onAccent: '#0a0e14',
133
+ onPrimary: '#0a0e14',
134
+ onInfo: '#0a0e14',
135
+ hover: '#141a21',
136
+ },
137
+ spacing: { none: 0, xs: 0, sm: 0, md: 1, lg: 1, xl: 2 },
138
+ components: {
139
+ Panel: { base: { border: 'single', padding: 0 } },
140
+ Button: { base: { padding: [0, 1] } },
141
+ },
142
+ };
143
+
144
+ /** Borderless. Whitespace and alignment do the separating. */
145
+ export const PAPER: ThemeDefinition = {
146
+ id: 'paper',
147
+ name: 'Paper',
148
+ appearance: 'light',
149
+ extends: 'light',
150
+ border: 'none',
151
+ density: 'airy',
152
+ colors: {
153
+ canvas: '#fffdf9',
154
+ surface: '#fffdf9',
155
+ surfaceAlt: '#f5f2ec',
156
+ border: '#e5e0d8',
157
+ borderSubtle: '#f0ece5',
158
+ subtle: '#c56532',
159
+ text: '#2b2a27',
160
+ muted: '#7a756c',
161
+ accent: '#b4531f',
162
+ primary: '#b4531f',
163
+ // Selection as a tint of the page, not a block of another colour.
164
+ // `paper` inherits from `light`, whose selection is a saturated blue -
165
+ // dropped onto cream it reads as damage rather than as a highlight, and
166
+ // every component that fills with `selected` inherits that.
167
+ hover: '#f7f1e7',
168
+ active: '#efe4d2',
169
+ selected: '#eadcc6',
170
+ inverted: '#2b2a27',
171
+ focus: '#b4531f',
172
+ },
173
+ spacing: { none: 0, xs: 1, sm: 1, md: 2, lg: 3, xl: 4 },
174
+ components: {
175
+ Panel: { base: { border: 'none', padding: [1, 2] } },
176
+ Button: { base: { padding: [0, 2] } },
177
+ },
178
+ };
179
+
180
+ /** Rounded chrome around a persistent frame. */
181
+ export const WORKBENCH: ThemeDefinition = {
182
+ id: 'workbench',
183
+ name: 'Workbench',
184
+ appearance: 'dark',
185
+ extends: 'dark',
186
+ border: 'round',
187
+ density: 'normal',
188
+ colors: {
189
+ canvas: '#1e1e2e',
190
+ surface: '#181825',
191
+ surfaceAlt: '#313244',
192
+ overlay: '#181825',
193
+ border: '#45475a',
194
+ borderStrong: '#585b70',
195
+ text: '#cdd6f4',
196
+ muted: '#a6adc8',
197
+ subtle: '#6c7086',
198
+ accent: '#89b4fa',
199
+ primary: '#89b4fa',
200
+ secondary: '#cba6f7',
201
+ success: '#a6e3a1',
202
+ warning: '#f9e2af',
203
+ danger: '#f38ba8',
204
+ info: '#89dceb',
205
+ hover: '#313244',
206
+ active: '#45475a',
207
+ selected: '#585b70',
208
+ focus: '#89b4fa',
209
+ scrim: '#11111b',
210
+ },
211
+ components: {
212
+ Panel: { base: { border: 'round' } },
213
+ },
214
+ };
215
+
216
+ /** No colour at all. What a `colorDepth: 0` terminal or a pipe gets. */
217
+ export const MONO: ThemeDefinition = {
218
+ id: 'mono',
219
+ name: 'Monochrome',
220
+ appearance: 'dark',
221
+ border: 'ascii',
222
+ density: 'normal',
223
+ colors: {
224
+ canvas: 'default',
225
+ surface: 'default',
226
+ surfaceAlt: 'default',
227
+ overlay: 'default',
228
+ border: 'default',
229
+ borderStrong: 'default',
230
+ borderSubtle: 'default',
231
+ text: 'default',
232
+ muted: 'default',
233
+ subtle: 'default',
234
+ inverted: 'default',
235
+ accent: 'default',
236
+ primary: 'default',
237
+ secondary: 'default',
238
+ success: 'default',
239
+ warning: 'default',
240
+ danger: 'default',
241
+ info: 'default',
242
+ onAccent: 'default',
243
+ onPrimary: 'default',
244
+ onSuccess: 'default',
245
+ onWarning: 'default',
246
+ onDanger: 'default',
247
+ onInfo: 'default',
248
+ hover: 'default',
249
+ active: 'default',
250
+ selected: 'default',
251
+ focus: 'default',
252
+ disabled: 'default',
253
+ scrim: 'default',
254
+ cursor: 'default',
255
+ shadow: 'default',
256
+ },
257
+ };
258
+
259
+ /**
260
+ * Paper, after dark.
261
+ *
262
+ * The same airy, borderless, warm-accented character as `paper` - the point of
263
+ * it is the restraint, not the brightness - with the ink and the page swapped.
264
+ * A warm dark rather than a blue one, so the two read as one family and a
265
+ * reader moving between them is not moving between two different products.
266
+ */
267
+ export const PAPER_DARK: ThemeDefinition = {
268
+ id: 'paper-dark',
269
+ name: 'Paper Dark',
270
+ appearance: 'dark',
271
+ extends: 'dark',
272
+ border: 'none',
273
+ density: 'airy',
274
+ colors: {
275
+ canvas: '#1c1a17',
276
+ surface: '#1c1a17',
277
+ surfaceAlt: '#26231f',
278
+ border: '#3a352e',
279
+ borderSubtle: '#2a2621',
280
+ subtle: '#db8c4c',
281
+ text: '#e8e3d9',
282
+ muted: '#9a9287',
283
+ accent: '#e0873f',
284
+ primary: '#e0873f',
285
+ // The same restraint, the other way up.
286
+ hover: '#26231f',
287
+ active: '#332e27',
288
+ selected: '#3d362c',
289
+ inverted: '#f4efe6',
290
+ focus: '#e0873f',
291
+ },
292
+ spacing: { none: 0, xs: 1, sm: 1, md: 2, lg: 3, xl: 4 },
293
+ components: {
294
+ Panel: { base: { border: 'none', padding: [1, 2] } },
295
+ Button: { base: { padding: [0, 2] } },
296
+ },
297
+ };
298
+
299
+ export const BUILTIN_THEMES: ThemeDefinition[] = [
300
+ DARK, LIGHT, CONSOLE, PAPER, PAPER_DARK, WORKBENCH, MONO,
301
+ ];
@@ -0,0 +1,101 @@
1
+ import type { ThemeGlyphs } from '../types/theme.js';
2
+ import type { UnicodeLevel } from '../types/capabilities.js';
3
+
4
+ /**
5
+ * The glyph vocabulary, named by role.
6
+ *
7
+ * A component asks for `bulletFilled`, never for `'●'`, so the same catalog
8
+ * renders on a full-Unicode terminal and on one that can only do ASCII - and
9
+ * so a theme can restate the vocabulary without touching a component.
10
+ */
11
+ export const FULL_GLYPHS: ThemeGlyphs = {
12
+ bulletFilled: '●',
13
+ bulletHollow: '○',
14
+ bulletHalf: '◌',
15
+ check: '✓',
16
+ cross: '✕',
17
+ warning: '⚠',
18
+ info: 'ℹ',
19
+ chevronRight: '▸',
20
+ chevronDown: '▾',
21
+ chevronLeft: '◂',
22
+ chevronUp: '▴',
23
+ arrowUp: '↑',
24
+ arrowDown: '↓',
25
+ ellipsis: '…',
26
+ search: '⌕',
27
+ radioOn: '●',
28
+ radioOff: '○',
29
+ checkboxOn: '■',
30
+ checkboxOff: '□',
31
+ checkboxMixed: '☒',
32
+ blocks: ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'],
33
+ progressFull: '█',
34
+ progressEmpty: '░',
35
+ progressPartial: ['▏', '▎', '▍', '▌', '▋', '▊', '▉'],
36
+ spinner: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
37
+ caret: '▏',
38
+ separator: '·',
39
+ breadcrumb: '›',
40
+ regionTop: '⬒',
41
+ regionBottom: '⬓',
42
+ regionLeft: '◧',
43
+ regionRight: '◨',
44
+ regionCentre: '▣',
45
+ regionOff: '□',
46
+ };
47
+
48
+ /** Box-drawing and geometric shapes only - no braille, no dingbats. */
49
+ export const BMP_GLYPHS: ThemeGlyphs = {
50
+ ...FULL_GLYPHS,
51
+ check: '√',
52
+ cross: 'x',
53
+ info: 'i',
54
+ spinner: ['|', '/', '-', '\\'],
55
+ checkboxOn: '[x]',
56
+ checkboxOff: '[ ]',
57
+ checkboxMixed: '[-]',
58
+ };
59
+
60
+ export const ASCII_GLYPHS: ThemeGlyphs = {
61
+ bulletFilled: '*',
62
+ bulletHollow: 'o',
63
+ bulletHalf: '+',
64
+ check: 'v',
65
+ cross: 'x',
66
+ warning: '!',
67
+ info: 'i',
68
+ chevronRight: '>',
69
+ chevronDown: 'v',
70
+ chevronLeft: '<',
71
+ chevronUp: '^',
72
+ arrowUp: '^',
73
+ arrowDown: 'v',
74
+ ellipsis: '...',
75
+ search: '/',
76
+ radioOn: '(*)',
77
+ radioOff: '( )',
78
+ checkboxOn: '[x]',
79
+ checkboxOff: '[ ]',
80
+ checkboxMixed: '[-]',
81
+ blocks: ['_', '.', ',', '-', '=', '+', '*', '#'],
82
+ progressFull: '#',
83
+ progressEmpty: '-',
84
+ progressPartial: ['-'],
85
+ spinner: ['|', '/', '-', '\\'],
86
+ caret: '_',
87
+ separator: '-',
88
+ breadcrumb: '>',
89
+ regionTop: '^',
90
+ regionBottom: 'v',
91
+ regionLeft: '[',
92
+ regionRight: ']',
93
+ regionCentre: '+',
94
+ regionOff: '.',
95
+ };
96
+
97
+ export function glyphsFor(unicode: UnicodeLevel): ThemeGlyphs {
98
+ if (unicode === 'ascii') return ASCII_GLYPHS;
99
+ if (unicode === 'bmp') return BMP_GLYPHS;
100
+ return FULL_GLYPHS;
101
+ }
@@ -0,0 +1,4 @@
1
+ export * from './borders.js';
2
+ export * from './glyphs.js';
3
+ export * from './builtin.js';
4
+ export * from './registry.js';
@@ -0,0 +1,224 @@
1
+ import type {
2
+ ResolvedTheme, ThemeDefinition, ThemeGlyphs, ThemeRegistry, ThemeSpacing,
3
+ } from '../types/theme.js';
4
+ import type { Color } from '../types/cells.js';
5
+ import type { BorderChars, BorderStyle, ColorToken, Density, StyleColor } from '../types/style.js';
6
+ import type { Style } from '../types/style.js';
7
+ import type { SyntaxScope } from '../types/syntax.js';
8
+ import type { TerminalCapabilities } from '../types/capabilities.js';
9
+ import type { Disposable } from '../types/disposable.js';
10
+ import { toDisposable } from '../util/disposable.js';
11
+ import { borderCharsFor } from './borders.js';
12
+ import { glyphsFor } from './glyphs.js';
13
+ import { BUILTIN_THEMES } from './builtin.js';
14
+
15
+ const DEFAULT_SPACING: ThemeSpacing = { none: 0, xs: 0, sm: 1, md: 1, lg: 2, xl: 3 };
16
+
17
+ const FALLBACK_COLORS: Record<ColorToken, Color> = {
18
+ canvas: 'default', surface: 'default', surfaceAlt: 'default', overlay: 'default',
19
+ border: 'default', borderStrong: 'default', borderSubtle: 'default',
20
+ text: 'default', muted: 'default', subtle: 'default', inverted: 'default',
21
+ accent: 'default', primary: 'default', secondary: 'default',
22
+ success: 'default', warning: 'default', danger: 'default', info: 'default',
23
+ onDefault: 'default', onMuted: 'default',
24
+ onAccent: 'default', onPrimary: 'default', onSecondary: 'default',
25
+ onSuccess: 'default', onWarning: 'default', onDanger: 'default', onInfo: 'default',
26
+ hover: 'default', active: 'default', selected: 'default', focus: 'default',
27
+ disabled: 'default', scrim: 'default', cursor: 'default', shadow: 'default',
28
+ };
29
+
30
+ /**
31
+ * What each syntax scope means in semantic terms.
32
+ *
33
+ * Naming a token rather than a colour is what makes highlighting survive a
34
+ * theme swap: `paper` gets ink-on-white strings and `console` gets green ones
35
+ * from the same highlighter, and a theme with no palette at all - or a
36
+ * terminal with no colour - resolves the lot to the default foreground.
37
+ */
38
+ const SYNTAX_DEFAULTS: Record<SyntaxScope, ColorToken> = {
39
+ plain: 'text',
40
+ keyword: 'primary',
41
+ string: 'success',
42
+ number: 'info',
43
+ boolean: 'warning',
44
+ null: 'muted',
45
+ comment: 'subtle',
46
+ punctuation: 'muted',
47
+ key: 'accent',
48
+ operator: 'text',
49
+ type: 'secondary',
50
+ function: 'accent',
51
+ tag: 'primary',
52
+ attribute: 'accent',
53
+ regexp: 'warning',
54
+ escape: 'warning',
55
+ invalid: 'danger',
56
+ };
57
+
58
+ /** Tokens that are the accent rather than merely near it. */
59
+ const FOLLOWS_ACCENT = ['focus', 'cursor'] as const;
60
+
61
+ export class Themes implements ThemeRegistry {
62
+ private defs = new Map<string, ThemeDefinition>();
63
+ private cache = new Map<string, ResolvedTheme>();
64
+
65
+ constructor(initial: ThemeDefinition[] = BUILTIN_THEMES) {
66
+ for (const def of initial) this.defs.set(def.id, def);
67
+ }
68
+
69
+ register(def: ThemeDefinition): Disposable {
70
+ this.defs.set(def.id, def);
71
+ this.cache.clear();
72
+ return toDisposable(() => {
73
+ this.defs.delete(def.id);
74
+ this.cache.clear();
75
+ });
76
+ }
77
+
78
+ unregister(id: string): void {
79
+ this.defs.delete(id);
80
+ this.cache.clear();
81
+ }
82
+
83
+ get(id: string): ThemeDefinition | undefined {
84
+ return this.defs.get(id);
85
+ }
86
+
87
+ list(): ThemeDefinition[] {
88
+ return [...this.defs.values()];
89
+ }
90
+
91
+ /**
92
+ * Flatten the `extends` chain, then degrade glyphs and borders to what the
93
+ * terminal can draw. Colour is *not* reduced here - the writer does that per
94
+ * cell against `colorDepth`, so one resolved theme serves every depth.
95
+ */
96
+ resolve(id: string, caps: TerminalCapabilities): ResolvedTheme {
97
+ const key = `${id}:${caps.unicode}:${caps.colorDepth}`;
98
+ const cached = this.cache.get(key);
99
+ if (cached) return cached;
100
+
101
+ const chain: ThemeDefinition[] = [];
102
+ let cursor = this.defs.get(id);
103
+ const seen = new Set<string>();
104
+ while (cursor && !seen.has(cursor.id)) {
105
+ seen.add(cursor.id);
106
+ chain.unshift(cursor);
107
+ cursor = cursor.extends ? this.defs.get(cursor.extends) : undefined;
108
+ }
109
+ if (chain.length === 0) {
110
+ throw new Error(`[textui] no theme registered as "${id}"`);
111
+ }
112
+ const leaf = chain[chain.length - 1] as ThemeDefinition;
113
+
114
+ const colors = { ...FALLBACK_COLORS };
115
+ let spacing = { ...DEFAULT_SPACING };
116
+ let glyphOverrides: Partial<ThemeGlyphs> = {};
117
+ let border: BorderStyle = 'single';
118
+ let density: Density = 'normal';
119
+ const components: Record<string, Record<string, Style>> = {};
120
+ let syntaxOverrides: Partial<Record<SyntaxScope, StyleColor>> = {};
121
+
122
+ for (const def of chain) {
123
+ Object.assign(colors, def.colors);
124
+ // A theme that restates the accent restates what the accent is *for*.
125
+ //
126
+ // `focus` and `cursor` have no identity of their own - a focus ring and
127
+ // a caret are the accent, drawn somewhere. Left to a plain merge they
128
+ // keep whatever the extended theme set, so `console` overrode accent to
129
+ // teal and kept a blue focus ring, and `paper` went warm all over with a
130
+ // blue caret still in it. Accent teal here and blue there, in one theme.
131
+ //
132
+ // Only these two. `selected` and `active` are backgrounds that have to
133
+ // be dim or bright enough for the text on them, which is a decision
134
+ // about contrast rather than hue - a theme picks those itself.
135
+ const stated = def.colors ?? {};
136
+ if (stated.accent !== undefined) {
137
+ for (const token of FOLLOWS_ACCENT) {
138
+ if (stated[token] === undefined) colors[token] = stated.accent;
139
+ }
140
+ }
141
+ if (def.syntax) syntaxOverrides = { ...syntaxOverrides, ...def.syntax };
142
+ if (def.spacing) spacing = { ...spacing, ...def.spacing };
143
+ if (def.glyphs) glyphOverrides = { ...glyphOverrides, ...def.glyphs };
144
+ if (def.border) border = def.border;
145
+ if (def.density) density = def.density;
146
+ for (const [name, variants] of Object.entries(def.components ?? {})) {
147
+ components[name] = { ...components[name], ...variants };
148
+ }
149
+ }
150
+
151
+ // A colourless terminal gets no colour, whatever the theme says.
152
+ if (caps.colorDepth === 0) {
153
+ for (const token of Object.keys(colors) as ColorToken[]) {
154
+ colors[token] = 'default';
155
+ }
156
+ }
157
+
158
+ const syntax = {} as Record<SyntaxScope, Color>;
159
+ for (const [scope, token] of Object.entries(SYNTAX_DEFAULTS) as [SyntaxScope, ColorToken][]) {
160
+ const override = syntaxOverrides[scope];
161
+ syntax[scope] = override === undefined
162
+ ? (colors[token] as Color)
163
+ : ((typeof override === 'string' && override in colors
164
+ ? colors[override as ColorToken]
165
+ : override) as Color);
166
+ }
167
+ // A theme may name a literal colour for a scope, so blanking `colors` is
168
+ // not enough to make a colourless terminal colourless.
169
+ if (caps.colorDepth === 0) {
170
+ for (const scope of Object.keys(syntax) as SyntaxScope[]) syntax[scope] = 'default';
171
+ }
172
+
173
+ const glyphs: ThemeGlyphs = { ...glyphsFor(caps.unicode), ...glyphOverrides };
174
+ const charCache = new Map<BorderStyle, BorderChars>();
175
+
176
+ const resolved: ResolvedTheme = {
177
+ id: leaf.id,
178
+ name: leaf.name,
179
+ appearance: leaf.appearance,
180
+ colors,
181
+ spacing,
182
+ glyphs,
183
+ border,
184
+ density,
185
+ components,
186
+ syntax,
187
+
188
+ color(token: string): Color {
189
+ if (token in colors) return colors[token as ColorToken] as Color;
190
+ // Not a token: a literal colour passed straight through.
191
+ return token as Color;
192
+ },
193
+
194
+ borderChars(style?: BorderStyle): BorderChars {
195
+ const s = style ?? border;
196
+ let chars = charCache.get(s);
197
+ if (!chars) {
198
+ const base = borderCharsFor(s, caps.unicode);
199
+ const custom = leaf.borderChars?.[s];
200
+ chars = custom ? { ...base, ...custom } : base;
201
+ charCache.set(s, chars);
202
+ }
203
+ return chars;
204
+ },
205
+
206
+ styleFor(component: string, variants: string[] = []): Style {
207
+ const entry = components[component];
208
+ if (!entry) return {};
209
+ let out: Style = { ...entry.base };
210
+ for (const variant of variants) {
211
+ if (entry[variant]) out = { ...out, ...entry[variant] };
212
+ }
213
+ return out;
214
+ },
215
+ };
216
+
217
+ this.cache.set(key, resolved);
218
+ return resolved;
219
+ }
220
+ }
221
+
222
+ export function createThemes(initial?: ThemeDefinition[]): Themes {
223
+ return new Themes(initial);
224
+ }
@@ -0,0 +1,41 @@
1
+ import type { Disposable } from './disposable.js';
2
+ import type { ComponentDefinition } from './component-registry.js';
3
+ import type { CommandDefinition } from './command.js';
4
+ import type { KeybindingDefinition } from './keybinding.js';
5
+ import type { HighlighterDefinition } from './syntax.js';
6
+ import type {
7
+ ResourceActionDefinition, ResourceEditorDefinition, ResourceKind,
8
+ ResourceProvider, ResourceViewerDefinition,
9
+ } from './resource.js';
10
+ import type { TextUIApp } from './app.js';
11
+
12
+ /**
13
+ * A resource adapter.
14
+ *
15
+ * Everything one resource type needs, in one value: what it is, where it comes
16
+ * from, what opens it, how it is coloured, and what can be done to it. The
17
+ * registries underneath stay separate - an adapter is a convenience for the
18
+ * author and a unit of undo for the application, not a new mechanism.
19
+ *
20
+ * Registering returns a `Disposable` that removes exactly what was added, so a
21
+ * plugin can be unloaded without the screen keeping a viewer nobody can
22
+ * explain.
23
+ */
24
+ export interface ResourceAdapter {
25
+ id: string;
26
+ title?: string;
27
+ description?: string;
28
+ kinds?: ResourceKind[];
29
+ providers?: ResourceProvider[];
30
+ viewers?: ResourceViewerDefinition[];
31
+ editors?: ResourceEditorDefinition[];
32
+ actions?: ResourceActionDefinition[];
33
+ highlighters?: HighlighterDefinition[];
34
+ /** Commands the adapter contributes. Usually one per action. */
35
+ commands?: CommandDefinition[];
36
+ keybindings?: KeybindingDefinition[];
37
+ /** Components the viewers and editors name. */
38
+ components?: ComponentDefinition[];
39
+ /** Anything the fields above cannot express. */
40
+ register?(app: TextUIApp): Disposable | void;
41
+ }