electrobun 1.18.4-beta.18 → 1.18.4-beta.21

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 (55) hide show
  1. package/README.md +9 -0
  2. package/bin/electrobun.cjs +165 -0
  3. package/dist/api/browser/ui/__tests__/dom.test.ts +473 -0
  4. package/dist/api/browser/ui/__tests__/domStub.ts +218 -0
  5. package/dist/api/browser/ui/dom.ts +490 -0
  6. package/dist/api/browser/ui/index.ts +44 -0
  7. package/dist/api/browser/ui/jsx-dev-runtime.ts +16 -0
  8. package/dist/api/browser/ui/jsx-runtime.ts +56 -0
  9. package/dist/api/config/ElectrobunConfig.ts +33 -0
  10. package/dist/api/preload/.generated/compiled.ts +1 -1
  11. package/dist/api/preload/index.ts +2 -0
  12. package/dist/api/preload/uiTag.ts +45 -0
  13. package/dist/api/sdks/main/__tests__/utils-quit-exit-code.test.ts +44 -0
  14. package/dist/api/sdks/main/core/GpuWindow.ts +19 -0
  15. package/dist/api/sdks/main/core/Utils.ts +52 -4
  16. package/dist/api/sdks/main/core/WGPUView.ts +9 -0
  17. package/dist/api/sdks/main/entries/ui.ts +1 -0
  18. package/dist/api/sdks/main/proc/native.ts +187 -0
  19. package/dist/api/sdks/main/ui/__tests__/font.test.ts +49 -0
  20. package/dist/api/sdks/main/ui/__tests__/hit.test.ts +64 -0
  21. package/dist/api/sdks/main/ui/__tests__/jsx.test.ts +252 -0
  22. package/dist/api/sdks/main/ui/__tests__/layout.test.ts +159 -0
  23. package/dist/api/sdks/main/ui/__tests__/paint.test.ts +115 -0
  24. package/dist/api/sdks/main/ui/__tests__/reactive.test.ts +456 -0
  25. package/dist/api/sdks/main/ui/__tests__/scroll-focus-input.test.ts +298 -0
  26. package/dist/api/sdks/main/ui/__tests__/tree.test.ts +96 -0
  27. package/dist/api/sdks/main/ui/__tests__/ui.test.ts +170 -0
  28. package/dist/api/sdks/main/ui/elements.ts +135 -0
  29. package/dist/api/sdks/main/ui/font.ts +168 -0
  30. package/dist/api/sdks/main/ui/hit.ts +46 -0
  31. package/dist/api/sdks/main/ui/index.ts +71 -0
  32. package/dist/api/sdks/main/ui/input.ts +268 -0
  33. package/dist/api/sdks/main/ui/jsx-dev-runtime.ts +16 -0
  34. package/dist/api/sdks/main/ui/jsx-runtime.ts +136 -0
  35. package/dist/api/sdks/main/ui/keymap.ts +147 -0
  36. package/dist/api/sdks/main/ui/layout.ts +178 -0
  37. package/dist/api/sdks/main/ui/paint.ts +196 -0
  38. package/dist/api/sdks/main/ui/reactive.ts +4 -0
  39. package/dist/api/sdks/main/ui/renderer.ts +278 -0
  40. package/dist/api/sdks/main/ui/text.ts +175 -0
  41. package/dist/api/sdks/main/ui/textInput.ts +121 -0
  42. package/dist/api/sdks/main/ui/tree.ts +276 -0
  43. package/dist/api/sdks/main/ui/ui.ts +457 -0
  44. package/dist/api/sdks/main/ui/uiTagHost.ts +56 -0
  45. package/dist/api/sdks/main/ui/uiwindow.ts +330 -0
  46. package/dist/api/shared/build-dependencies.test.ts +1 -1
  47. package/dist/api/shared/build-dependencies.ts +4 -4
  48. package/dist/api/shared/linux-webkit-automation.test.ts +1 -1
  49. package/dist/api/shared/warren/jsx.ts +279 -0
  50. package/dist/api/shared/warren/reactive.ts +638 -0
  51. package/dist/api/shared/windows-unicode-ui.test.ts +4 -4
  52. package/dist/preload-full.js +35 -0
  53. package/dist/zig-sdk/electrobun.zig +197 -162
  54. package/{dash.config.ts → hutch.config.ts} +4 -3
  55. package/package.json +14 -2
