react-x11 1.2.0 → 2.0.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 (158) hide show
  1. package/README.md +330 -81
  2. package/package.json +132 -14
  3. package/src/ClickToComponent.js +9 -2
  4. package/src/DevToolsIntegration.js +440 -32
  5. package/src/Reconciler.js +463 -165
  6. package/src/a11y.js +1299 -0
  7. package/src/acceleratorhooks.js +86 -0
  8. package/src/accelerators.js +128 -0
  9. package/src/activate.js +168 -0
  10. package/src/anchor.js +360 -0
  11. package/src/appcontext.js +161 -0
  12. package/src/appearance.js +748 -0
  13. package/src/appearancehooks.js +96 -0
  14. package/src/apphooks.js +73 -0
  15. package/src/application.js +748 -0
  16. package/src/atspi.js +1953 -0
  17. package/src/bus.js +545 -0
  18. package/src/bushooks.js +136 -0
  19. package/src/clientmessage.js +140 -0
  20. package/src/clipboard.js +121 -0
  21. package/src/components/Button.js +148 -33
  22. package/src/components/Checkbox.js +66 -32
  23. package/src/components/Dialog.js +96 -29
  24. package/src/components/FileDialog.js +491 -0
  25. package/src/components/Icon.js +329 -0
  26. package/src/components/Menu.js +797 -133
  27. package/src/components/PasswordInput.js +434 -0
  28. package/src/components/ProgressBar.js +91 -12
  29. package/src/components/Radio.js +78 -35
  30. package/src/components/Select.js +255 -65
  31. package/src/components/Slider.js +102 -41
  32. package/src/components/SplitPane.js +206 -0
  33. package/src/components/Switch.js +100 -22
  34. package/src/components/Table.js +529 -0
  35. package/src/components/Tabs.js +211 -0
  36. package/src/components/Tooltip.js +528 -54
  37. package/src/components/anchor.js +154 -107
  38. package/src/components/change.js +34 -0
  39. package/src/components/dnd.js +103 -0
  40. package/src/components/index.js +16 -3
  41. package/src/components/keys.js +17 -20
  42. package/src/components/scribble.js +247 -0
  43. package/src/components/theme.js +350 -42
  44. package/src/components/typeahead.js +12 -2
  45. package/src/compose.js +868 -0
  46. package/src/compositing.js +224 -0
  47. package/src/dbusmenu.js +384 -0
  48. package/src/debug.d.ts +63 -0
  49. package/src/debug.js +642 -0
  50. package/src/decorations.js +486 -0
  51. package/src/desktopsettings.js +209 -0
  52. package/src/desktopsettingshooks.js +62 -0
  53. package/src/dnd.js +1725 -0
  54. package/src/editmenu.js +272 -0
  55. package/src/errors.js +98 -0
  56. package/src/events.js +1406 -162
  57. package/src/extensions.js +45 -0
  58. package/src/filedialog.js +375 -0
  59. package/src/filedialoghooks.js +132 -0
  60. package/src/fonthooks.js +64 -0
  61. package/src/fonts.js +301 -0
  62. package/src/foreignnodes.js +519 -0
  63. package/src/frame/child.js +33 -0
  64. package/src/frame/childmain.js +274 -0
  65. package/src/frame/env.js +140 -0
  66. package/src/frame/index.js +444 -0
  67. package/src/frame/lifecycle.js +67 -0
  68. package/src/frame/protocol.js +179 -0
  69. package/src/frames.js +73 -0
  70. package/src/glbackend.js +114 -0
  71. package/src/glnodes.js +125 -67
  72. package/src/globalmenu.js +683 -0
  73. package/src/host.d.ts +88 -0
  74. package/src/host.js +42 -0
  75. package/src/idle.js +490 -0
  76. package/src/idlehooks.js +100 -0
  77. package/src/imagesource.js +349 -0
  78. package/src/index.d.ts +475 -0
  79. package/src/index.js +83 -9
  80. package/src/inputtime.js +206 -0
  81. package/src/jsx-dev-runtime.d.ts +21 -0
  82. package/src/jsx-dev-runtime.js +2 -0
  83. package/src/jsx-runtime.d.ts +39 -0
  84. package/src/jsx-runtime.js +6 -0
  85. package/src/keyboard.js +256 -0
  86. package/src/keyboardstate.js +278 -0
  87. package/src/keyboardstatehooks.js +58 -0
  88. package/src/keysyms.d.ts +149 -0
  89. package/src/keysyms.js +270 -0
  90. package/src/locale.js +170 -0
  91. package/src/localehooks.js +47 -0
  92. package/src/menuitem.js +223 -0
  93. package/src/node.d.ts +597 -0
  94. package/src/node.js +44 -0
  95. package/src/nodes.js +9546 -690
  96. package/src/ntk.d.ts +44 -0
  97. package/src/ntk.js +25 -0
  98. package/src/paintcache.js +366 -0
  99. package/src/palette.js +380 -0
  100. package/src/pastestate.js +66 -0
  101. package/src/portal.js +461 -0
  102. package/src/priority.js +26 -0
  103. package/src/refresh/index.d.ts +40 -0
  104. package/src/refresh/index.js +122 -0
  105. package/src/refresh/loader.d.ts +37 -0
  106. package/src/refresh/loader.js +401 -0
  107. package/src/refresh/register.d.ts +5 -0
  108. package/src/refresh/register.js +13 -0
  109. package/src/registry.js +232 -0
  110. package/src/scale.js +626 -0
  111. package/src/scalehooks.js +27 -0
  112. package/src/screencolor.js +640 -0
  113. package/src/screencolorhooks.js +101 -0
  114. package/src/screens.js +754 -0
  115. package/src/screenshooks.js +137 -0
  116. package/src/startup.js +302 -0
  117. package/src/style.d.ts +126 -0
  118. package/src/style.js +33 -0
  119. package/src/styles.js +1482 -6
  120. package/src/svgnodes.js +306 -0
  121. package/src/testing/a11y.js +484 -0
  122. package/src/testing/components.js +414 -0
  123. package/src/testing/events.js +407 -0
  124. package/src/testing/harness.js +455 -0
  125. package/src/testing/index.d.ts +558 -0
  126. package/src/testing/index.js +85 -0
  127. package/src/testing/mock-app.js +463 -0
  128. package/src/testing/pixels.js +152 -0
  129. package/src/testing/queries.js +224 -0
  130. package/src/textrange.js +83 -0
  131. package/src/textselection.js +439 -0
  132. package/src/trace-registry.js +63 -0
  133. package/src/transfer.js +93 -0
  134. package/src/types/appearance.d.ts +84 -0
  135. package/src/types/application.d.ts +173 -0
  136. package/src/types/components.d.ts +820 -0
  137. package/src/types/dbus.d.ts +177 -0
  138. package/src/types/elements.d.ts +897 -0
  139. package/src/types/events.d.ts +549 -0
  140. package/src/types/filedialog.d.ts +260 -0
  141. package/src/types/fonts.d.ts +124 -0
  142. package/src/types/frame.d.ts +146 -0
  143. package/src/types/globalmenu.d.ts +42 -0
  144. package/src/types/nodes.d.ts +199 -0
  145. package/src/types/screencolor.d.ts +84 -0
  146. package/src/types/style.d.ts +434 -0
  147. package/src/types/system.d.ts +287 -0
  148. package/src/windowid.js +151 -0
  149. package/src/windowstate.js +393 -0
  150. package/src/xsettings.js +336 -0
  151. package/src/yoga.d.ts +55 -0
  152. package/src/yoga.js +135 -0
  153. package/src/components/Canvas3D.js +0 -28
  154. package/src/geometry3d.js +0 -223
  155. package/src/pointer3d.js +0 -158
  156. package/src/raycast3d.js +0 -146
  157. package/src/richnodes.js +0 -436
  158. package/src/scene3d.js +0 -683
