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.
- package/README.md +330 -81
- package/package.json +132 -14
- package/src/ClickToComponent.js +9 -2
- package/src/DevToolsIntegration.js +440 -32
- package/src/Reconciler.js +463 -165
- package/src/a11y.js +1299 -0
- package/src/acceleratorhooks.js +86 -0
- package/src/accelerators.js +128 -0
- package/src/activate.js +168 -0
- package/src/anchor.js +360 -0
- package/src/appcontext.js +161 -0
- package/src/appearance.js +748 -0
- package/src/appearancehooks.js +96 -0
- package/src/apphooks.js +73 -0
- package/src/application.js +748 -0
- package/src/atspi.js +1953 -0
- package/src/bus.js +545 -0
- package/src/bushooks.js +136 -0
- package/src/clientmessage.js +140 -0
- package/src/clipboard.js +121 -0
- package/src/components/Button.js +148 -33
- package/src/components/Checkbox.js +66 -32
- package/src/components/Dialog.js +96 -29
- package/src/components/FileDialog.js +491 -0
- package/src/components/Icon.js +329 -0
- package/src/components/Menu.js +797 -133
- package/src/components/PasswordInput.js +434 -0
- package/src/components/ProgressBar.js +91 -12
- package/src/components/Radio.js +78 -35
- package/src/components/Select.js +255 -65
- package/src/components/Slider.js +102 -41
- package/src/components/SplitPane.js +206 -0
- package/src/components/Switch.js +100 -22
- package/src/components/Table.js +529 -0
- package/src/components/Tabs.js +211 -0
- package/src/components/Tooltip.js +528 -54
- package/src/components/anchor.js +154 -107
- package/src/components/change.js +34 -0
- package/src/components/dnd.js +103 -0
- package/src/components/index.js +16 -3
- package/src/components/keys.js +17 -20
- package/src/components/scribble.js +247 -0
- package/src/components/theme.js +350 -42
- package/src/components/typeahead.js +12 -2
- package/src/compose.js +868 -0
- package/src/compositing.js +224 -0
- package/src/dbusmenu.js +384 -0
- package/src/debug.d.ts +63 -0
- package/src/debug.js +642 -0
- package/src/decorations.js +486 -0
- package/src/desktopsettings.js +209 -0
- package/src/desktopsettingshooks.js +62 -0
- package/src/dnd.js +1725 -0
- package/src/editmenu.js +272 -0
- package/src/errors.js +98 -0
- package/src/events.js +1406 -162
- package/src/extensions.js +45 -0
- package/src/filedialog.js +375 -0
- package/src/filedialoghooks.js +132 -0
- package/src/fonthooks.js +64 -0
- package/src/fonts.js +301 -0
- package/src/foreignnodes.js +519 -0
- package/src/frame/child.js +33 -0
- package/src/frame/childmain.js +274 -0
- package/src/frame/env.js +140 -0
- package/src/frame/index.js +444 -0
- package/src/frame/lifecycle.js +67 -0
- package/src/frame/protocol.js +179 -0
- package/src/frames.js +73 -0
- package/src/glbackend.js +114 -0
- package/src/glnodes.js +125 -67
- package/src/globalmenu.js +683 -0
- package/src/host.d.ts +88 -0
- package/src/host.js +42 -0
- package/src/idle.js +490 -0
- package/src/idlehooks.js +100 -0
- package/src/imagesource.js +349 -0
- package/src/index.d.ts +475 -0
- package/src/index.js +83 -9
- package/src/inputtime.js +206 -0
- package/src/jsx-dev-runtime.d.ts +21 -0
- package/src/jsx-dev-runtime.js +2 -0
- package/src/jsx-runtime.d.ts +39 -0
- package/src/jsx-runtime.js +6 -0
- package/src/keyboard.js +256 -0
- package/src/keyboardstate.js +278 -0
- package/src/keyboardstatehooks.js +58 -0
- package/src/keysyms.d.ts +149 -0
- package/src/keysyms.js +270 -0
- package/src/locale.js +170 -0
- package/src/localehooks.js +47 -0
- package/src/menuitem.js +223 -0
- package/src/node.d.ts +597 -0
- package/src/node.js +44 -0
- package/src/nodes.js +9546 -690
- package/src/ntk.d.ts +44 -0
- package/src/ntk.js +25 -0
- package/src/paintcache.js +366 -0
- package/src/palette.js +380 -0
- package/src/pastestate.js +66 -0
- package/src/portal.js +461 -0
- package/src/priority.js +26 -0
- package/src/refresh/index.d.ts +40 -0
- package/src/refresh/index.js +122 -0
- package/src/refresh/loader.d.ts +37 -0
- package/src/refresh/loader.js +401 -0
- package/src/refresh/register.d.ts +5 -0
- package/src/refresh/register.js +13 -0
- package/src/registry.js +232 -0
- package/src/scale.js +626 -0
- package/src/scalehooks.js +27 -0
- package/src/screencolor.js +640 -0
- package/src/screencolorhooks.js +101 -0
- package/src/screens.js +754 -0
- package/src/screenshooks.js +137 -0
- package/src/startup.js +302 -0
- package/src/style.d.ts +126 -0
- package/src/style.js +33 -0
- package/src/styles.js +1482 -6
- package/src/svgnodes.js +306 -0
- package/src/testing/a11y.js +484 -0
- package/src/testing/components.js +414 -0
- package/src/testing/events.js +407 -0
- package/src/testing/harness.js +455 -0
- package/src/testing/index.d.ts +558 -0
- package/src/testing/index.js +85 -0
- package/src/testing/mock-app.js +463 -0
- package/src/testing/pixels.js +152 -0
- package/src/testing/queries.js +224 -0
- package/src/textrange.js +83 -0
- package/src/textselection.js +439 -0
- package/src/trace-registry.js +63 -0
- package/src/transfer.js +93 -0
- package/src/types/appearance.d.ts +84 -0
- package/src/types/application.d.ts +173 -0
- package/src/types/components.d.ts +820 -0
- package/src/types/dbus.d.ts +177 -0
- package/src/types/elements.d.ts +897 -0
- package/src/types/events.d.ts +549 -0
- package/src/types/filedialog.d.ts +260 -0
- package/src/types/fonts.d.ts +124 -0
- package/src/types/frame.d.ts +146 -0
- package/src/types/globalmenu.d.ts +42 -0
- package/src/types/nodes.d.ts +199 -0
- package/src/types/screencolor.d.ts +84 -0
- package/src/types/style.d.ts +434 -0
- package/src/types/system.d.ts +287 -0
- package/src/windowid.js +151 -0
- package/src/windowstate.js +393 -0
- package/src/xsettings.js +336 -0
- package/src/yoga.d.ts +55 -0
- package/src/yoga.js +135 -0
- package/src/components/Canvas3D.js +0 -28
- package/src/geometry3d.js +0 -223
- package/src/pointer3d.js +0 -158
- package/src/raycast3d.js +0 -146
- package/src/richnodes.js +0 -436
- package/src/scene3d.js +0 -683
package/src/svgnodes.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// `<svg>`: a retained-node wrapper over ntk's SvgView. The document is
|
|
2
|
+
// either JSX children — declarative SVG elements, one `SvgChildNode` each,
|
|
3
|
+
// serialized into the htmlparser2-style DOM SvgView consumes — or a `source`
|
|
4
|
+
// markup string, and it is sized like `<image>` from its viewBox.
|
|
5
|
+
//
|
|
6
|
+
// This file used to hold `<markdown>`, `<html>` and `<tex>` beside it, over
|
|
7
|
+
// ntk's document widgets. Those widgets rendered a document as one opaque
|
|
8
|
+
// drawing, which foreclosed both selection and per-block streaming; their
|
|
9
|
+
// successors are components in `@react-x11/components` (`<Markdown>`,
|
|
10
|
+
// `<Formula>`), composed from public host elements. `<svg>` stays here
|
|
11
|
+
// because it is not a document — it is a drawing, and one node is the right
|
|
12
|
+
// grain for it.
|
|
13
|
+
import { SvgView } from 'ntk';
|
|
14
|
+
|
|
15
|
+
import { Node, intrinsicSize } from './nodes.js';
|
|
16
|
+
import { isLayoutProp, isPaintProp } from './styles.js';
|
|
17
|
+
|
|
18
|
+
// SVG attributes that are camelCase in SVG itself; every other camelCase
|
|
19
|
+
// prop (strokeWidth, fillRule, textAnchor, …) kebab-cases, like React DOM.
|
|
20
|
+
const SVG_CAMEL_ATTRS = new Set([
|
|
21
|
+
'viewBox',
|
|
22
|
+
'preserveAspectRatio',
|
|
23
|
+
'gradientUnits',
|
|
24
|
+
'gradientTransform',
|
|
25
|
+
'spreadMethod',
|
|
26
|
+
'patternUnits',
|
|
27
|
+
'patternContentUnits',
|
|
28
|
+
'patternTransform',
|
|
29
|
+
'clipPathUnits',
|
|
30
|
+
'maskUnits',
|
|
31
|
+
'maskContentUnits',
|
|
32
|
+
'markerWidth',
|
|
33
|
+
'markerHeight',
|
|
34
|
+
'markerUnits',
|
|
35
|
+
'refX',
|
|
36
|
+
'refY',
|
|
37
|
+
'textLength',
|
|
38
|
+
'lengthAdjust',
|
|
39
|
+
'stdDeviation',
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const toSvgAttrib = (name) =>
|
|
43
|
+
SVG_CAMEL_ATTRS.has(name)
|
|
44
|
+
? name
|
|
45
|
+
: name.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
|
|
46
|
+
|
|
47
|
+
function svgAttribs(props, skip) {
|
|
48
|
+
const attribs = {};
|
|
49
|
+
for (const key of Object.keys(props)) {
|
|
50
|
+
const value = props[key];
|
|
51
|
+
if (key === 'children' || value == null) continue;
|
|
52
|
+
if (typeof value === 'function') continue;
|
|
53
|
+
if (skip && skip(key)) continue;
|
|
54
|
+
attribs[toSvgAttrib(key)] = String(value);
|
|
55
|
+
}
|
|
56
|
+
return attribs;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* An element inside <svg>: no yoga node, no painting of its own — it only
|
|
61
|
+
* carries a tag name and props, and is serialized into the htmlparser2-style
|
|
62
|
+
* DOM that SvgView consumes. Prop/child changes bubble to the owning
|
|
63
|
+
* SvgNode via the _textContentChanged channel (also used by text chunks,
|
|
64
|
+
* so string children of an SVG <text> invalidate the same way).
|
|
65
|
+
*/
|
|
66
|
+
export class SvgChildNode extends Node {
|
|
67
|
+
constructor(tag, props, app) {
|
|
68
|
+
super(tag, props, app, { yoga: false });
|
|
69
|
+
this.isSvgChild = true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// <circle cx r fill>… is SVG's vocabulary, not the style channel's
|
|
73
|
+
get stylable() {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_textContentChanged() {
|
|
78
|
+
this.parent?._textContentChanged();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
applyProps(newProps, oldProps) {
|
|
82
|
+
super.applyProps(newProps, oldProps);
|
|
83
|
+
this._textContentChanged();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
toDom() {
|
|
87
|
+
return {
|
|
88
|
+
type: 'tag',
|
|
89
|
+
name: this.kind,
|
|
90
|
+
attribs: svgAttribs(this.props),
|
|
91
|
+
children: this.children
|
|
92
|
+
.map((c) =>
|
|
93
|
+
c.kind === 'textchunk'
|
|
94
|
+
? { type: 'text', data: c.text }
|
|
95
|
+
: c.isSvgChild
|
|
96
|
+
? c.toDom()
|
|
97
|
+
: null,
|
|
98
|
+
)
|
|
99
|
+
.filter(Boolean),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* <svg>: ntk SvgView. Content is either JSX children (React-DOM style —
|
|
106
|
+
* <svg viewBox="0 0 24 24"><circle cx={12} … />; children win when both
|
|
107
|
+
* are present) or a `source` markup string. Sized like <image>: natural
|
|
108
|
+
* (viewBox) size, kept to its aspect ratio when the style constrains only
|
|
109
|
+
* one axis; scales to the content box.
|
|
110
|
+
*/
|
|
111
|
+
export class SvgNode extends Node {
|
|
112
|
+
constructor(props, app) {
|
|
113
|
+
super('svg', props, app);
|
|
114
|
+
this.view = null;
|
|
115
|
+
this._stale = true;
|
|
116
|
+
/** Bumped every time the document is rebuilt, and sampled at paint time,
|
|
117
|
+
* so `paintCachePlan` can tell an animated document from a settled one.
|
|
118
|
+
* See the note there. */
|
|
119
|
+
this._docRevision = 0;
|
|
120
|
+
this._paintedRevision = -1;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The viewBox is the natural size; `<image>`'s rules do the rest —
|
|
124
|
+
* including the unit: natural sizes are logical pixels, multiplied to
|
|
125
|
+
* device here so an unsized drawing keeps its size relative to
|
|
126
|
+
* everything else. Unlike a bitmap it costs nothing: the document
|
|
127
|
+
* rasterizes at whatever box layout settles on (src/scale.js). */
|
|
128
|
+
measureContent(constraints) {
|
|
129
|
+
const view = this._ensureView();
|
|
130
|
+
const s = this.scale;
|
|
131
|
+
return intrinsicSize(
|
|
132
|
+
{
|
|
133
|
+
width: (view?.naturalWidth ?? 0) * s,
|
|
134
|
+
height: (view?.naturalHeight ?? 0) * s,
|
|
135
|
+
},
|
|
136
|
+
constraints,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Any change to the svg subtree (props, children, text) lands here. */
|
|
141
|
+
_textContentChanged() {
|
|
142
|
+
this._stale = true;
|
|
143
|
+
this.invalidateMeasure('props');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
_ensureView() {
|
|
147
|
+
if (!this._stale) return this.view;
|
|
148
|
+
this._stale = false;
|
|
149
|
+
this._docRevision++;
|
|
150
|
+
this.view = null;
|
|
151
|
+
this._docKey = null;
|
|
152
|
+
try {
|
|
153
|
+
const children = this.children.filter(
|
|
154
|
+
(c) => c.isSvgChild || c.kind === 'textchunk',
|
|
155
|
+
);
|
|
156
|
+
if (children.length > 0) {
|
|
157
|
+
const skip = (key) =>
|
|
158
|
+
key === 'source' ||
|
|
159
|
+
isLayoutProp(key) ||
|
|
160
|
+
isPaintProp(key) ||
|
|
161
|
+
key === 'cursor' ||
|
|
162
|
+
key === 'focusable' ||
|
|
163
|
+
key === 'pointerEvents';
|
|
164
|
+
const dom = {
|
|
165
|
+
type: 'tag',
|
|
166
|
+
name: 'svg',
|
|
167
|
+
attribs: svgAttribs(this.props, skip),
|
|
168
|
+
children: children.map((c) =>
|
|
169
|
+
c.kind === 'textchunk' ? { type: 'text', data: c.text } : c.toDom(),
|
|
170
|
+
),
|
|
171
|
+
};
|
|
172
|
+
// The document's identity, for the paint cache. Built here rather
|
|
173
|
+
// than at paint time on purpose: this runs once per content change,
|
|
174
|
+
// where paint runs every frame — and it is the serialization that
|
|
175
|
+
// makes two cells holding the same drawing one cache entry instead
|
|
176
|
+
// of two. Key order follows prop order, so two logically identical
|
|
177
|
+
// documents written differently miss rather than collide, which is
|
|
178
|
+
// the harmless direction.
|
|
179
|
+
this._docKey = JSON.stringify(dom);
|
|
180
|
+
this.view = new SvgView(null).setSvgDom(dom);
|
|
181
|
+
} else if (this.props.source) {
|
|
182
|
+
this._docKey = String(this.props.source);
|
|
183
|
+
this.view = new SvgView(null).setSvg(this._docKey);
|
|
184
|
+
}
|
|
185
|
+
} catch (err) {
|
|
186
|
+
console.error('react-x11: <svg> failed to parse:', err.message);
|
|
187
|
+
}
|
|
188
|
+
return this.view;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
applyProps(newProps, oldProps) {
|
|
192
|
+
super.applyProps(newProps, oldProps);
|
|
193
|
+
// attributes live in props (children form) or in `source`; either way
|
|
194
|
+
// the SvgView is cheap to rebuild on the next measure/paint
|
|
195
|
+
this._textContentChanged();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
paintContent(ctx) {
|
|
199
|
+
const view = this._ensureView();
|
|
200
|
+
if (!view) return;
|
|
201
|
+
const content = this.contentBox();
|
|
202
|
+
if (content.width <= 0 || content.height <= 0) return;
|
|
203
|
+
view.draw(ctx, content.x, content.y, content.width, content.height, {
|
|
204
|
+
color: this._currentColor(),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* What `fill="currentColor"` resolves to here.
|
|
210
|
+
*
|
|
211
|
+
* The node's own `color`, then what it inherits, then the palette's — the
|
|
212
|
+
* same resolution `<text>` and `<canvas mono>` use, and it has to be, or
|
|
213
|
+
* `currentColor` means something different depending on which element is
|
|
214
|
+
* drawing. It used to stop at a hardcoded black, which is invisible on a
|
|
215
|
+
* dark desktop and does not follow a `<box style={{ color }}>` either.
|
|
216
|
+
*/
|
|
217
|
+
_currentColor() {
|
|
218
|
+
return this.resolvedTextStyle().color;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Cache plan (see `Node.paintContent` for the protocol).
|
|
223
|
+
*
|
|
224
|
+
* The key is the document, the size it is drawn at, and the device scale.
|
|
225
|
+
* Everything else that could change the pixels is either handled at blit
|
|
226
|
+
* time or deliberately excluded:
|
|
227
|
+
*
|
|
228
|
+
* - `globalAlpha`, the clip and any ancestor translation are applied by
|
|
229
|
+
* `drawImage`, so an ancestor animating opacity is a cache *hit*;
|
|
230
|
+
* - colour is out of the key for a `mono` document, because the entry is
|
|
231
|
+
* coverage and the colour arrives at blit time — one entry then serves
|
|
232
|
+
* hover, `:disabled` and every theme;
|
|
233
|
+
* - a `multi` document bakes its colours, so the resolved colour joins the
|
|
234
|
+
* key. It rarely varies, so the extra component costs nothing.
|
|
235
|
+
*
|
|
236
|
+
* Sizes are rounded so the blit is unscaled: a scaled `drawImage` brackets
|
|
237
|
+
* every composite with SetPictureTransform and a filter change, which for
|
|
238
|
+
* a wall of icons is four extra requests each. The cost is that a cached
|
|
239
|
+
* drawing sits on the pixel grid where a live one could straddle it, which
|
|
240
|
+
* for icon-sized content is not visible.
|
|
241
|
+
*
|
|
242
|
+
* ## Animated documents opt themselves out
|
|
243
|
+
*
|
|
244
|
+
* A document that was rebuilt since this node's last paint is being
|
|
245
|
+
* animated, and caching it is a pure loss: the entry is written, composited
|
|
246
|
+
* once at most, and never matched again. The cache has a "seen twice" gate
|
|
247
|
+
* for exactly this, but content keying defeats it here — a wall of the same
|
|
248
|
+
* animated drawing at different phases produces documents that recur across
|
|
249
|
+
* *nodes* by coincidence, which is enough sightings to pass the gate and
|
|
250
|
+
* never enough to pay it back. Measured on a wall of 30 animated gauges:
|
|
251
|
+
* 450 of 1076 distinct documents recurred, 12% of lookups hit, and each
|
|
252
|
+
* miss-that-cached bought a CreatePixmap plus a second rasterization of a
|
|
253
|
+
* drawing that had already been painted live. On a server where offscreen
|
|
254
|
+
* rasterization is a software fallback that doubled the frame's fence.
|
|
255
|
+
*
|
|
256
|
+
* So the revision has to match the one this node last painted. A node that
|
|
257
|
+
* has never painted has nothing to compare and stays speculative, exactly
|
|
258
|
+
* as before — the "seen twice" gate is still the thing deciding, so a wall
|
|
259
|
+
* of identical icons fills on its first frame from its own sibling cells.
|
|
260
|
+
* Only a document that then *changes* opts out, which is the signal that it
|
|
261
|
+
* was never going to be matched again.
|
|
262
|
+
*/
|
|
263
|
+
paintCachePlan() {
|
|
264
|
+
const view = this._ensureView();
|
|
265
|
+
if (!view || !this._docKey) return null;
|
|
266
|
+
const painted = this._paintedRevision;
|
|
267
|
+
this._paintedRevision = this._docRevision;
|
|
268
|
+
if (painted >= 0 && painted !== this._docRevision) return null;
|
|
269
|
+
const content = this.contentBox();
|
|
270
|
+
const width = Math.round(content.width);
|
|
271
|
+
const height = Math.round(content.height);
|
|
272
|
+
if (width <= 0 || height <= 0) return null;
|
|
273
|
+
|
|
274
|
+
const mono = view.paintKind === 'mono';
|
|
275
|
+
// a mono document paints in exactly one colour: its own, or ours
|
|
276
|
+
const tint = !mono
|
|
277
|
+
? null
|
|
278
|
+
: view.soloPaint === 'currentColor'
|
|
279
|
+
? this._currentColor()
|
|
280
|
+
: view.soloPaint;
|
|
281
|
+
if (mono && !tint) return null; // nothing in the document paints at all
|
|
282
|
+
|
|
283
|
+
const size = `${width}x${height}@1`;
|
|
284
|
+
return {
|
|
285
|
+
key: mono
|
|
286
|
+
? `svg|${size}|${this._docKey}`
|
|
287
|
+
: `svg|${size}|${this._currentColor()}|${this._docKey}`,
|
|
288
|
+
x: Math.round(content.x),
|
|
289
|
+
y: Math.round(content.y),
|
|
290
|
+
width,
|
|
291
|
+
height,
|
|
292
|
+
format: mono ? 'a8' : 'argb32',
|
|
293
|
+
tint,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
paintCached(ctx, box) {
|
|
298
|
+
const view = this._ensureView();
|
|
299
|
+
if (!view) return;
|
|
300
|
+
// Into a coverage surface, only the alpha of a paint survives, and the
|
|
301
|
+
// tint arrives at blit time — so any opaque colour renders the same mask.
|
|
302
|
+
view.draw(ctx, box.x, box.y, box.width, box.height, {
|
|
303
|
+
color: view.paintKind === 'mono' ? '#ffffff' : this._currentColor(),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|