react-x11 2.15.3 → 2.16.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 (57) hide show
  1. package/README.md +37 -0
  2. package/package.json +3 -3
  3. package/src/Reconciler.js +85 -22
  4. package/src/anchor.js +60 -18
  5. package/src/capabilities.js +29 -4
  6. package/src/cocoa/app.js +15 -9
  7. package/src/cocoa/context2d.js +23 -0
  8. package/src/cocoa/fonts.js +78 -0
  9. package/src/cocoa/presenter.js +17 -0
  10. package/src/cocoa/promotion.js +20 -0
  11. package/src/cocoa/relaunch.js +8 -3
  12. package/src/cocoa/symbols.js +64 -0
  13. package/src/cocoa/threaded.js +24 -4
  14. package/src/cocoa/window.js +362 -139
  15. package/src/components/ProgressBar.js +1 -1
  16. package/src/components/Slider.js +72 -39
  17. package/src/components/anchor.js +7 -2
  18. package/src/components/index.js +1 -0
  19. package/src/components/theme.js +32 -28
  20. package/src/desktopcapabilityhooks.js +29 -6
  21. package/src/filedialoghooks.js +3 -5
  22. package/src/frame/childmain.js +8 -20
  23. package/src/frame/env.js +2 -10
  24. package/src/icontheme.js +240 -0
  25. package/src/imagesource.js +83 -1
  26. package/src/index.js +3 -0
  27. package/src/node.d.ts +7 -0
  28. package/src/nodes/animation.js +17 -47
  29. package/src/nodes/cascade.js +17 -2
  30. package/src/nodes/image.js +63 -1
  31. package/src/nodes/kinds.js +12 -0
  32. package/src/nodes/layout.js +5 -1
  33. package/src/nodes/node.js +17 -3
  34. package/src/nodes/paint.js +117 -0
  35. package/src/nodes/scope.js +259 -0
  36. package/src/nodes/scrollable.js +53 -6
  37. package/src/nodes/text.js +2 -0
  38. package/src/nodes/textarea.js +1 -1
  39. package/src/nodes/textinput.js +1 -1
  40. package/src/nodes/window/anchoring.js +45 -18
  41. package/src/nodes/window/flush.js +6 -5
  42. package/src/nodes/window/popup.js +10 -0
  43. package/src/nodes/window/size.js +40 -2
  44. package/src/nodes/window/window.js +41 -14
  45. package/src/registry.js +2 -1
  46. package/src/settings.js +332 -0
  47. package/src/statusnotifier.js +164 -17
  48. package/src/styles.js +212 -8
  49. package/src/symbols.js +200 -0
  50. package/src/testing/mock-app.js +10 -0
  51. package/src/trayhooks.js +21 -5
  52. package/src/types/capabilities.d.ts +13 -1
  53. package/src/types/components.d.ts +33 -0
  54. package/src/types/elements.d.ts +57 -6
  55. package/src/types/style.d.ts +57 -0
  56. package/src/types/system.d.ts +104 -0
  57. package/src/types/tray.d.ts +14 -2
@@ -480,9 +480,21 @@ export function propBoxProps(node, app, scale, parentOrigin, order) {
480
480
  backgroundColor: colour(style.backgroundColor),
481
481
  borderWidth: border / scale,
482
482
  borderColor: colour(style.borderColor),
483
+ opacity: layerOpacity(style),
483
484
  };
484
485
  }
485
486
 
487
+ /**
488
+ * `opacity` as a layer's own. On the layer presenter a node's children are
489
+ * its sublayers, so this fades the subtree with it; the bitmap paths draw
490
+ * the group themselves and never promote a faded box.
491
+ */
492
+ export function layerOpacity(style) {
493
+ const value = style?.opacity;
494
+ if (typeof value !== 'number' || Number.isNaN(value)) return 1;
495
+ return Math.min(1, Math.max(0, value));
496
+ }
497
+
486
498
  // --- animations the render server runs ---------------------------------------
487
499
  //
488
500
  // The node model keeps deciding what is animating and when it ends