package/src/richnodes.js DELETED
@@ -1,436 +0,0 @@
1
- // Rich-content elements: thin retained-node wrappers over ntk's document
2
- // widgets in standalone mode (`layout(width)` + `draw(ctx, x, y)` +
3
- // `contentHeight`). One yoga measure function per node feeds the widget's
4
- // own layout into flexbox; async content (mermaid models, images) arrives
5
- // through the widget's `onInvalidate` hook (ntk >= 3.4.0).
6
- import { MarkdownView, HtmlView, SvgView, layoutTex } from 'ntk';
7
-
8
- import { Node } from './nodes.js';
9
- import { Yoga, isLayoutProp, isPaintProp } from './styles.js';
10
-
11
- /** Joined text of string children (react-markdown style), or null when the
12
- * element has none — then the `source` prop is the content. */
13
- function stringChildrenOf(node) {
14
- let text = null;
15
- for (const child of node.children) {
16
- if (child.kind === 'textchunk') text = (text ?? '') + child.text;
17
- }
18
- return text;
19
- }
20
-
21
- // Fallback width for measuring under an unconstrained yoga width
22
- // (position: absolute without width, unbounded row). Flowed documents have
23
- // no natural width, so pick something readable.
24
- const PREFERRED_WIDTH = 480;
25
-
26
- /**
27
- * Base for <markdown> and <html>: width-driven document flow. Subclasses
28
- * implement _createView() (a widget with layout/draw/contentHeight) and
29
- * _setSource(view). Content is a string child (like react-markdown:
30
- * <markdown>{`# Hi`}</markdown> — use a template-literal expression, JSX
31
- * literal text collapses newlines) or the `source` prop; children win.
32
- * The view is created lazily because the headless mock app in smoke tests
33
- * has no font manager — everything measures 0×0 there, like <text>.
34
- */
35
- class DocumentViewNode extends Node {
36
- constructor(kind, props, app) {
37
- super(kind, props, app);
38
- this.view = null;
39
- this._laidOutWidth = -1;
40
- this.yoga.setMeasureFunc((width, widthMode) => {
41
- const view = this._ensureView();
42
- if (!view) return { width: 0, height: 0 };
43
- const unconstrained =
44
- widthMode === Yoga.MEASURE_MODE_UNDEFINED ||
45
- !Number.isFinite(width) ||
46
- width <= 0;
47
- const w = Math.max(1, Math.ceil(unconstrained ? PREFERRED_WIDTH : width));
48
- const height = this._layoutAt(w);
49
- return { width: w, height: Math.ceil(height) };
50
- });
51
- }
52
-
53
- _ensureView() {
54
- if (this.view || !this.app?.fonts) return this.view;
55
- this.view = this._createView();
56
- if (this.view) {
57
- // ntk >= 3.4.0 notifies here when async content (a mermaid model, an
58
- // image) invalidates the widget layout; harmless no-op before that
59
- this.view.onInvalidate = () => this._contentInvalidated();
60
- this._setSource(this.view);
61
- }
62
- return this.view;
63
- }
64
-
65
- _contentInvalidated() {
66
- if (this.destroyed) return;
67
- this._laidOutWidth = -1;
68
- this.yoga?.markDirty();
69
- this.root?.invalidate(true);
70
- }
71
-
72
- _source() {
73
- return stringChildrenOf(this) ?? this.props.source ?? '';
74
- }
75
-
76
- /** String children changed (chunk added/removed/edited). */
77
- _textContentChanged() {
78
- if (this.view) this._setSource(this.view);
79
- this._contentInvalidated();
80
- }
81
-
82
- _layoutAt(width) {
83
- if (this._laidOutWidth !== width) {
84
- this.view.layout(width);
85
- this._laidOutWidth = width;
86
- }
87
- return this.view.contentHeight;
88
- }
89
-
90
- /** Props that require rebuilding the view (theme and loader options are
91
- * constructor-only in the ntk widgets). Subclasses extend. */
92
- _viewProps() {
93
- return ['theme'];
94
- }
95
-
96
- applyProps(newProps, oldProps) {
97
- const before = oldProps ?? this.props;
98
- super.applyProps(newProps, oldProps);
99
- if (!this.view) return;
100
- if (this._viewProps().some((key) => newProps[key] !== before[key])) {
101
- this.view = null; // rebuilt (and re-sourced) on next measure/paint
102
- this._contentInvalidated();
103
- return;
104
- }
105
- if (newProps.source !== before.source) {
106
- this._setSource(this.view);
107
- this._contentInvalidated();
108
- }
109
- }
110
-
111
- _paintContent(ctx) {
112
- const view = this._ensureView();
113
- if (!view) return;
114
- const content = this.contentBox();
115
- if (content.width <= 0) return;
116
- this._layoutAt(Math.max(1, Math.ceil(content.width)));
117
- ctx.save();
118
- ctx.beginPath();
119
- ctx.rect(this.abs.x, this.abs.y, this.abs.width, this.abs.height);
120
- ctx.clip();
121
- // links recorded by draw() are in window coordinates, so linkAt can
122
- // take synthetic-event coordinates directly (_defaultMouseDown)
123
- view.draw(ctx, content.x, content.y);
124
- ctx.restore();
125
- }
126
-
127
- _defaultMouseDown(ev) {
128
- if (ev.button !== 1 || !this.view || !this.props.onLink) return;
129
- const hit = this.view.linkAt(ev.x, ev.y);
130
- if (!hit) return;
131
- // MarkdownView.linkAt returns the href, HtmlView an { href, element }
132
- if (typeof hit === 'string') this.props.onLink(hit, ev);
133
- else this.props.onLink(hit.href, ev, hit.element);
134
- }
135
- }
136
-
137
- /**
138
- * <markdown source>: ntk MarkdownView — headings, emphasis, lists, quotes,
139
- * tables, syntax-highlighted fences, math fences, async mermaid fences.
140
- * Props: source, theme, onLink(href, ev). Spacing comes from the box model
141
- * (padding prop), not the widget's own page padding.
142
- */
143
- export class MarkdownNode extends DocumentViewNode {
144
- constructor(props, app) {
145
- super('markdown', props, app);
146
- }
147
-
148
- _createView() {
149
- return new MarkdownView(null, {
150
- fonts: this.app.fonts,
151
- theme: this.props.theme,
152
- padding: 0,
153
- });
154
- }
155
-
156
- _setSource(view) {
157
- view.setMarkdown(this._source());
158
- }
159
- }
160
-
161
- /**
162
- * <html source>: ntk HtmlView — its own CSS cascade (document <style>s plus
163
- * the `stylesheet` prop), flexbox/block layout, images. Props: source,
164
- * stylesheet, baseUrl, loadResource, theme, onLink(href, ev, element).
165
- */
166
- export class HtmlNode extends DocumentViewNode {
167
- constructor(props, app) {
168
- super('html', props, app);
169
- }
170
-
171
- _viewProps() {
172
- return ['theme', 'stylesheet', 'baseUrl', 'loadResource'];
173
- }
174
-
175
- _createView() {
176
- return new HtmlView(null, {
177
- fonts: this.app.fonts,
178
- theme: this.props.theme,
179
- stylesheet: this.props.stylesheet,
180
- baseUrl: this.props.baseUrl,
181
- loadResource: this.props.loadResource,
182
- });
183
- }
184
-
185
- _setSource(view) {
186
- view.setHtml(this._source(), { baseUrl: this.props.baseUrl });
187
- }
188
- }
189
-
190
- // SVG attributes that are camelCase in SVG itself; every other camelCase
191
- // prop (strokeWidth, fillRule, textAnchor, …) kebab-cases, like React DOM.
192
- const SVG_CAMEL_ATTRS = new Set([
193
- 'viewBox',
194
- 'preserveAspectRatio',
195
- 'gradientUnits',
196
- 'gradientTransform',
197
- 'spreadMethod',
198
- 'patternUnits',
199
- 'patternContentUnits',
200
- 'patternTransform',
201
- 'clipPathUnits',
202
- 'maskUnits',
203
- 'maskContentUnits',
204
- 'markerWidth',
205
- 'markerHeight',
206
- 'markerUnits',
207
- 'refX',
208
- 'refY',
209
- 'textLength',
210
- 'lengthAdjust',
211
- 'stdDeviation',
212
- ]);
213
-
214
- const toSvgAttrib = (name) =>
215
- SVG_CAMEL_ATTRS.has(name)
216
- ? name
217
- : name.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
218
-
219
- function svgAttribs(props, skip) {
220
- const attribs = {};
221
- for (const key of Object.keys(props)) {
222
- const value = props[key];
223
- if (key === 'children' || value == null) continue;
224
- if (typeof value === 'function') continue;
225
- if (skip && skip(key)) continue;
226
- attribs[toSvgAttrib(key)] = String(value);
227
- }
228
- return attribs;
229
- }
230
-
231
- /**
232
- * An element inside <svg>: no yoga node, no painting of its own — it only
233
- * carries a tag name and props, and is serialized into the htmlparser2-style
234
- * DOM that SvgView consumes. Prop/child changes bubble to the owning
235
- * SvgNode via the _textContentChanged channel (also used by text chunks,
236
- * so string children of an SVG <text> invalidate the same way).
237
- */
238
- export class SvgChildNode extends Node {
239
- constructor(tag, props, app) {
240
- super(tag, props, app, { yoga: false });
241
- this.isSvgChild = true;
242
- }
243
-
244
- _textContentChanged() {
245
- this.parent?._textContentChanged();
246
- }
247
-
248
- applyProps(newProps, oldProps) {
249
- super.applyProps(newProps, oldProps);
250
- this._textContentChanged();
251
- }
252
-
253
- toDom() {
254
- return {
255
- type: 'tag',
256
- name: this.kind,
257
- attribs: svgAttribs(this.props),
258
- children: this.children
259
- .map((c) =>
260
- c.kind === 'textchunk'
261
- ? { type: 'text', data: c.text }
262
- : c.isSvgChild
263
- ? c.toDom()
264
- : null,
265
- )
266
- .filter(Boolean),
267
- };
268
- }
269
- }
270
-
271
- /**
272
- * <svg>: ntk SvgView. Content is either JSX children (React-DOM style —
273
- * <svg viewBox="0 0 24 24"><circle cx={12} … />; children win when both
274
- * are present) or a `source` markup string. Sized like <image>: natural
275
- * (viewBox) size, kept to its aspect ratio when only the width is
276
- * constrained; scales to the content box.
277
- */
278
- export class SvgNode extends Node {
279
- constructor(props, app) {
280
- super('svg', props, app);
281
- this.view = null;
282
- this._stale = true;
283
- this._configureMeasure();
284
- }
285
-
286
- /** Any change to the svg subtree (props, children, text) lands here. */
287
- _textContentChanged() {
288
- this._stale = true;
289
- // markDirty is only legal on nodes with a measure function (it is
290
- // unset when width and height are both fixed)
291
- if (this._hasMeasure) this.yoga?.markDirty();
292
- this.root?.invalidate(true);
293
- }
294
-
295
- _ensureView() {
296
- if (!this._stale) return this.view;
297
- this._stale = false;
298
- this.view = null;
299
- try {
300
- const children = this.children.filter(
301
- (c) => c.isSvgChild || c.kind === 'textchunk',
302
- );
303
- if (children.length > 0) {
304
- const skip = (key) =>
305
- key === 'source' ||
306
- isLayoutProp(key) ||
307
- isPaintProp(key) ||
308
- key === 'cursor' ||
309
- key === 'focusable' ||
310
- key === 'pointerEvents';
311
- this.view = new SvgView(null).setSvgDom({
312
- type: 'tag',
313
- name: 'svg',
314
- attribs: svgAttribs(this.props, skip),
315
- children: children.map((c) =>
316
- c.kind === 'textchunk' ? { type: 'text', data: c.text } : c.toDom(),
317
- ),
318
- });
319
- } else if (this.props.source) {
320
- this.view = new SvgView(null).setSvg(String(this.props.source));
321
- }
322
- } catch (err) {
323
- console.error('react-x11: <svg> failed to parse:', err.message);
324
- }
325
- return this.view;
326
- }
327
-
328
- _configureMeasure() {
329
- if (this.props.width != null && this.props.height != null) {
330
- if (this._hasMeasure) this.yoga.unsetMeasureFunc();
331
- this._hasMeasure = false;
332
- return;
333
- }
334
- this._hasMeasure = true;
335
- this.yoga.setMeasureFunc((width, widthMode) => {
336
- const view = this._ensureView();
337
- const natW = view?.naturalWidth ?? 0;
338
- const natH = view?.naturalHeight ?? 0;
339
- let w = natW;
340
- if (
341
- widthMode !== Yoga.MEASURE_MODE_UNDEFINED &&
342
- Number.isFinite(width) &&
343
- width < w
344
- ) {
345
- w = width;
346
- }
347
- return { width: w, height: natW > 0 ? (w * natH) / natW : natH };
348
- });
349
- }
350
-
351
- applyProps(newProps, oldProps) {
352
- super.applyProps(newProps, oldProps);
353
- this._configureMeasure();
354
- // attributes live in props (children form) or in `source`; either way
355
- // the SvgView is cheap to rebuild on the next measure/paint
356
- this._textContentChanged();
357
- }
358
-
359
- _paintContent(ctx) {
360
- const view = this._ensureView();
361
- if (!view) return;
362
- const content = this.contentBox();
363
- if (content.width <= 0 || content.height <= 0) return;
364
- view.draw(ctx, content.x, content.y, content.width, content.height);
365
- }
366
- }
367
-
368
- /**
369
- * <tex source displayMode size color>: a KaTeX formula via ntk layoutTex.
370
- * Layout is synchronous and headless; the box has an intrinsic size (no
371
- * wrapping). Invalid TeX renders nothing and logs once.
372
- */
373
- export class TexNode extends Node {
374
- constructor(props, app) {
375
- super('tex', props, app);
376
- this.box = null;
377
- this._boxKey = null;
378
- this.yoga.setMeasureFunc(() => {
379
- const box = this._ensureBox();
380
- if (!box) return { width: 0, height: 0 };
381
- return { width: Math.ceil(box.width), height: Math.ceil(box.height) };
382
- });
383
- }
384
-
385
- /** Formula text changed via string children. */
386
- _textContentChanged() {
387
- this._boxKey = null;
388
- this.yoga?.markDirty();
389
- this.root?.invalidate(true);
390
- }
391
-
392
- _ensureBox() {
393
- const { size, color, displayMode, katex } = this.props;
394
- const source = stringChildrenOf(this) ?? this.props.source;
395
- if (!source) return null;
396
- const key = `${source}|${size}|${color}|${displayMode}`;
397
- if (this._boxKey === key) return this.box;
398
- this._boxKey = key;
399
- this.box = null;
400
- try {
401
- this.box = layoutTex(String(source), {
402
- size,
403
- color,
404
- displayMode: displayMode ?? false,
405
- katex,
406
- });
407
- } catch (err) {
408
- console.error('react-x11: <tex> failed to layout:', err.message);
409
- }
410
- return this.box;
411
- }
412
-
413
- applyProps(newProps, oldProps) {
414
- const before = oldProps ?? this.props;
415
- super.applyProps(newProps, oldProps);
416
- if (
417
- newProps.source !== before.source ||
418
- newProps.size !== before.size ||
419
- newProps.color !== before.color ||
420
- newProps.displayMode !== before.displayMode
421
- ) {
422
- this.yoga.markDirty();
423
- this.root?.invalidate(true);
424
- }
425
- }
426
-
427
- _paintContent(ctx) {
428
- const box = this._ensureBox();
429
- // TexBox.draw issues raw XRender requests through ctx.window.app —
430
- // it needs a real ntk 2d context (headless mock contexts skip)
431
- if (!box || !ctx.window?.app?.display) return;
432
- const content = this.contentBox();
433
- ctx.fillStyle = this.props.color ?? '#222222';
434
- box.draw(ctx, content.x, content.y);
435
- }
436
- }