@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,593 @@
1
+ import { toDisposable } from '../util/disposable.js';
2
+ import { ancestorKeys, hasWildcard, isDescendantKey, matchKey, pathKey, PathError, } from '../util/paths.js';
3
+ /**
4
+ * The store is one tree, and it is the only place state lives. A component
5
+ * that keeps a copy in its own state has created a second answer to a question
6
+ * the store already answers.
7
+ */
8
+ export class Store {
9
+ root = {};
10
+ subs = new Set();
11
+ computedByKey = new Map();
12
+ computedDeps = new Map();
13
+ providers = new Map();
14
+ schemas = new Map();
15
+ persistence = new Set();
16
+ persistTimers = new Map();
17
+ batchDepth = 0;
18
+ pending = new Map();
19
+ disposed = false;
20
+ /** Thrown-away errors from listeners would hide bugs; collect and report. */
21
+ onError = (err, context) => {
22
+ console.error(`[textui/store] ${context}`, err);
23
+ };
24
+ // ---------------------------------------------------------------- reading
25
+ get(path) {
26
+ const key = pathKey(path);
27
+ this.touchProvider(key);
28
+ return this.readKey(key);
29
+ }
30
+ read(path, fallback) {
31
+ const v = this.get(path);
32
+ return v === undefined ? fallback : v;
33
+ }
34
+ has(path) {
35
+ return this.readKey(pathKey(path)) !== undefined;
36
+ }
37
+ readKey(key) {
38
+ if (key === '')
39
+ return this.root;
40
+ const segs = key.split('/');
41
+ let node = this.root;
42
+ for (const seg of segs) {
43
+ if (node === null || node === undefined)
44
+ return undefined;
45
+ if (Array.isArray(node)) {
46
+ const i = Number(seg);
47
+ if (!Number.isInteger(i))
48
+ return undefined;
49
+ node = node[i];
50
+ continue;
51
+ }
52
+ if (typeof node !== 'object')
53
+ return undefined;
54
+ node = node[seg];
55
+ }
56
+ return node;
57
+ }
58
+ // ---------------------------------------------------------------- writing
59
+ set(path, value) {
60
+ if (hasWildcard(path)) {
61
+ throw new PathError('wildcards are legal in subscriptions only', path);
62
+ }
63
+ const key = pathKey(path);
64
+ this.writeKey(key, value);
65
+ }
66
+ update(path, fn) {
67
+ this.set(path, fn(this.get(path)));
68
+ }
69
+ patch(path, partial) {
70
+ const current = this.get(path);
71
+ const base = current && typeof current === 'object' && !Array.isArray(current) ? current : {};
72
+ this.set(path, { ...base, ...partial });
73
+ }
74
+ patchMany(entries) {
75
+ this.batch(() => {
76
+ for (const [path, value] of Object.entries(entries)) {
77
+ this.set(path, value);
78
+ }
79
+ });
80
+ }
81
+ delete(path) {
82
+ const key = pathKey(path);
83
+ const segs = key.split('/');
84
+ const last = segs.pop();
85
+ const parent = segs.length === 0 ? this.root : this.readKey(segs.join('/'));
86
+ if (parent === null || typeof parent !== 'object')
87
+ return;
88
+ const previous = parent[last];
89
+ if (previous === undefined && !(last in parent))
90
+ return;
91
+ if (Array.isArray(parent))
92
+ parent.splice(Number(last), 1);
93
+ else
94
+ delete parent[last];
95
+ this.enqueue({ path: `$/${key}`, value: undefined, previous });
96
+ this.flushIfIdle();
97
+ }
98
+ writeKey(key, value) {
99
+ if (this.disposed)
100
+ return;
101
+ if (key === '')
102
+ throw new PathError('cannot write the store root', '$/');
103
+ const schema = this.schemas.get(key);
104
+ if (schema) {
105
+ const problem = schema.validate(value);
106
+ if (problem)
107
+ throw new PathError(`schema rejected value (${problem})`, `$/${key}`);
108
+ }
109
+ const segs = key.split('/');
110
+ const last = segs.pop();
111
+ let node = this.root;
112
+ for (const seg of segs) {
113
+ let next = node[seg];
114
+ if (next === null || typeof next !== 'object') {
115
+ next = {};
116
+ node[seg] = next;
117
+ }
118
+ node = next;
119
+ }
120
+ const previous = node[last];
121
+ if (previous === value)
122
+ return;
123
+ node[last] = value;
124
+ this.enqueue({ path: `$/${key}`, value, previous });
125
+ this.flushIfIdle();
126
+ }
127
+ // --------------------------------------------------------------- batching
128
+ batch(fn) {
129
+ this.batchDepth++;
130
+ try {
131
+ return fn();
132
+ }
133
+ finally {
134
+ this.batchDepth--;
135
+ this.flushIfIdle();
136
+ }
137
+ }
138
+ enqueue(change) {
139
+ const key = pathKey(change.path);
140
+ const existing = this.pending.get(key);
141
+ // Keep the oldest `previous` so a batch reports one net change.
142
+ this.pending.set(key, existing ? { ...change, previous: existing.previous } : change);
143
+ }
144
+ flushIfIdle() {
145
+ if (this.batchDepth > 0 || this.pending.size === 0)
146
+ return;
147
+ // Recomputing may enqueue more; drain until quiet, with a runaway guard.
148
+ let rounds = 0;
149
+ while (this.pending.size > 0) {
150
+ if (++rounds > 50) {
151
+ this.onError(new Error('computed paths did not settle after 50 rounds'), 'flush');
152
+ this.pending.clear();
153
+ return;
154
+ }
155
+ const changes = [...this.pending.values()];
156
+ this.pending.clear();
157
+ this.batchDepth++;
158
+ try {
159
+ for (const change of changes)
160
+ this.recomputeDependents(pathKey(change.path));
161
+ }
162
+ finally {
163
+ this.batchDepth--;
164
+ }
165
+ for (const change of changes)
166
+ this.notify(change);
167
+ for (const change of changes)
168
+ this.schedulePersist(pathKey(change.path));
169
+ }
170
+ }
171
+ notify(change) {
172
+ const key = pathKey(change.path);
173
+ for (const sub of [...this.subs]) {
174
+ if (!this.subMatches(sub, key))
175
+ continue;
176
+ try {
177
+ sub.fn(this.readKey(sub.wildcard ? key : sub.key), change);
178
+ }
179
+ catch (err) {
180
+ this.onError(err, `subscriber of $/${sub.key}`);
181
+ }
182
+ }
183
+ }
184
+ subMatches(sub, changedKey) {
185
+ if (sub.wildcard)
186
+ return matchKey(changedKey, sub.key, sub.subtree);
187
+ if (sub.key === changedKey)
188
+ return true;
189
+ // A write below an exact subscription still changes that object's contents.
190
+ if (isDescendantKey(changedKey, sub.key))
191
+ return true;
192
+ // A write above it may have replaced the subtree the subscriber reads.
193
+ if (isDescendantKey(sub.key, changedKey))
194
+ return true;
195
+ return sub.subtree && ancestorKeys(changedKey).includes(sub.key);
196
+ }
197
+ // ---------------------------------------------------------- subscriptions
198
+ subscribe(path, fn, options = {}) {
199
+ const key = pathKey(path);
200
+ const sub = {
201
+ key,
202
+ subtree: options.subtree ?? false,
203
+ wildcard: hasWildcard(key),
204
+ fn,
205
+ };
206
+ this.subs.add(sub);
207
+ this.touchProvider(key);
208
+ if (options.immediate) {
209
+ try {
210
+ const value = this.readKey(key);
211
+ fn(value, { path, value, previous: undefined });
212
+ }
213
+ catch (err) {
214
+ this.onError(err, `immediate subscriber of ${path}`);
215
+ }
216
+ }
217
+ return toDisposable(() => {
218
+ this.subs.delete(sub);
219
+ this.maybeUnloadProvider(key);
220
+ });
221
+ }
222
+ subscriberCount(scope) {
223
+ let n = 0;
224
+ for (const sub of this.subs) {
225
+ if (sub.key === scope || sub.key.startsWith(scope + '/'))
226
+ n++;
227
+ }
228
+ return n;
229
+ }
230
+ clearScope(scope) {
231
+ const key = pathKey(scope.startsWith('$/') ? scope : `$/${scope}`);
232
+ const previous = this.readKey(key);
233
+ if (previous === undefined)
234
+ return;
235
+ const segs = key.split('/');
236
+ const last = segs.pop();
237
+ const parent = segs.length === 0 ? this.root : this.readKey(segs.join('/'));
238
+ if (parent && typeof parent === 'object') {
239
+ delete parent[last];
240
+ }
241
+ this.enqueue({ path: `$/${key}`, value: undefined, previous });
242
+ this.flushIfIdle();
243
+ }
244
+ // -------------------------------------------------------------- computed
245
+ computed(path, def) {
246
+ const key = pathKey(path);
247
+ const deps = def.from.map((p) => pathKey(p));
248
+ const select = typeof def.select === 'string'
249
+ ? compileSelect(def.select, def.from)
250
+ : def.select;
251
+ const entry = {
252
+ key,
253
+ deps,
254
+ compute: () => {
255
+ const values = {};
256
+ for (let i = 0; i < def.from.length; i++) {
257
+ const p = def.from[i];
258
+ values[p] = this.readKey(deps[i]);
259
+ }
260
+ return select(values);
261
+ },
262
+ };
263
+ this.computedByKey.set(key, entry);
264
+ for (const dep of deps) {
265
+ let set = this.computedDeps.get(dep);
266
+ if (!set) {
267
+ set = new Set();
268
+ this.computedDeps.set(dep, set);
269
+ }
270
+ set.add(key);
271
+ }
272
+ this.recompute(entry);
273
+ return toDisposable(() => {
274
+ this.computedByKey.delete(key);
275
+ for (const dep of deps)
276
+ this.computedDeps.get(dep)?.delete(key);
277
+ });
278
+ }
279
+ recompute(entry) {
280
+ try {
281
+ this.writeKey(entry.key, entry.compute());
282
+ }
283
+ catch (err) {
284
+ this.onError(err, `computed $/${entry.key}`);
285
+ }
286
+ }
287
+ recomputeDependents(changedKey) {
288
+ for (const [dep, keys] of this.computedDeps) {
289
+ const affected = dep === changedKey ||
290
+ isDescendantKey(changedKey, dep) ||
291
+ isDescendantKey(dep, changedKey);
292
+ if (!affected)
293
+ continue;
294
+ for (const key of keys) {
295
+ const entry = this.computedByKey.get(key);
296
+ if (entry)
297
+ this.recompute(entry);
298
+ }
299
+ }
300
+ }
301
+ // ------------------------------------------------------------ collections
302
+ collection(path) {
303
+ const read = () => {
304
+ const v = this.get(path);
305
+ return Array.isArray(v) ? v : [];
306
+ };
307
+ const write = (items) => this.set(path, items);
308
+ return {
309
+ all: () => read(),
310
+ at: (index) => read()[index],
311
+ find: (pred) => read().find(pred),
312
+ append: (...items) => write([...read(), ...items]),
313
+ prepend: (...items) => write([...items, ...read()]),
314
+ insertAt: (index, ...items) => {
315
+ const next = read();
316
+ next.splice(index, 0, ...items);
317
+ write(next);
318
+ },
319
+ removeAt: (index) => {
320
+ const next = read();
321
+ if (index < 0 || index >= next.length)
322
+ return;
323
+ next.splice(index, 1);
324
+ write(next);
325
+ },
326
+ remove: (pred) => {
327
+ const items = read();
328
+ const kept = items.filter((item, i) => !pred(item, i));
329
+ const removed = items.length - kept.length;
330
+ if (removed > 0)
331
+ write(kept);
332
+ return removed;
333
+ },
334
+ update: (pred, patch) => {
335
+ const items = read();
336
+ let changed = 0;
337
+ const next = items.map((item, i) => {
338
+ if (!pred(item, i))
339
+ return item;
340
+ changed++;
341
+ return typeof patch === 'function'
342
+ ? patch(item)
343
+ : { ...item, ...patch };
344
+ });
345
+ if (changed > 0)
346
+ write(next);
347
+ return changed;
348
+ },
349
+ replace: (items) => write([...items]),
350
+ filter: (pred) => read().filter(pred),
351
+ clear: () => write([]),
352
+ cap: (n) => {
353
+ const items = read();
354
+ if (items.length > n)
355
+ write(items.slice(items.length - n));
356
+ },
357
+ get length() {
358
+ return read().length;
359
+ },
360
+ };
361
+ }
362
+ // -------------------------------------------------------------- providers
363
+ registerDataProvider(def) {
364
+ const key = pathKey(def.namespace.startsWith('$/') ? def.namespace : `$/${def.namespace}`);
365
+ const entry = { def, loaded: false, loading: null, unloadTimer: null };
366
+ this.providers.set(key, entry);
367
+ if (def.load === 'eager')
368
+ void this.loadProvider(key);
369
+ return toDisposable(() => {
370
+ if (entry.unloadTimer)
371
+ clearTimeout(entry.unloadTimer);
372
+ void def.provider.unload?.(this);
373
+ this.providers.delete(key);
374
+ });
375
+ }
376
+ listDataProviders() {
377
+ return [...this.providers.values()].map((e) => e.def);
378
+ }
379
+ /** A read or subscribe below a provider's namespace is what loads it. */
380
+ touchProvider(key) {
381
+ for (const [ns, entry] of this.providers) {
382
+ if (key !== ns && !isDescendantKey(key, ns))
383
+ continue;
384
+ if (entry.unloadTimer) {
385
+ clearTimeout(entry.unloadTimer);
386
+ entry.unloadTimer = null;
387
+ }
388
+ if (!entry.loaded && !entry.loading)
389
+ void this.loadProvider(ns);
390
+ }
391
+ }
392
+ async loadProvider(ns) {
393
+ const entry = this.providers.get(ns);
394
+ if (!entry || entry.loaded || entry.loading)
395
+ return;
396
+ const run = (async () => {
397
+ try {
398
+ await entry.def.provider.load(this);
399
+ entry.loaded = true;
400
+ }
401
+ catch (err) {
402
+ this.onError(err, `data provider ${entry.def.namespace}`);
403
+ }
404
+ finally {
405
+ entry.loading = null;
406
+ }
407
+ })();
408
+ entry.loading = run;
409
+ await run;
410
+ }
411
+ maybeUnloadProvider(key) {
412
+ for (const [ns, entry] of this.providers) {
413
+ if (key !== ns && !isDescendantKey(key, ns))
414
+ continue;
415
+ const { unloadAfter } = entry.def;
416
+ if (!unloadAfter || !entry.loaded)
417
+ continue;
418
+ if (this.subscriberCount(ns) > 0)
419
+ continue;
420
+ if (entry.unloadTimer)
421
+ clearTimeout(entry.unloadTimer);
422
+ entry.unloadTimer = setTimeout(() => {
423
+ entry.unloadTimer = null;
424
+ if (this.subscriberCount(ns) > 0)
425
+ return;
426
+ void entry.def.provider.unload?.(this);
427
+ entry.loaded = false;
428
+ }, unloadAfter);
429
+ entry.unloadTimer.unref?.();
430
+ }
431
+ }
432
+ // ---------------------------------------------------- schemas, persistence
433
+ registerSchema(schema) {
434
+ const key = pathKey(schema.path);
435
+ this.schemas.set(key, schema);
436
+ if (schema.initial !== undefined && this.readKey(key) === undefined) {
437
+ this.writeKey(key, schema.initial);
438
+ }
439
+ return toDisposable(() => this.schemas.delete(key));
440
+ }
441
+ registerPersistence(adapter) {
442
+ this.persistence.add(adapter);
443
+ return toDisposable(() => {
444
+ const timer = this.persistTimers.get(adapter.id);
445
+ if (timer)
446
+ clearTimeout(timer);
447
+ this.persistTimers.delete(adapter.id);
448
+ this.persistence.delete(adapter);
449
+ });
450
+ }
451
+ async hydrate() {
452
+ for (const adapter of this.persistence) {
453
+ try {
454
+ const entries = await adapter.read();
455
+ this.batch(() => {
456
+ for (const [path, value] of Object.entries(entries)) {
457
+ this.set((path.startsWith('$/') ? path : `$/${path}`), value);
458
+ }
459
+ });
460
+ }
461
+ catch (err) {
462
+ this.onError(err, `persistence adapter ${adapter.id}`);
463
+ }
464
+ }
465
+ }
466
+ schedulePersist(changedKey) {
467
+ for (const adapter of this.persistence) {
468
+ const owns = adapter.paths.some((p) => {
469
+ const key = adapterKey(p);
470
+ return p.endsWith('/')
471
+ ? changedKey === key || isDescendantKey(changedKey, key)
472
+ : changedKey === key;
473
+ });
474
+ if (!owns)
475
+ continue;
476
+ const existing = this.persistTimers.get(adapter.id);
477
+ if (existing)
478
+ clearTimeout(existing);
479
+ const timer = setTimeout(() => {
480
+ this.persistTimers.delete(adapter.id);
481
+ const entries = {};
482
+ for (const p of adapter.paths) {
483
+ const key = adapterKey(p);
484
+ entries[`$/${key}`] = this.readKey(key);
485
+ }
486
+ try {
487
+ void adapter.write(entries);
488
+ }
489
+ catch (err) {
490
+ this.onError(err, `persistence adapter ${adapter.id}`);
491
+ }
492
+ }, adapter.debounceMs ?? 250);
493
+ timer.unref?.();
494
+ this.persistTimers.set(adapter.id, timer);
495
+ }
496
+ }
497
+ // --------------------------------------------------------------- snapshot
498
+ snapshot(scope) {
499
+ if (!scope)
500
+ return structuredClone(this.root);
501
+ const key = pathKey(scope.startsWith('$/') ? scope : `$/${scope}`);
502
+ const value = this.readKey(key);
503
+ return value === undefined ? {} : structuredClone({ [key]: value });
504
+ }
505
+ restore(snapshot) {
506
+ this.batch(() => {
507
+ for (const [key, value] of Object.entries(snapshot)) {
508
+ this.set(`$/${pathKey(key.startsWith('$/') ? key : `$/${key}`)}`, value);
509
+ }
510
+ });
511
+ }
512
+ dispose() {
513
+ if (this.disposed)
514
+ return;
515
+ this.disposed = true;
516
+ for (const timer of this.persistTimers.values())
517
+ clearTimeout(timer);
518
+ this.persistTimers.clear();
519
+ for (const entry of this.providers.values()) {
520
+ if (entry.unloadTimer)
521
+ clearTimeout(entry.unloadTimer);
522
+ try {
523
+ void entry.def.provider.unload?.(this);
524
+ }
525
+ catch {
526
+ /* teardown is best effort */
527
+ }
528
+ }
529
+ this.providers.clear();
530
+ this.subs.clear();
531
+ this.computedByKey.clear();
532
+ this.computedDeps.clear();
533
+ this.root = {};
534
+ }
535
+ }
536
+ /**
537
+ * A computed defined as data. Deliberately tiny: `sum`, `count`, `length`,
538
+ * `first`, `join`, or a path reference. Anything more is a function.
539
+ */
540
+ function compileSelect(expr, from) {
541
+ const [op, arg] = expr.split(':');
542
+ const pick = (values) => values[arg ?? from[0]];
543
+ switch (op) {
544
+ case 'length':
545
+ case 'count':
546
+ return (v) => {
547
+ const x = pick(v);
548
+ return Array.isArray(x) ? x.length : x == null ? 0 : Object.keys(x).length;
549
+ };
550
+ case 'sum':
551
+ return (v) => {
552
+ const x = pick(v);
553
+ return Array.isArray(x) ? x.reduce((a, b) => a + Number(b || 0), 0) : 0;
554
+ };
555
+ case 'first':
556
+ return (v) => {
557
+ const x = pick(v);
558
+ return Array.isArray(x) ? x[0] : undefined;
559
+ };
560
+ case 'last':
561
+ return (v) => {
562
+ const x = pick(v);
563
+ return Array.isArray(x) ? x[x.length - 1] : undefined;
564
+ };
565
+ case 'not':
566
+ return (v) => !pick(v);
567
+ case 'join':
568
+ return (v) => {
569
+ const x = pick(v);
570
+ return Array.isArray(x) ? x.join(arg ?? ', ') : '';
571
+ };
572
+ case 'concat':
573
+ return (v) => from.map((p) => String(v[p] ?? '')).join('');
574
+ default:
575
+ // A bare path reference: `$/a/b`.
576
+ return (v) => v[expr];
577
+ }
578
+ }
579
+ export function createStore() {
580
+ return new Store();
581
+ }
582
+ /**
583
+ * The key an adapter's path stands for.
584
+ *
585
+ * A trailing slash means "and everything under it", so it has to come off
586
+ * before the path is turned into a key: `segments` keeps the empty piece it
587
+ * leaves behind, so `$/a/b/` became the key `a/b/` and matched neither `a/b`
588
+ * nor anything below it. Every subtree adapter ever registered was inert.
589
+ */
590
+ function adapterKey(path) {
591
+ const bare = path.endsWith('/') ? path.slice(0, -1) : path;
592
+ return pathKey(bare.startsWith('$/') ? bare : `$/${bare}`);
593
+ }
@@ -0,0 +1,81 @@
1
+ import type { LayoutDefinition, LayoutRegistry, Mount, SurfaceName, SurfaceRegistry, SurfaceState } from '../types/surface.js';
2
+ import type { ShellDefinition, ShellRegistry } from '../types/shell.js';
3
+ import type { ReactiveStore } from '../types/store.js';
4
+ import type { WhenEngine } from '../types/when.js';
5
+ import type { ResourceRegistry } from '../types/resource.js';
6
+ import type { Disposable } from '../types/disposable.js';
7
+ /**
8
+ * The surfaces the shipped shells use.
9
+ *
10
+ * A convenience, not a constraint: it is what gets seeded at boot so the
11
+ * built-in shells have state to read. An application is free to mount onto a
12
+ * name that is not here, and nothing checks.
13
+ */
14
+ export declare const SURFACE_NAMES: SurfaceName[];
15
+ /**
16
+ * Surfaces, mounts and their state.
17
+ *
18
+ * A surface is whatever the application calls a region. The runtime keeps
19
+ * state per name and never validates one: mounting onto `lateral1` works
20
+ * exactly as well as mounting onto `sidebar`, and needs no registration. The
21
+ * shipped names are seeded at boot only so the shipped shells have something
22
+ * to read.
23
+ *
24
+ * Which layout a surface uses is store state rather than code, which is what
25
+ * makes it switchable at runtime and persistable per user.
26
+ */
27
+ export declare class Surfaces implements SurfaceRegistry {
28
+ private deps;
29
+ private byKey;
30
+ /** Subscriptions to the paths a mount's `when` clause reads. */
31
+ private conditions;
32
+ constructor(deps: {
33
+ store: ReactiveStore;
34
+ when: WhenEngine;
35
+ resources(): ResourceRegistry;
36
+ onChange(): void;
37
+ });
38
+ private id;
39
+ private publish;
40
+ open(mount: Mount): Disposable;
41
+ close(surface: SurfaceName, key: string): void;
42
+ closeAll(surface: SurfaceName): void;
43
+ get(surface: SurfaceName, key: string): Mount | undefined;
44
+ private watchCondition;
45
+ private unwatchCondition;
46
+ /** Every mount on a surface, conditional or not. */
47
+ all(surface: SurfaceName): Mount[];
48
+ /** Mounts whose `when` clause passes, in order. */
49
+ mounts(surface: SurfaceName): Mount[];
50
+ activate(surface: SurfaceName, key: string): void;
51
+ state(surface: SurfaceName): SurfaceState;
52
+ setState(surface: SurfaceName, patch: Partial<SurfaceState>): void;
53
+ /**
54
+ * Resource-level open: ask the registry which viewer handles this kind, then
55
+ * mount it. The caller never names a component, which is what lets a new
56
+ * resource type arrive with its own viewer and work everywhere at once.
57
+ */
58
+ openResource(uri: string, options?: {
59
+ surface?: SurfaceName;
60
+ viewerId?: string;
61
+ mode?: 'view' | 'edit';
62
+ }): Promise<Disposable | null>;
63
+ }
64
+ export declare class Layouts implements LayoutRegistry {
65
+ private defs;
66
+ register(def: LayoutDefinition): Disposable;
67
+ get(name: string): LayoutDefinition | undefined;
68
+ list(surface?: SurfaceName): LayoutDefinition[];
69
+ }
70
+ export declare class Shells implements ShellRegistry {
71
+ private defs;
72
+ register(def: ShellDefinition): Disposable;
73
+ get(id: string): ShellDefinition | undefined;
74
+ list(): ShellDefinition[];
75
+ /** Shells that fit, largest minimum first - the richest one that works. */
76
+ suitable(width: number, height: number): ShellDefinition[];
77
+ }
78
+ export declare function createSurfaces(deps: ConstructorParameters<typeof Surfaces>[0]): Surfaces;
79
+ export declare function createLayouts(): Layouts;
80
+ export declare function createShells(): Shells;
81
+ //# sourceMappingURL=surfaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"surfaces.d.ts","sourceRoot":"","sources":["../../src/core/surfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAAc,cAAc,EAAE,KAAK,EAAE,WAAW,EAChE,eAAe,EAAE,YAAY,EAC9B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGzD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,EAAE,WAAW,EAEtC,CAAC;AA2CF;;;;;;;;;;;GAWG;AACH,qBAAa,QAAS,YAAW,eAAe;IAM5C,OAAO,CAAC,IAAI;IALd,OAAO,CAAC,KAAK,CAA4B;IACzC,gEAAgE;IAChE,OAAO,CAAC,UAAU,CAAmC;gBAG3C,IAAI,EAAE;QACZ,KAAK,EAAE,aAAa,CAAC;QACrB,IAAI,EAAE,UAAU,CAAC;QACjB,SAAS,IAAI,gBAAgB,CAAC;QAC9B,QAAQ,IAAI,IAAI,CAAC;KAClB;IASH,OAAO,CAAC,EAAE;IAIV,OAAO,CAAC,OAAO;IAUf,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,UAAU;IAiB9B,KAAK,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAa9C,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAUpC,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAIzD,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,gBAAgB;IAKxB,oDAAoD;IACpD,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,KAAK,EAAE;IAMlC,mDAAmD;IACnD,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,KAAK,EAAE;IAMrC,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAKjD,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,YAAY;IAIzC,QAAQ,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI;IAWlE;;;;OAIG;IACG,YAAY,CAChB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,WAAW,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAAO,GACjF,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;CAgB9B;AAED,qBAAa,OAAQ,YAAW,cAAc;IAC5C,OAAO,CAAC,IAAI,CAAuC;IAEnD,QAAQ,CAAC,GAAG,EAAE,gBAAgB,GAAG,UAAU;IAK3C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAI/C,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,gBAAgB,EAAE;CAIhD;AAED,qBAAa,MAAO,YAAW,aAAa;IAC1C,OAAO,CAAC,IAAI,CAAsC;IAElD,QAAQ,CAAC,GAAG,EAAE,eAAe,GAAG,UAAU;IAK1C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAI5C,IAAI,IAAI,eAAe,EAAE;IAIzB,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,EAAE;CAa3D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAExF;AACD,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AACD,wBAAgB,YAAY,IAAI,MAAM,CAErC"}