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
@@ -0,0 +1,329 @@
1
+ // The system icon set: the affordance glyphs core's own widgets are drawn
2
+ // with, and the same ones an application or a third-party widget can reach
3
+ // for so that a Select it did not write and a toolbar it did lines up.
4
+ //
5
+ // ## Affordances, not nouns
6
+ //
7
+ // The set holds glyphs that mean something about *the control*: there is
8
+ // more here, this one is chosen, this closes, this is hidden. It holds no
9
+ // nouns — no folder, no document, no save, no printer. Those belong to an
10
+ // icon theme (lucide, an XDG icon theme, the app's own art), they are
11
+ // unbounded in number, and a widget set that starts shipping them has taken
12
+ // on a design system. That line is what keeps this file small enough to
13
+ // read, and it is the answer to "should X be added?".
14
+ //
15
+ // ## One shape per idea
16
+ //
17
+ // Before this existed, core drew a chevron four ways: a filled triangle in
18
+ // Select and Table, and a `▸` text
19
+ // glyph in Menu — which is tofu on a machine without it, the very thing
20
+ // docs/components.md warns applications about. There is now one chevron and
21
+ // the filled caret is gone: a stroked chevron is what a modern control uses
22
+ // to say "more this way", and one idea drawn one way is the point of a set.
23
+ //
24
+ // ## How they are drawn, and why not a font
25
+ //
26
+ // A drawing per icon over `<canvas mono>`: the ink comes from `style.color`,
27
+ // so one drawing serves the resting row, the highlighted row, the disabled
28
+ // one and both colour schemes, and the paint cache keeps one rendered copy
29
+ // for every instance of it on screen. (A `mono` entry bakes its colour for
30
+ // now rather than caching coverage and tinting at composite time — see
31
+ // `CanvasNode.paintCachePlan` for why, and what it costs.) The draw
32
+ // functions are module-level, so their identity is stable across renders and
33
+ // a table of 500 twisties invalidates nothing when its rows re-render.
34
+ //
35
+ // A bundled icon *font* would work — ntk's FontManager takes font bytes, and
36
+ // glyphs composite through a solid source picture, so a whole column of
37
+ // chevrons would be one CompositeGlyphs. It loses on everything else: a
38
+ // binary artifact and a generation toolchain in a package that is otherwise
39
+ // pure JS, baseline rather than box alignment, unhinted mush at 12px, and an
40
+ // icon name routed through text layout and the accessibility tree. Revisit
41
+ // only if a profile of a large tree view shows composite count dominating.
42
+ //
43
+ // The drawings are also deliberately *not* themable. Colour and size are —
44
+ // see `Icon` — but the geometry is core's vocabulary, and an application
45
+ // that wants a different chevron wants an icon library, which is a separate
46
+ // thing it is welcome to bring.
47
+
48
+ import React from 'react';
49
+ import { useTheme } from './theme.js';
50
+
51
+ const h = React.createElement;
52
+
53
+ /**
54
+ * Stroke weight for a glyph in a `size` box.
55
+ *
56
+ * `size / 12` is the 2-on-a-24-grid weight every modern outline set uses,
57
+ * with a floor: below about 1.25 a stroke stops reading as a line at all,
58
+ * and a disclosure twisty is 10px.
59
+ */
60
+ const weight = (size) => Math.max(1.25, size / 12);
61
+
62
+ /**
63
+ * The live area: the ink runs corner to corner apart from this margin, so
64
+ * **`size` is the size of the mark** rather than of a grid it sits in.
65
+ *
66
+ * That is the one place this set departs from how lucide and its
67
+ * descendants draw, and it is deliberate. Their chevron spans half the width
68
+ * and a quarter the height of its box, so a "24px" lucide icon carries about
69
+ * 14px of ink and an author picks `size={20}` to sit beside 14px text. Every
70
+ * call site here is a small number chosen as _how big the mark should be_ —
71
+ * a `Select`'s chevron matches the capitals beside it, a `Checkbox`'s tick
72
+ * fits its 16px well — and the grid convention silently made all of them
73
+ * about 40% too small. The glyphs core drew before the set filled their
74
+ * boxes the same way (the tick was 8×5.5 of ink in a 10×8 canvas), which is
75
+ * what these proportions are matched against.
76
+ *
77
+ * The margin is half a stroke plus a hair: the stroke is centred on the
78
+ * path, so ink at 0.0 would paint outside the box and `<canvas>` clips to
79
+ * its bounds. 0.08 clears it at the smallest size anything is drawn at.
80
+ */
81
+ const PAD = 0.08;
82
+ const END = 1 - PAD;
83
+
84
+ /**
85
+ * Stroke a polyline given in fractions of the box.
86
+ *
87
+ * Fractions rather than a fixed design grid because the box is the only
88
+ * thing the drawing is told about, and it is what the cache is keyed on: an
89
+ * icon at 10px and the same icon at 16px are two entries either way, and
90
+ * scaling here costs nothing while a transform would defeat the unscaled
91
+ * blit the cache goes out of its way to keep.
92
+ *
93
+ * Never sets a colour: under `mono` the ink is preset from `style.color`,
94
+ * and a drawing that named its own would collide with itself in the cache.
95
+ */
96
+ function line(ctx, w, hgt, points) {
97
+ ctx.lineWidth = weight(Math.min(w, hgt));
98
+ ctx.lineCap = 'round';
99
+ ctx.lineJoin = 'round';
100
+ ctx.beginPath();
101
+ for (let i = 0; i < points.length; i++) {
102
+ const [x, y] = points[i];
103
+ if (i === 0) ctx.moveTo(x * w, y * hgt);
104
+ else ctx.lineTo(x * w, y * hgt);
105
+ }
106
+ ctx.stroke();
107
+ }
108
+
109
+ /** A filled disc, in fractions of the box. */
110
+ function disc(ctx, w, hgt, cx, cy, r) {
111
+ ctx.beginPath();
112
+ ctx.arc(cx * w, cy * hgt, r * Math.min(w, hgt), 0, Math.PI * 2);
113
+ ctx.fill();
114
+ }
115
+
116
+ /**
117
+ * The eye's outline: two quadratics meeting at the corners, which is the
118
+ * almond every peek toggle is drawn as. A quadratic reaches only half way to
119
+ * its control point, so the lids are pushed past the box (`-0.15`, `1.15`)
120
+ * to arrive at 0.175 and 0.825 — and the curve stays flatter than a circle,
121
+ * which is what stops it reading as a lemon.
122
+ */
123
+ function almond(ctx, w, hgt) {
124
+ ctx.lineWidth = weight(Math.min(w, hgt));
125
+ ctx.lineCap = 'round';
126
+ ctx.lineJoin = 'round';
127
+ ctx.beginPath();
128
+ ctx.moveTo(0.06 * w, 0.5 * hgt);
129
+ ctx.quadraticCurveTo(0.5 * w, -0.15 * hgt, 0.94 * w, 0.5 * hgt);
130
+ ctx.quadraticCurveTo(0.5 * w, 1.15 * hgt, 0.06 * w, 0.5 * hgt);
131
+ ctx.stroke();
132
+ ctx.beginPath();
133
+ ctx.arc(0.5 * w, 0.5 * hgt, 0.17 * Math.min(w, hgt), 0, Math.PI * 2);
134
+ ctx.stroke();
135
+ }
136
+
137
+ /**
138
+ * The set. Each entry is an `onDraw` — `(ctx, { width, height })` — with the
139
+ * signature `<canvas>` already speaks, so an application can pass one
140
+ * straight to `<canvas mono>` without going through `<Icon>`:
141
+ *
142
+ * <canvas mono cacheKey="check" onDraw={icons.check} style={…} />
143
+ *
144
+ * Module-level and frozen: identity is the thing `CanvasNode` compares to
145
+ * decide whether a re-render changed the picture, so these must never be
146
+ * rebuilt per render.
147
+ */
148
+ export const icons = Object.freeze({
149
+ // The four chevrons are four drawings rather than one rotated, because a
150
+ // rotation is a transform and the cache only composites unscaled,
151
+ // unrotated surfaces — and because a 10px chevron's crispness is a
152
+ // different problem per direction.
153
+ //
154
+ // Arms at 45°, so the ink is twice as wide as it is tall (or the other way
155
+ // round): the long axis takes the whole live area and the short one half
156
+ // of it, centred. A chevron is the flattest glyph in the set for that
157
+ // reason, and it is why `size` for one reads as its *width*.
158
+ chevronRight: (ctx, { width: w, height: hgt }) =>
159
+ line(ctx, w, hgt, [
160
+ [0.29, PAD],
161
+ [0.71, 0.5],
162
+ [0.29, END],
163
+ ]),
164
+ chevronLeft: (ctx, { width: w, height: hgt }) =>
165
+ line(ctx, w, hgt, [
166
+ [0.71, PAD],
167
+ [0.29, 0.5],
168
+ [0.71, END],
169
+ ]),
170
+ chevronDown: (ctx, { width: w, height: hgt }) =>
171
+ line(ctx, w, hgt, [
172
+ [PAD, 0.29],
173
+ [0.5, 0.71],
174
+ [END, 0.29],
175
+ ]),
176
+ chevronUp: (ctx, { width: w, height: hgt }) =>
177
+ line(ctx, w, hgt, [
178
+ [PAD, 0.71],
179
+ [0.5, 0.29],
180
+ [END, 0.71],
181
+ ]),
182
+
183
+ check: (ctx, { width: w, height: hgt }) =>
184
+ line(ctx, w, hgt, [
185
+ [PAD, 0.53],
186
+ [0.34, 0.79],
187
+ [END, 0.21],
188
+ ]),
189
+ // The mixed state of a checkbox and a menu's `toggleState === -1`: a bar,
190
+ // because "some of these" is not "none of these" and an empty well says
191
+ // the second.
192
+ dash: (ctx, { width: w, height: hgt }) =>
193
+ line(ctx, w, hgt, [
194
+ [PAD, 0.5],
195
+ [END, 0.5],
196
+ ]),
197
+ // A radio's mark, and a menu row standing in for one. Smaller than the
198
+ // live area on purpose: a solid disc carries far more ink per pixel than
199
+ // a stroke, and one drawn out to `PAD` reads as a blob beside a chevron
200
+ // rather than as the same size.
201
+ dot: (ctx, { width: w, height: hgt }) => disc(ctx, w, hgt, 0.5, 0.5, 0.26),
202
+
203
+ // Inset past the live area, for the mirror of the same reason: an X that
204
+ // reaches the corners measures longer on the diagonal than anything else
205
+ // in the set measures on its side, and looks it.
206
+ close: (ctx, { width: w, height: hgt }) => {
207
+ line(ctx, w, hgt, [
208
+ [0.13, 0.13],
209
+ [0.87, 0.87],
210
+ ]);
211
+ line(ctx, w, hgt, [
212
+ [0.87, 0.13],
213
+ [0.13, 0.87],
214
+ ]);
215
+ },
216
+ plus: (ctx, { width: w, height: hgt }) => {
217
+ line(ctx, w, hgt, [
218
+ [0.5, PAD],
219
+ [0.5, END],
220
+ ]);
221
+ line(ctx, w, hgt, [
222
+ [PAD, 0.5],
223
+ [END, 0.5],
224
+ ]);
225
+ },
226
+ // Overflow. Vertical because that is where it goes — at the end of a row,
227
+ // opening a menu below it.
228
+ moreVertical: (ctx, { width: w, height: hgt }) => {
229
+ disc(ctx, w, hgt, 0.5, 0.15, 0.115);
230
+ disc(ctx, w, hgt, 0.5, 0.5, 0.115);
231
+ disc(ctx, w, hgt, 0.5, 0.85, 0.115);
232
+ },
233
+
234
+ eye: (ctx, { width: w, height: hgt }) => almond(ctx, w, hgt),
235
+ eyeOff: (ctx, { width: w, height: hgt }) => {
236
+ almond(ctx, w, hgt);
237
+ line(ctx, w, hgt, [
238
+ [0.1, 0.9],
239
+ [0.9, 0.1],
240
+ ]);
241
+ },
242
+ });
243
+
244
+ /** Every name in the set, for a runtime check or a gallery. */
245
+ export const iconNames = Object.freeze(Object.keys(icons));
246
+
247
+ /**
248
+ * The size an icon takes when nothing says otherwise: a shade under the
249
+ * text it sits beside, which at the default 14px body is the 12px the menu
250
+ * gutter was already built around.
251
+ *
252
+ * Derived from `fontSize` rather than given a palette token of its own, for
253
+ * the reason the popup radii are: a theme that scales the type wants the
254
+ * glyphs to scale with it, and a theme that does not should not have to
255
+ * remember a token to keep them in proportion. The per-call `size` prop is
256
+ * the seam for the one icon that needs to be bigger.
257
+ */
258
+ export const iconSize = (fontSize) => Math.round(fontSize * 0.85);
259
+
260
+ /**
261
+ * <Icon name size color /> — one glyph from the system set.
262
+ *
263
+ * ```jsx
264
+ * <Icon name="chevronDown" />
265
+ * <Icon name="check" size={10} color={theme.accentText} />
266
+ * ```
267
+ *
268
+ * ## Colour inherits; size does not
269
+ *
270
+ * `color` travels down the tree, so an icon inside a row that dims itself
271
+ * dims with it, and a `:hover` block that sets `color` on the row reaches
272
+ * the icon exactly as it reaches the row's label — the ink is an inherited
273
+ * property, and the state block changes it on the row (docs/styling.md).
274
+ * The `color` prop here is for saying something the surrounding text does
275
+ * not: a destructive action's mark, a check drawn on an accent fill.
276
+ *
277
+ * **`size` does not inherit** and is deliberately not `fontSize`. A glyph is
278
+ * a drawing rather than a letter — it has no baseline to sit on and no
279
+ * ascent to be measured against — so it takes its default from the palette's
280
+ * `fontSize` (`iconSize`) rather than from whatever text happens to be
281
+ * around it, which keeps a chevron the same size in a row that shrank its
282
+ * label. Pass `size` for the one icon that has to be bigger.
283
+ *
284
+ * Everything else is a `<canvas>`: `style` merges last, and the rest of the
285
+ * props go straight through, so a clickable icon is `<Icon onClick focusable/>`.
286
+ * `aria-hidden` defaults to true because an affordance glyph is decoration —
287
+ * the meaning is already on the control, in its `role` and its
288
+ * `aria-expanded`. Name it (`aria-hidden={false} aria-label="Close"`) only
289
+ * when the icon *is* the control and nothing else says so.
290
+ */
291
+ export function Icon({
292
+ name,
293
+ size,
294
+ color,
295
+ style,
296
+ 'aria-hidden': ariaHidden = true,
297
+ ...canvasProps
298
+ }) {
299
+ // Before the hook on purpose: a name outside the set never renders, so
300
+ // there is no hook order to keep, and this way the check is reachable
301
+ // without a tree — which is what makes it testable and what makes the
302
+ // message land on the call site rather than inside a paint.
303
+ const draw = icons[name];
304
+ if (!draw) {
305
+ throw new Error(
306
+ `<Icon name="${name}">: not a system icon. One of: ${iconNames.join(', ')}`,
307
+ );
308
+ }
309
+ const theme = useTheme();
310
+ const px = size ?? iconSize(theme.fontSize);
311
+ return h('canvas', {
312
+ mono: true,
313
+ // The size is already in the cache key — the plan carries the node's
314
+ // width and height — so the name is the whole of what this adds, and
315
+ // the colour is out of it on purpose: that is what `mono` buys.
316
+ cacheKey: name,
317
+ onDraw: draw,
318
+ 'aria-hidden': ariaHidden,
319
+ ...canvasProps,
320
+ // No `color` unless the call site named one: leaving it off the style is
321
+ // what lets it inherit, where `color: theme.text` would pin every icon to
322
+ // the palette and undo the row it sits in.
323
+ style: [
324
+ { width: px, height: px, flexShrink: 0 },
325
+ color ? { color } : null,
326
+ style,
327
+ ],
328
+ });
329
+ }