@@ -0,0 +1,276 @@
1
+ // ABI-shaped retained UI tree. Everything crossing this interface is a number
2
+ // (u32 node ids, numeric prop ids, f64 values) or a UTF-8 string, so the
3
+ // implementation can later move behind Electrobun's core C ABI — with each
4
+ // mainProcess language SDK binding the same operations — without changing any
5
+ // code written against it.
6
+
7
+ export const enum NodeKind {
8
+ Box = 1,
9
+ Text = 2,
10
+ // Layout-only anchor: reports its computed rect so nativeWrapper can
11
+ // position a native layer (webview, wgpu view) over it — the same anchor
12
+ // role <electrobun-webview>/<electrobun-wgpu> play inside webviews.
13
+ Anchor = 3,
14
+ }
15
+
16
+ export const enum Prop {
17
+ Dir = 1, // 0 row, 1 column
18
+ Gap = 2,
19
+ Pad = 3,
20
+ Width = 4, // -1 auto
21
+ Height = 5, // -1 auto
22
+ Grow = 6,
23
+ Justify = 7, // 0 start, 1 center, 2 end, 3 space-between
24
+ Align = 8, // 0 start, 1 center, 2 end, 3 stretch
25
+ Bg = 9, // color as u32 0xRRGGBBAA; alpha 0 = not painted
26
+ Radius = 10,
27
+ BorderWidth = 11,
28
+ BorderColor = 12, // u32 0xRRGGBBAA
29
+ FontSize = 13, // glyph height in px
30
+ TextColor = 14, // u32 0xRRGGBBAA
31
+ Hittable = 15, // 0/1
32
+ Overflow = 16, // 0 visible, 1 scroll (clips children, scrollable main axis)
33
+ Scroll = 17, // main-axis scroll offset in px (Overflow=1 boxes)
34
+ Focusable = 18, // 0/1
35
+ WindowDrag = 19, // 0/1 — pressing this node drags the host window
36
+ }
37
+
38
+ const PROP_SLOTS = 20;
39
+
40
+ export const AUTO = -1;
41
+
42
+ export const enum Justify {
43
+ Start = 0,
44
+ Center = 1,
45
+ End = 2,
46
+ SpaceBetween = 3,
47
+ }
48
+
49
+ export const enum Align {
50
+ Start = 0,
51
+ Center = 1,
52
+ End = 2,
53
+ Stretch = 3,
54
+ }
55
+
56
+ export interface NodeRecord {
57
+ kind: NodeKind;
58
+ parent: number;
59
+ first: number;
60
+ last: number;
61
+ prev: number;
62
+ next: number;
63
+ props: Float64Array;
64
+ text: string;
65
+ // Computed layout, absolute px from the window's top-left content corner.
66
+ x: number;
67
+ y: number;
68
+ w: number;
69
+ h: number;
70
+ // Natural main-axis extent of children (Overflow=1 boxes): scroll range.
71
+ // Main-axis means height for columns, width for rows.
72
+ contentMain: number;
73
+ }
74
+
75
+ function defaultProps(kind: NodeKind): Float64Array {
76
+ const props = new Float64Array(PROP_SLOTS);
77
+ props[Prop.Width] = AUTO;
78
+ props[Prop.Height] = AUTO;
79
+ // Flexbox default: children stretch on the cross axis (layout applies it
80
+ // only to auto-sized, non-text children).
81
+ props[Prop.Align] = Align.Stretch;
82
+ if (kind === NodeKind.Text) {
83
+ props[Prop.FontSize] = 14;
84
+ props[Prop.TextColor] = 0xffffffff;
85
+ }
86
+ return props;
87
+ }
88
+
89
+ export class UiTree {
90
+ private nodes = new Map<number, NodeRecord>();
91
+ private nextId = 1;
92
+ private dirty = true;
93
+ readonly root: number;
94
+
95
+ constructor() {
96
+ this.root = this.createNode(NodeKind.Box);
97
+ }
98
+
99
+ get(id: number): NodeRecord {
100
+ const node = this.nodes.get(id);
101
+ if (!node) throw new Error(`Unknown UI node ${id}`);
102
+ return node;
103
+ }
104
+
105
+ has(id: number): boolean {
106
+ return this.nodes.has(id);
107
+ }
108
+
109
+ createNode(kind: NodeKind): number {
110
+ const id = this.nextId++;
111
+ this.nodes.set(id, {
112
+ kind,
113
+ parent: 0,
114
+ first: 0,
115
+ last: 0,
116
+ prev: 0,
117
+ next: 0,
118
+ props: defaultProps(kind),
119
+ text: "",
120
+ x: 0,
121
+ y: 0,
122
+ w: 0,
123
+ h: 0,
124
+ contentMain: 0,
125
+ });
126
+ this.dirty = true;
127
+ return id;
128
+ }
129
+
130
+ createTextNode(text: string): number {
131
+ const id = this.createNode(NodeKind.Text);
132
+ this.get(id).text = text;
133
+ return id;
134
+ }
135
+
136
+ setProp(id: number, prop: Prop, value: number): void {
137
+ const node = this.get(id);
138
+ if (node.props[prop] !== value) {
139
+ node.props[prop] = value;
140
+ this.dirty = true;
141
+ }
142
+ }
143
+
144
+ getProp(id: number, prop: Prop): number {
145
+ return this.get(id).props[prop]!;
146
+ }
147
+
148
+ setText(id: number, text: string): void {
149
+ const node = this.get(id);
150
+ if (node.kind !== NodeKind.Text) {
151
+ throw new Error(`Node ${id} is not a text node`);
152
+ }
153
+ if (node.text !== text) {
154
+ node.text = text;
155
+ this.dirty = true;
156
+ }
157
+ }
158
+
159
+ getText(id: number): string {
160
+ return this.get(id).text;
161
+ }
162
+
163
+ isTextNode(id: number): boolean {
164
+ return this.get(id).kind === NodeKind.Text;
165
+ }
166
+
167
+ parentOf(id: number): number {
168
+ return this.get(id).parent;
169
+ }
170
+
171
+ firstChildOf(id: number): number {
172
+ return this.get(id).first;
173
+ }
174
+
175
+ nextSiblingOf(id: number): number {
176
+ return this.get(id).next;
177
+ }
178
+
179
+ childrenOf(id: number): number[] {
180
+ const out: number[] = [];
181
+ for (let c = this.get(id).first; c !== 0; c = this.get(c).next) {
182
+ out.push(c);
183
+ }
184
+ return out;
185
+ }
186
+
187
+ /** Insert `id` under `parent`, before `anchor` (0 appends). */
188
+ insertBefore(parent: number, id: number, anchor = 0): void {
189
+ if (id === this.root) throw new Error("Cannot re-parent the root node");
190
+ const node = this.get(id);
191
+ const parentNode = this.get(parent);
192
+ if (node.parent !== 0) this.detach(id);
193
+
194
+ if (anchor === 0) {
195
+ node.prev = parentNode.last;
196
+ node.next = 0;
197
+ if (parentNode.last !== 0) this.get(parentNode.last).next = id;
198
+ parentNode.last = id;
199
+ if (parentNode.first === 0) parentNode.first = id;
200
+ } else {
201
+ const anchorNode = this.get(anchor);
202
+ if (anchorNode.parent !== parent) {
203
+ throw new Error(`Anchor ${anchor} is not a child of ${parent}`);
204
+ }
205
+ node.prev = anchorNode.prev;
206
+ node.next = anchor;
207
+ if (anchorNode.prev !== 0) this.get(anchorNode.prev).next = id;
208
+ else parentNode.first = id;
209
+ anchorNode.prev = id;
210
+ }
211
+ node.parent = parent;
212
+ this.dirty = true;
213
+ }
214
+
215
+ append(parent: number, id: number): void {
216
+ this.insertBefore(parent, id, 0);
217
+ }
218
+
219
+ /** Unlink `id` from its parent, keeping the subtree alive. */
220
+ detach(id: number): void {
221
+ const node = this.get(id);
222
+ if (node.parent === 0) return;
223
+ const parentNode = this.get(node.parent);
224
+ if (node.prev !== 0) this.get(node.prev).next = node.next;
225
+ else parentNode.first = node.next;
226
+ if (node.next !== 0) this.get(node.next).prev = node.prev;
227
+ else parentNode.last = node.prev;
228
+ node.parent = 0;
229
+ node.prev = 0;
230
+ node.next = 0;
231
+ this.dirty = true;
232
+ }
233
+
234
+ /** Detach `id` and free its whole subtree. */
235
+ destroy(id: number): void {
236
+ if (id === this.root) throw new Error("Cannot destroy the root node");
237
+ this.detach(id);
238
+ const stack = [id];
239
+ while (stack.length) {
240
+ const current = stack.pop()!;
241
+ for (
242
+ let c = this.get(current).first;
243
+ c !== 0;
244
+ c = this.nodes.get(c)?.next ?? 0
245
+ ) {
246
+ stack.push(c);
247
+ }
248
+ this.nodes.delete(current);
249
+ }
250
+ this.dirty = true;
251
+ }
252
+
253
+ destroyChildren(id: number): void {
254
+ for (const child of this.childrenOf(id)) {
255
+ this.destroy(child);
256
+ }
257
+ }
258
+
259
+ get size(): number {
260
+ return this.nodes.size;
261
+ }
262
+
263
+ markDirty(): void {
264
+ this.dirty = true;
265
+ }
266
+
267
+ isDirty(): boolean {
268
+ return this.dirty;
269
+ }
270
+
271
+ takeDirty(): boolean {
272
+ const was = this.dirty;
273
+ this.dirty = false;
274
+ return was;
275
+ }
276
+ }