@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,238 @@
1
+ import type { ContextValue, WhenClause, WhenEngine } from '../types/when.js';
2
+ import type { BindingPath } from '../types/graph.js';
3
+ import type { ReactiveStore } from '../types/store.js';
4
+
5
+ /**
6
+ * A `when` clause is a small expression over store paths and context keys.
7
+ * Chrome that should not exist for this user does not mount, rather than
8
+ * mounting disabled - which is why this evaluates rather than styles.
9
+ *
10
+ * Supported: `&&` `||` `!` `==` `!=` `>` `>=` `<` `<=` `=~`, parentheses,
11
+ * string/number/boolean literals, `$/store/paths` and bare context keys.
12
+ * Deliberately not a language: no calls, no member access, no assignment.
13
+ */
14
+
15
+ type Token =
16
+ | { t: 'path'; v: string }
17
+ | { t: 'ident'; v: string }
18
+ | { t: 'string'; v: string }
19
+ | { t: 'number'; v: number }
20
+ | { t: 'op'; v: string };
21
+
22
+ const OPERATORS = ['&&', '||', '==', '!=', '>=', '<=', '=~', '!', '>', '<', '(', ')'];
23
+
24
+ function tokenize(input: string): Token[] {
25
+ const out: Token[] = [];
26
+ let i = 0;
27
+ while (i < input.length) {
28
+ const c = input[i] as string;
29
+ if (/\s/.test(c)) { i++; continue; }
30
+
31
+ if (c === '$' && input[i + 1] === '/') {
32
+ let j = i + 2;
33
+ while (j < input.length && /[A-Za-z0-9_\-./*]/.test(input[j] as string)) j++;
34
+ out.push({ t: 'path', v: input.slice(i, j) });
35
+ i = j;
36
+ continue;
37
+ }
38
+
39
+ if (c === "'" || c === '"') {
40
+ let j = i + 1;
41
+ let v = '';
42
+ while (j < input.length && input[j] !== c) {
43
+ if (input[j] === '\\') j++;
44
+ v += input[j];
45
+ j++;
46
+ }
47
+ out.push({ t: 'string', v });
48
+ i = j + 1;
49
+ continue;
50
+ }
51
+
52
+ if (/[0-9]/.test(c)) {
53
+ let j = i;
54
+ while (j < input.length && /[0-9.]/.test(input[j] as string)) j++;
55
+ out.push({ t: 'number', v: Number(input.slice(i, j)) });
56
+ i = j;
57
+ continue;
58
+ }
59
+
60
+ const op = OPERATORS.find((o) => input.startsWith(o, i));
61
+ if (op) {
62
+ out.push({ t: 'op', v: op });
63
+ i += op.length;
64
+ continue;
65
+ }
66
+
67
+ if (/[A-Za-z_]/.test(c)) {
68
+ let j = i;
69
+ while (j < input.length && /[A-Za-z0-9_.:-]/.test(input[j] as string)) j++;
70
+ out.push({ t: 'ident', v: input.slice(i, j) });
71
+ i = j;
72
+ continue;
73
+ }
74
+
75
+ throw new SyntaxError(`unexpected character "${c}" in when clause: ${input}`);
76
+ }
77
+ return out;
78
+ }
79
+
80
+ type Resolver = (name: string, isPath: boolean) => unknown;
81
+
82
+ function truthy(v: unknown): boolean {
83
+ if (v === undefined || v === null) return false;
84
+ if (typeof v === 'boolean') return v;
85
+ if (typeof v === 'number') return v !== 0;
86
+ if (typeof v === 'string') return v !== '' && v !== 'false';
87
+ if (Array.isArray(v)) return v.length > 0;
88
+ return true;
89
+ }
90
+
91
+ class Parser {
92
+ private pos = 0;
93
+ constructor(private tokens: Token[], private resolve: Resolver) {}
94
+
95
+ parse(): unknown {
96
+ const v = this.or();
97
+ if (this.pos < this.tokens.length) {
98
+ throw new SyntaxError('trailing tokens in when clause');
99
+ }
100
+ return v;
101
+ }
102
+
103
+ private peek(): Token | undefined {
104
+ return this.tokens[this.pos];
105
+ }
106
+
107
+ private eat(op: string): boolean {
108
+ const t = this.peek();
109
+ if (t && t.t === 'op' && t.v === op) {
110
+ this.pos++;
111
+ return true;
112
+ }
113
+ return false;
114
+ }
115
+
116
+ private or(): unknown {
117
+ let left = this.and();
118
+ while (this.eat('||')) {
119
+ const right = this.and();
120
+ left = truthy(left) || truthy(right);
121
+ }
122
+ return left;
123
+ }
124
+
125
+ private and(): unknown {
126
+ let left = this.comparison();
127
+ while (this.eat('&&')) {
128
+ const right = this.comparison();
129
+ left = truthy(left) && truthy(right);
130
+ }
131
+ return left;
132
+ }
133
+
134
+ private comparison(): unknown {
135
+ const left = this.unary();
136
+ for (const op of ['==', '!=', '>=', '<=', '>', '<', '=~'] as const) {
137
+ if (this.eat(op)) {
138
+ const right = this.unary();
139
+ switch (op) {
140
+ case '==': return looseEquals(left, right);
141
+ case '!=': return !looseEquals(left, right);
142
+ case '>': return Number(left) > Number(right);
143
+ case '>=': return Number(left) >= Number(right);
144
+ case '<': return Number(left) < Number(right);
145
+ case '<=': return Number(left) <= Number(right);
146
+ case '=~': return new RegExp(String(right)).test(String(left ?? ''));
147
+ }
148
+ }
149
+ }
150
+ return left;
151
+ }
152
+
153
+ private unary(): unknown {
154
+ if (this.eat('!')) return !truthy(this.unary());
155
+ return this.primary();
156
+ }
157
+
158
+ private primary(): unknown {
159
+ if (this.eat('(')) {
160
+ const v = this.or();
161
+ if (!this.eat(')')) throw new SyntaxError('missing ")" in when clause');
162
+ return v;
163
+ }
164
+ const t = this.peek();
165
+ if (!t) throw new SyntaxError('unexpected end of when clause');
166
+ this.pos++;
167
+ switch (t.t) {
168
+ case 'string': return t.v;
169
+ case 'number': return t.v;
170
+ case 'path': return this.resolve(t.v, true);
171
+ case 'ident':
172
+ if (t.v === 'true') return true;
173
+ if (t.v === 'false') return false;
174
+ if (t.v === 'null') return null;
175
+ return this.resolve(t.v, false);
176
+ default:
177
+ throw new SyntaxError(`unexpected "${t.v}" in when clause`);
178
+ }
179
+ }
180
+ }
181
+
182
+ function looseEquals(a: unknown, b: unknown): boolean {
183
+ if (a === b) return true;
184
+ if (a == null || b == null) return false;
185
+ if (typeof a === 'number' || typeof b === 'number') return Number(a) === Number(b);
186
+ return String(a) === String(b);
187
+ }
188
+
189
+ export class When implements WhenEngine {
190
+ private context = new Map<string, ContextValue>();
191
+ private cache = new Map<string, Token[]>();
192
+
193
+ constructor(private store: ReactiveStore) {}
194
+
195
+ evaluate(clause: WhenClause | undefined, extra?: Record<string, ContextValue>): boolean {
196
+ if (!clause || clause.trim() === '') return true;
197
+ try {
198
+ let tokens = this.cache.get(clause);
199
+ if (!tokens) {
200
+ tokens = tokenize(clause);
201
+ this.cache.set(clause, tokens);
202
+ }
203
+ const resolve: Resolver = (name, isPath) => {
204
+ if (isPath) return this.store.get(name as BindingPath);
205
+ if (extra && name in extra) return extra[name];
206
+ return this.context.get(name);
207
+ };
208
+ return truthy(new Parser(tokens, resolve).parse());
209
+ } catch (err) {
210
+ // A malformed clause is a programmer error, and hiding chrome silently
211
+ // is worse than showing it - fail loud, render visible.
212
+ console.error(`[textui/when] bad clause "${clause}"`, err);
213
+ return true;
214
+ }
215
+ }
216
+
217
+ dependencies(clause: WhenClause): BindingPath[] {
218
+ try {
219
+ return tokenize(clause)
220
+ .filter((t): t is { t: 'path'; v: string } => t.t === 'path')
221
+ .map((t) => t.v as BindingPath);
222
+ } catch {
223
+ return [];
224
+ }
225
+ }
226
+
227
+ setContext(key: string, value: ContextValue): void {
228
+ this.context.set(key, value);
229
+ }
230
+
231
+ getContext(key: string): ContextValue | undefined {
232
+ return this.context.get(key);
233
+ }
234
+ }
235
+
236
+ export function createWhen(store: ReactiveStore): When {
237
+ return new When(store);
238
+ }
package/src/index.ts ADDED
@@ -0,0 +1,76 @@
1
+ // Contracts
2
+ export * from './types/index.js';
3
+
4
+ // JSX
5
+ export { h, Fragment, defineComponent, componentNameOf, toSerializable, nodeFunction } from './jsx/factory.js';
6
+ export type { BoxProps, TextProps, CanvasProps, SpacerProps, BaseProps } from './jsx/intrinsics.js';
7
+
8
+ // Core
9
+ export { createStore, Store } from './core/store.js';
10
+ export { createEvents, Events } from './core/events.js';
11
+ export { createWhen, When } from './core/when.js';
12
+ export { createComponents, Components } from './core/components.js';
13
+ export { createServices, Services } from './core/services.js';
14
+ export { createCommands, Commands } from './core/commands.js';
15
+ export { createKeybindings, Keybindings, strokeOf, splitStroke, normalizeStroke } from './core/keybindings.js';
16
+ export { createFocus, Focus, GLOBAL_SCOPE } from './core/focus.js';
17
+ export { createLayers, Layers } from './core/layers.js';
18
+ export { createAnimation, Animation } from './core/animation.js';
19
+ export { createI18n, I18nRegistry } from './core/i18n.js';
20
+ export { CLIPBOARD_PATH, readClipboard, writeClipboard } from './core/clipboard.js';
21
+ export { serviceKey } from './types/services.js';
22
+
23
+ // Render
24
+ export { Buffer, createBuffer } from './render/buffer.js';
25
+ export { diffFrame } from './render/diff.js';
26
+ export type { Frame, Run } from './render/diff.js';
27
+ export * from './render/color.js';
28
+ export { layout, measureBox, resolveEdges } from './render/layout.js';
29
+ export type { LayoutBox } from './render/layout.js';
30
+ export { renderOnce, renderToString, createStaticRuntime } from './render/static.js';
31
+ export type { StaticRenderOptions, StaticRenderResult } from './render/static.js';
32
+
33
+ // Runtime
34
+ export type { Runtime } from './runtime/runtime.js';
35
+ export type { Instance } from './runtime/instance.js';
36
+ export { renderTree, collectEffects, disposeTree } from './runtime/reconcile.js';
37
+ export { buildBoxes, paintTree, createRenderContext } from './runtime/paint.js';
38
+ export type { PaintEnv } from './runtime/paint.js';
39
+ export * from './runtime/style.js';
40
+ export * from './runtime/hooks.js';
41
+ export { Screen, SCREEN_COMPONENTS } from './ui/screen.js';
42
+ export type { ScreenProps } from './ui/screen.js';
43
+
44
+ // The application
45
+ export { App, createApp, WRITER_KEY } from './app/app.js';
46
+ export type { FrameWriter } from './app/app.js';
47
+
48
+ // Registries
49
+ export { createSurfaces, createLayouts, createShells, SURFACE_NAMES } from './core/surfaces.js';
50
+ export { createNavigation, Navigation } from './core/navigation.js';
51
+ export { createResources, Resources } from './core/resources.js';
52
+ export {
53
+ createSyntax, Syntax, nameOf, plainTokens, tokensFromSpans, spanHighlighter,
54
+ } from './core/syntax.js';
55
+ export { createManifests, Manifests } from './core/manifest.js';
56
+
57
+ // Themes
58
+ export * from './themes/index.js';
59
+
60
+ // The host primitives.
61
+ //
62
+ // Not the catalog - that is `@textui/widgets`. These four are what the layout
63
+ // engine and the painter actually reason about, so they belong to the runtime
64
+ // that reasons about them. `Box`, `Text` and `Canvas` are the Capitalized
65
+ // spellings, and they are the strings themselves.
66
+ export { PRIMITIVES, Box, Text, Canvas, Spacer } from './ui/primitives.js';
67
+
68
+ // Adapters
69
+ export * from './adapters/index.js';
70
+
71
+ // Utilities
72
+ export * from './util/text.js';
73
+ export * from './util/markdown.js';
74
+ export * from './util/paths.js';
75
+ export * from './util/stream.js';
76
+ export { createBag, toDisposable, disposeAll, NOOP_DISPOSABLE } from './util/disposable.js';
@@ -0,0 +1,124 @@
1
+ import type { ComponentNode, PropValue } from '../types/graph.js';
2
+ import type { FunctionComponent, RenderOutput } from '../types/render.js';
3
+
4
+ /**
5
+ * JSX compiles to component nodes.
6
+ *
7
+ * `<Row gap={1}/>` and `{ component: 'Row', gap: 1 }` are the same value, so a
8
+ * screen written in TypeScript, loaded from JSON or generated by something
9
+ * else all reach the runtime as one shape. That is the whole trick, and it is
10
+ * why every prop should stay serializable: a closure in a node is legal but
11
+ * marks the node as in-process only.
12
+ *
13
+ * The naming rule mirrors HTML's: **lowercase is a host primitive**
14
+ * (`box`, `text`, `canvas`, `spacer`), **Capitalized is a registered
15
+ * component** resolved through the registry at mount time.
16
+ */
17
+
18
+ const NAME = Symbol.for('textui.componentName');
19
+
20
+ interface NamedFunction {
21
+ (props: never): RenderOutput;
22
+ [NAME]?: string;
23
+ displayName?: string;
24
+ name: string;
25
+ }
26
+
27
+ let anonymousCounter = 0;
28
+
29
+ /** The registry name for a function component, assigning one if needed. */
30
+ export function componentNameOf(fn: FunctionComponent): string {
31
+ const f = fn as unknown as NamedFunction;
32
+ const existing = f[NAME];
33
+ if (existing) return existing;
34
+
35
+ const inferred = f.displayName || f.name;
36
+ const name = inferred && inferred !== 'anonymous'
37
+ ? inferred
38
+ : `Anonymous${++anonymousCounter}`;
39
+ f[NAME] = name;
40
+ return name;
41
+ }
42
+
43
+ /** Name a component explicitly. Do this for anything the registry exposes. */
44
+ export function defineComponent<P>(name: string, fn: (props: P) => RenderOutput): (props: P) => RenderOutput {
45
+ (fn as unknown as NamedFunction)[NAME] = name;
46
+ (fn as unknown as NamedFunction).displayName = name;
47
+ return fn;
48
+ }
49
+
50
+ export function isNamed(fn: unknown): boolean {
51
+ return typeof fn === 'function' && (fn as NamedFunction)[NAME] !== undefined;
52
+ }
53
+
54
+ export const Fragment = defineComponent<{ children?: unknown }>('Fragment', (props) => {
55
+ return (props.children ?? null) as RenderOutput;
56
+ });
57
+
58
+ export type ElementType = string | FunctionComponent;
59
+
60
+ /**
61
+ * Build a node. `key` is lifted out of props because it is reconciliation
62
+ * identity, not data the component receives.
63
+ */
64
+ export function h(
65
+ type: ElementType,
66
+ props?: (Record<string, unknown> & { key?: string | number }) | null,
67
+ ...children: unknown[]
68
+ ): ComponentNode {
69
+ const { key, ...rest } = props ?? {};
70
+
71
+ const node: ComponentNode =
72
+ typeof type === 'string'
73
+ ? { component: type, ...rest }
74
+ : { component: componentNameOf(type), ...rest, $meta: { fn: type } };
75
+
76
+ if (children.length > 0) {
77
+ node.children = (children.length === 1 ? children[0] : children) as PropValue;
78
+ }
79
+ if (key !== undefined) node.key = key;
80
+
81
+ return node;
82
+ }
83
+
84
+ /** The function behind a node built from a function component, if any. */
85
+ export function nodeFunction(node: ComponentNode): FunctionComponent | undefined {
86
+ const fn = node.$meta?.fn;
87
+ return typeof fn === 'function' ? (fn as FunctionComponent) : undefined;
88
+ }
89
+
90
+ /**
91
+ * Strip everything unserializable so a node can be persisted or sent. Closures
92
+ * (`$meta.fn`, `{ handler }` actions, function props) are dropped, which is
93
+ * exactly the set of things that only mean something in this process.
94
+ */
95
+ export function toSerializable(node: ComponentNode): ComponentNode {
96
+ const out: ComponentNode = { component: node.component };
97
+ if (node.id !== undefined) out.id = node.id;
98
+ if (node.key !== undefined) out.key = node.key;
99
+
100
+ for (const [k, v] of Object.entries(node)) {
101
+ if (k === 'component' || k === 'id' || k === 'key' || k === '$meta') continue;
102
+ const clean = cleanValue(v);
103
+ if (clean !== undefined) out[k] = clean;
104
+ }
105
+ return out;
106
+ }
107
+
108
+ function cleanValue(v: unknown): unknown {
109
+ if (typeof v === 'function') return undefined;
110
+ if (v === null || typeof v !== 'object') return v;
111
+ if (Array.isArray(v)) {
112
+ const arr = v.map(cleanValue).filter((x) => x !== undefined);
113
+ return arr;
114
+ }
115
+ if ('component' in (v as object)) return toSerializable(v as ComponentNode);
116
+ if ('handler' in (v as object)) return undefined;
117
+
118
+ const out: Record<string, unknown> = {};
119
+ for (const [k, val] of Object.entries(v as Record<string, unknown>)) {
120
+ const clean = cleanValue(val);
121
+ if (clean !== undefined) out[k] = clean;
122
+ }
123
+ return out;
124
+ }
@@ -0,0 +1,99 @@
1
+ import type { Style, StyleInput } from '../types/style.js';
2
+ import type { SemanticRole } from '../types/component-registry.js';
3
+ import type { KeyEvent, MouseEvent } from '../types/input.js';
4
+ import type { PaintSurface, RenderContext } from '../types/render.js';
5
+ import type { Action } from '../types/graph.js';
6
+
7
+ /**
8
+ * Props every node accepts.
9
+ *
10
+ * Style arrives three ways on purpose: the full `style` object for anything
11
+ * stateful, a merged list for composition, and the individual style keys
12
+ * inline as convenience props - `<box gap={1} border="single">` rather than
13
+ * `<box style={{ gap: 1, border: 'single' }}>` for the common case.
14
+ */
15
+ export interface BaseProps extends Style {
16
+ id?: string;
17
+ key?: string | number;
18
+ style?: StyleInput;
19
+
20
+ /** Semantic metadata. Drives the test harness, and future a11y work. */
21
+ role?: SemanticRole;
22
+ label?: string;
23
+ description?: string;
24
+ disabled?: boolean;
25
+ selected?: boolean;
26
+
27
+ /** Participates in tab order. Implied by an interactive role. */
28
+ focusable?: boolean;
29
+ /** The focus scope this node belongs to. */
30
+ focusScope?: string;
31
+ autoFocus?: boolean;
32
+ /**
33
+ * `onKey` runs whether or not this node is focused.
34
+ *
35
+ * For a node that wraps something else and wants the keys that thing
36
+ * declines - a dropdown panel taking left and right while the menu inside it
37
+ * keeps up and down. Without this a handler only runs while focused, which
38
+ * is what focus means.
39
+ */
40
+ global?: boolean;
41
+
42
+ onKey?(event: KeyEvent): boolean | void;
43
+ onFocus?(): void;
44
+ onBlur?(): void;
45
+ onMouse?(event: MouseEvent): boolean | void;
46
+ onClick?: Action | ((event: MouseEvent) => void);
47
+ onHover?(hovering: boolean): void;
48
+
49
+ /** OSC 8 link target, where the terminal supports hyperlinks. */
50
+ link?: string;
51
+
52
+ /** Below this width the node renders `compact`; below that, `minimal`. */
53
+ breakpoints?: { compact?: number; minimal?: number };
54
+ }
55
+
56
+ export interface BoxProps extends BaseProps {
57
+ children?: unknown;
58
+ /** Header text drawn into the top border. Needs a border to land on. */
59
+ title?: string;
60
+ titleAlign?: 'left' | 'center' | 'right';
61
+ /**
62
+ * A second label on the top border, hard against the right.
63
+ *
64
+ * For the short thing that belongs beside a heading rather than under it - a
65
+ * count, a shortcut, a state. It takes its space first and `title` gets what
66
+ * is left, so the two never collide and the title is the one that truncates.
67
+ */
68
+ rightTitle?: string;
69
+ /** Footer text drawn into the bottom border. */
70
+ footer?: string;
71
+ footerAlign?: 'left' | 'center' | 'right';
72
+ /** Scroll offset in cells, when overflow is 'scroll'. */
73
+ scrollTop?: number;
74
+ scrollLeft?: number;
75
+ }
76
+
77
+ export interface TextProps extends BaseProps {
78
+ children?: unknown;
79
+ /** The string to draw. `children` is accepted as a shorthand. */
80
+ content?: string;
81
+ /** Where to cut when the text does not fit. */
82
+ truncate?: 'end' | 'start' | 'middle' | false;
83
+ ellipsis?: string;
84
+ }
85
+
86
+ export interface CanvasProps extends BaseProps {
87
+ /**
88
+ * Paint directly. The escape hatch charts and gauges use; everything else
89
+ * should compose `box` and `text` so the layout engine can reason about it.
90
+ */
91
+ draw(surface: PaintSurface, ctx: RenderContext): void;
92
+ /** Intrinsic size when the style does not fix one. */
93
+ intrinsic?: { width?: number; height?: number };
94
+ }
95
+
96
+ export interface SpacerProps extends BaseProps {
97
+ /** Cells to take. Unset means "take whatever is left", the same as `flex: 1`. */
98
+ size?: number;
99
+ }
@@ -0,0 +1,23 @@
1
+ import type { ComponentNode } from '../types/graph.js';
2
+ import type { FunctionComponent } from '../types/render.js';
3
+ import { jsx, Fragment, type JSX } from './jsx-runtime.js';
4
+
5
+ export { Fragment };
6
+ export type { JSX };
7
+
8
+ export function jsxDEV(
9
+ type: string | FunctionComponent,
10
+ props: Record<string, unknown> & { children?: unknown },
11
+ key: string | number | undefined,
12
+ _isStatic: boolean,
13
+ source?: { fileName: string; lineNumber: number; columnNumber: number },
14
+ ): ComponentNode {
15
+ const node = jsx(type, props, key);
16
+ if (source) {
17
+ node.$meta = {
18
+ ...node.$meta,
19
+ source: { file: source.fileName, line: source.lineNumber, column: source.columnNumber },
20
+ };
21
+ }
22
+ return node;
23
+ }
@@ -0,0 +1,76 @@
1
+ import type { ComponentNode } from '../types/graph.js';
2
+ import type { FunctionComponent } from '../types/render.js';
3
+ import type { RenderOutput } from '../types/render.js';
4
+ import { componentNameOf, Fragment } from './factory.js';
5
+ import type { BoxProps, CanvasProps, SpacerProps, TextProps } from './intrinsics.js';
6
+
7
+ export { Fragment };
8
+ export type { BoxProps, TextProps, CanvasProps, SpacerProps };
9
+
10
+ type Props = Record<string, unknown> & { children?: unknown };
11
+
12
+ function build(
13
+ type: string | FunctionComponent,
14
+ props: Props,
15
+ key?: string | number,
16
+ ): ComponentNode {
17
+ const node: ComponentNode =
18
+ typeof type === 'string'
19
+ ? { component: type, ...props }
20
+ : { component: componentNameOf(type), ...props, $meta: { fn: type } };
21
+ if (key !== undefined) node.key = key;
22
+ return node;
23
+ }
24
+
25
+ export function jsx(
26
+ type: string | FunctionComponent,
27
+ props: Props,
28
+ key?: string | number,
29
+ ): ComponentNode {
30
+ return build(type, props, key);
31
+ }
32
+
33
+ export function jsxs(
34
+ type: string | FunctionComponent,
35
+ props: Props,
36
+ key?: string | number,
37
+ ): ComponentNode {
38
+ return build(type, props, key);
39
+ }
40
+
41
+ // TypeScript looks for a `JSX` namespace on the jsx-runtime module by name.
42
+ // This is the one place a namespace is not a style choice.
43
+ // eslint-disable-next-line @typescript-eslint/no-namespace
44
+ export declare namespace JSX {
45
+ type Element = ComponentNode;
46
+
47
+ /**
48
+ * What may appear as a JSX tag.
49
+ *
50
+ * A component returns `RenderOutput` - a node, a list, a string, or nothing -
51
+ * rather than exactly one element. Without this, TypeScript insists every
52
+ * component return a single node, and `return null` stops compiling.
53
+ */
54
+ type ElementType = string | ((props: never) => RenderOutput);
55
+
56
+ interface ElementChildrenAttribute {
57
+ children: unknown;
58
+ }
59
+
60
+ /**
61
+ * Lowercase names are host primitives - the only three shapes the layout
62
+ * engine reasons about, plus a spacer. Everything Capitalized is a component
63
+ * you import, which is what gives it prop types.
64
+ */
65
+ interface IntrinsicElements {
66
+ box: BoxProps;
67
+ text: TextProps;
68
+ canvas: CanvasProps;
69
+ spacer: SpacerProps;
70
+ }
71
+
72
+ interface IntrinsicAttributes {
73
+ key?: string | number;
74
+ id?: string;
75
+ }
76
+ }