@@ -586,6 +598,10 @@ export class LayerAnimations {
586
598
  opts.timing = EASING_CONTROL_POINTS[entry.easing];
587
599
  opts.repeat = Infinity;
588
600
  opts.autoreverse = entry.alternate;
601
+ // CA's two halves of one CSS delay: a begin time the layer waits
602
+ // for showing `from`, or a time offset that starts it that far in
603
+ if (entry.delay > 0) opts.delay = entry.delay / 1000;
604
+ else if (entry.delay < 0) opts.timeOffset = -entry.delay / 1000;
589
605
  } else if (map.colour) {
590
606
  // From where the pixels are — which is what "an interrupted
591
607
  // transition reverses from where it got to" means here. A colour
@@ -1112,6 +1128,7 @@ export class CocoaLayerPresenter {
1112
1128
  ],
1113
1129
  zPosition: order,
1114
1130
  hidden: Boolean(node.hidden),
1131
+ opacity: layerOpacity(node.style),
1115
1132
  // the layer covers the ink bounds; clipping (if any) belongs to the
1116
1133
  // CONTENT box, which a raster self cannot express — scrolling
1117
1134
  // containers that also raster keep clipping via a child guard below
@@ -154,6 +154,25 @@ function insideGlArea(node) {
154
154
  return false;
155
155
  }
156
156
 
157
+ const faded = (style) => style?.opacity !== undefined && !(style.opacity >= 1);
158
+
159
+ /**
160
+ * A box whose `opacity` is below 1, now or on its way there, is a group the
161
+ * bitmap composites (`NodePaint._paintGroup`) — the box and everything in it
162
+ * drawn once and faded as one. On a layer of its own the box and its content
163
+ * would be separate layers under the one opacity, fading one over the other,
164
+ * and an opacity the frame clock is still animating would never reach the
165
+ * layer at all.
166
+ */
167
+ function fadesAsGroup(node) {
168
+ return (
169
+ faded(node.style) ||
170
+ faded(node._targetStyle) ||
171
+ Boolean(node._anim?.has('opacity')) ||
172
+ Boolean(node._loops?.some((loop) => loop.prop === 'opacity'))
173
+ );
174
+ }
175
+
157
176
  /**
158
177
  * Can this node be a property box on a layer at all — the static half of
159
178
  * the answer, the same whatever the scene around it does: a plain box by
@@ -163,6 +182,7 @@ function insideGlArea(node) {
163
182
  function promotableNode(node) {
164
183
  if (node.destroyed || !plainBox(node)) return false;
165
184
  if (insideGlArea(node)) return false;
185
+ if (fadesAsGroup(node)) return false;
166
186
  if (!stylePaintsPlain(node, node._targetStyle ?? node.style)) return false;
167
187
  if (node.isScroller?.()) return false;
168
188
  return !paintsOutline(node);
@@ -181,11 +181,16 @@ export function relaunch(entry, native) {
181
181
  // the package's own would be a second one the bundler never emits. The
182
182
  // worker sets itself up when it imports react-x11 (`bootstrapWorker`,
183
183
  // src/bootstrap.js), recognising the shared state in its workerData.
184
- // execArgv carries a loader like `--import tsx`, which is what lets the
185
- // worker load a .jsx entry at all.
184
+ //
185
+ // No `execArgv`: a Worker given none inherits the node flags the process
186
+ // started with, and among them a loader like `--import tsx`, which is what
187
+ // lets the worker load a .jsx entry at all. Handed `process.execArgv`
188
+ // instead, it refuses the whole list over one V8 or process-wide flag —
189
+ // `--expose-gc`, `--max-old-space-size`, `--title` — which the worker
190
+ // would have had anyway, since those hold for every thread: it gets `gc`
191
+ // either way (measured, Node 20–26; Bun treats the two the same).
186
192
  new Worker(entry, {
187
193
  argv: process.argv.slice(2),
188
- execArgv: process.execArgv,
189
194
  env: SHARE_ENV,
190
195
  workerData: { reactX11State: state.buffer },
191
196
  });
@@ -0,0 +1,64 @@
1
+ // SF Symbols for the Cocoa backend (#591): `app.symbols`, the provider
2
+ // `<image src={{ symbol }}>` asks (src/symbols.js), over `@windowkit/appkit`'s
3
+ // `symbolSize` and `ctxDrawSymbol` (0.12.0).
4
+ //
5
+ // A symbol is a template — its shape, in whatever colour it is drawn with —
6
+ // so it is drawn the way a glyph run is: in the context's fill colour, through
7
+ // its transform and clip. Sizes are points, which on this backend are logical
8
+ // pixels.
9
+
10
+ import { warnOnce } from '../symbols.js';
11
+
12
+ export class CocoaSymbols {
13
+ constructor(native) {
14
+ this.native = native;
15
+ this.sizes = new Map(); // name and options -> { width, height } | null
16
+ }
17
+
18
+ /** Whether the bridge draws symbols at all. Without the verbs nothing is
19
+ * shown and nothing else goes wrong, so development says so, once. */
20
+ _available() {
21
+ if (typeof this.native?.ctxDrawSymbol === 'function') return true;
22
+ warnOnce(
23
+ 'react-x11: this @windowkit/appkit has no ctxDrawSymbol, so <image ' +
24
+ 'src={{ symbol }}> shows nothing on the Cocoa backend. Update ' +
25
+ '@windowkit/appkit to the version react-x11 lists in its ' +
26
+ 'optionalDependencies.',
27
+ );
28
+ return false;
29
+ }
30
+
31
+ size(name, options) {
32
+ if (!this._available()) return null;
33
+ const bridge = bridgeOptions(options);
34
+ const key = JSON.stringify([name, bridge]);
35
+ if (!this.sizes.has(key)) {
36
+ if (this.sizes.size > 512) this.sizes.clear();
37
+ this.sizes.set(key, this.native.symbolSize(name, bridge));
38
+ }
39
+ return this.sizes.get(key);
40
+ }
41
+
42
+ draw(ctx, name, rect, options) {
43
+ if (typeof ctx.drawSymbol !== 'function' || !this._available()) {
44
+ return false;
45
+ }
46
+ ctx.fillStyle = options.color;
47
+ return ctx.drawSymbol(
48
+ name,
49
+ rect.x,
50
+ rect.y,
51
+ rect.width,
52
+ rect.height,
53
+ bridgeOptions(options),
54
+ );
55
+ }
56
+ }
57
+
58
+ /** What the bridge is handed: the options it knows, and only those set. */
59
+ const bridgeOptions = ({ pointSize, weight, scale, variableValue }) => ({
60
+ pointSize,
61
+ weight,
62
+ ...(scale === undefined ? {} : { scale }),
63
+ ...(variableValue === undefined ? {} : { variableValue }),
64
+ });
@@ -188,10 +188,23 @@ export function fdStream(fd, write = fs.writeSync) {
188
188
  /**
189
189
  * Stdout and stderr on fds 1 and 2, and a console over them — the console
190
190
  * too, because Bun's writes past `process.stdout` altogether.
191
+ *
192
+ * A `Console` is only the writing half of the runtime's console. Node adds
193
+ * the inspector's methods on top — `timeStamp`, `profile`, `profileEnd`,
194
+ * `context`, `createTask` — and `console.Console`, so the new console takes
195
+ * whatever it lacks from the one it replaces, as it is. Code that loaded
196
+ * before this saw them: React's development reconciler asks once, as it
197
+ * loads, whether `console.timeStamp` is a function, and calls it on every
198
+ * render after, so a script importing `react-x11/test` ahead of `react-x11`
199
+ * threw `console.timeStamp is not a function` on its first render.
191
200
  */
192
- export function installStdio(proc = process, global = globalThis) {
193
- const stdout = fdStream(1);
194
- const stderr = fdStream(2);
201
+ export function installStdio(
202
+ proc = process,
203
+ global = globalThis,
204
+ write = fs.writeSync,
205
+ ) {
206
+ const stdout = fdStream(1, write);
207
+ const stderr = fdStream(2, write);
195
208
  Object.defineProperty(proc, 'stdout', {
196
209
  configurable: true,
197
210
  get: () => stdout,
@@ -200,10 +213,17 @@ export function installStdio(proc = process, global = globalThis) {
200
213
  configurable: true,
201
214
  get: () => stderr,
202
215
  });
216
+ const runtime = global.console;
217
+ const ours = new Console({ stdout, stderr });
218
+ for (const key of Reflect.ownKeys(runtime)) {
219
+ if (key in ours) continue;
220
+ const desc = Object.getOwnPropertyDescriptor(runtime, key);
221
+ Object.defineProperty(ours, key, desc);
222
+ }
203
223
  Object.defineProperty(global, 'console', {
204
224
  configurable: true,
205
225
  writable: true,
206
- value: new Console({ stdout, stderr }),
226
+ value: ours,
207
227
  });
208
228
  }
209
229