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,897 @@
1
+ /**
2
+ * The host elements. Only `<window>`, `<popup>`, `<glarea>` and `<foreign>`
3
+ * are real X11 windows; everything else is a retained node laid out by yoga
4
+ * and painted into the owning window. See docs/elements.md.
5
+ */
6
+
7
+ import type { Ref, RefObject, ReactNode, Key } from 'react';
8
+ import type { Color, Cursor, StyleProp } from './style.js';
9
+ import type {
10
+ DrawnNode,
11
+ NtkWindow,
12
+ ScrollableNode,
13
+ TextInputNode,
14
+ } from './nodes.js';
15
+ import type { AnchorOptions } from './components.js';
16
+ import type {
17
+ ChangeEvent,
18
+ SelectionChangeEvent,
19
+ ClientMessageEvent,
20
+ EventHandlers,
21
+ MouseEvent,
22
+ ScrollEvent,
23
+ SubmitEvent,
24
+ SyntheticEvent,
25
+ ViewportEvent,
26
+ WindowResizeEvent,
27
+ } from './events.js';
28
+
29
+ /** Props every element takes. */
30
+ export interface CommonProps {
31
+ key?: Key;
32
+ children?: ReactNode;
33
+ style?: StyleProp;
34
+ }
35
+
36
+ /** Focus and interaction, on drawn elements and windows alike. */
37
+ export interface InteractionProps {
38
+ focusable?: boolean;
39
+ /** Sequential focus order; `-1` is focusable but not tabbable. */
40
+ tabIndex?: number;
41
+ autoFocus?: boolean;
42
+ /**
43
+ * Own a focus scope: Tab and presses stay inside, and focus is restored
44
+ * when it unmounts. This is what makes a modal.
45
+ */
46
+ trapFocus?: boolean;
47
+ /** Never focusable, and the trigger for a `:disabled` style block. Also
48
+ * clears the AT-SPI ENABLED/SENSITIVE states. */
49
+ disabled?: boolean;
50
+ }
51
+
52
+ /**
53
+ * The ARIA role vocabulary the AT-SPI bridge maps (src/a11y.js). An
54
+ * unknown string falls back to the element default and warns in DEV; the
55
+ * open union keeps forward compatibility.
56
+ */
57
+ export type A11yRole =
58
+ | 'alert'
59
+ | 'alertdialog'
60
+ | 'article'
61
+ | 'banner'
62
+ | 'blockquote'
63
+ | 'button'
64
+ | 'caption'
65
+ | 'cell'
66
+ | 'checkbox'
67
+ | 'columnheader'
68
+ | 'combobox'
69
+ | 'comment'
70
+ | 'complementary'
71
+ | 'contentinfo'
72
+ | 'dialog'
73
+ | 'document'
74
+ | 'form'
75
+ | 'grid'
76
+ | 'gridcell'
77
+ | 'group'
78
+ | 'heading'
79
+ | 'img'
80
+ | 'link'
81
+ | 'list'
82
+ | 'listbox'
83
+ | 'listitem'
84
+ | 'log'
85
+ | 'main'
86
+ | 'marquee'
87
+ | 'math'
88
+ | 'menu'
89
+ | 'menubar'
90
+ | 'menuitem'
91
+ | 'menuitemcheckbox'
92
+ | 'menuitemradio'
93
+ | 'meter'
94
+ | 'navigation'
95
+ | 'none'
96
+ | 'note'
97
+ | 'option'
98
+ | 'paragraph'
99
+ | 'presentation'
100
+ | 'progressbar'
101
+ | 'radio'
102
+ | 'radiogroup'
103
+ | 'region'
104
+ | 'row'
105
+ | 'rowheader'
106
+ | 'scrollbar'
107
+ | 'search'
108
+ | 'searchbox'
109
+ | 'separator'
110
+ | 'slider'
111
+ | 'spinbutton'
112
+ | 'status'
113
+ | 'switch'
114
+ | 'tab'
115
+ | 'table'
116
+ | 'tablist'
117
+ | 'tabpanel'
118
+ | 'term'
119
+ | 'textbox'
120
+ | 'timer'
121
+ | 'toolbar'
122
+ | 'tooltip'
123
+ | 'tree'
124
+ | 'treegrid'
125
+ | 'treeitem'
126
+ | 'window'
127
+ | (string & {});
128
+
129
+ /** An action assistive technology asked the app to perform. */
130
+ export interface A11yActionEvent {
131
+ action: 'setValue';
132
+ /** For `setValue`: the value the AT wants (AT-SPI `Value.CurrentValue`). */
133
+ value?: number;
134
+ }
135
+
136
+ /**
137
+ * Accessibility, in the web's vocabulary — the same `role` / `aria-*`
138
+ * names react-dom accepts. Read by the AT-SPI bridge (docs/accessibility.md);
139
+ * inert (and harmless) where there is no accessibility bus. Every host
140
+ * element takes these, which is the whole seam a component library needs.
141
+ */
142
+ export interface A11yProps {
143
+ /** What this element *is* to a screen reader. */
144
+ role?: A11yRole;
145
+ /** The accessible name, above every other name source. */
146
+ 'aria-label'?: string;
147
+ /** Supplementary description (AT-SPI `Description`). */
148
+ 'aria-description'?: string;
149
+ /** Remove this subtree from the accessible tree entirely. */
150
+ 'aria-hidden'?: boolean;
151
+ 'aria-checked'?: boolean | 'mixed';
152
+ 'aria-selected'?: boolean;
153
+ 'aria-expanded'?: boolean;
154
+ 'aria-pressed'?: boolean | 'mixed';
155
+ 'aria-busy'?: boolean;
156
+ 'aria-modal'?: boolean;
157
+ 'aria-readonly'?: boolean;
158
+ 'aria-required'?: boolean;
159
+ /** Truthy exposes HAS_POPUP; the string names what opens. */
160
+ 'aria-haspopup'?: boolean | 'menu' | 'listbox' | 'dialog' | 'grid' | 'tree';
161
+ 'aria-orientation'?: 'horizontal' | 'vertical';
162
+ /** Present ⇒ the element exposes the AT-SPI Value interface. */
163
+ 'aria-valuenow'?: number;
164
+ 'aria-valuemin'?: number;
165
+ 'aria-valuemax'?: number;
166
+ 'aria-valuetext'?: string;
167
+ /** Outline level, for `role="heading"` and tree items. */
168
+ 'aria-level'?: number;
169
+ 'aria-posinset'?: number;
170
+ 'aria-setsize'?: number;
171
+ /** The shortcut that triggers this ("Ctrl+N"), announced with the item. */
172
+ 'aria-keyshortcuts'?: string;
173
+ /**
174
+ * Assistive technology drove the control — Orca setting a slider's value
175
+ * through the AT-SPI Value interface. Wire it to the same state setter
176
+ * as the pointer and the keyboard. Activation needs no handler here: the
177
+ * bridge dispatches a synthetic click through the normal event path.
178
+ */
179
+ onAccessibilityAction?: (ev: A11yActionEvent) => void;
180
+ }
181
+
182
+ export interface DrawnProps<T = DrawnNode>
183
+ extends
184
+ CommonProps,
185
+ InteractionProps,
186
+ A11yProps,
187
+ SelectionProps<T>,
188
+ EventHandlers<T> {
189
+ ref?: Ref<T>;
190
+ }
191
+
192
+ /**
193
+ * Selecting read-only text. `selectable` on an element makes it the surface
194
+ * a drag inside it selects across — see
195
+ * [elements.md](elements.md#selecting-text).
196
+ */
197
+ export interface SelectionProps<T = DrawnNode> {
198
+ /**
199
+ * `true` makes this element a selection surface: a drag across the text
200
+ * inside it selects, double and triple clicks take a word and a block,
201
+ * Ctrl+A and Ctrl+C work, and a release takes PRIMARY. It also makes the
202
+ * element a focus target, so the keys have somewhere to arrive —
203
+ * `tabIndex={-1}` keeps it out of the Tab cycle.
204
+ *
205
+ * `false` opts a subtree out of the surface above it, the way CSS's
206
+ * `user-select: none` does: a list's bullets, a table's chrome, a button
207
+ * inside a document.
208
+ */
209
+ selectable?: boolean;
210
+ /** The highlight behind selected text. Defaults to a tint of the theme's
211
+ * accent, which keeps the ink's own contrast intact on any palette. */
212
+ selectionColor?: Color;
213
+ /** The selection in this surface changed. */
214
+ onSelectionChange?: (ev: SelectionChangeEvent<T>) => void;
215
+ }
216
+
217
+ // --- windows ---------------------------------------------------------------
218
+
219
+ /**
220
+ * ICCCM `WM_NORMAL_HINTS` — what the window manager will let the user do.
221
+ *
222
+ * The four bounds also take `'auto'`, which asks the content: on a floor
223
+ * that is the smallest size it can be drawn at, and on a cap the size it
224
+ * wanted. Both are re-measured as the content changes.
225
+ */
226
+ export interface SizeHintProps {
227
+ minWidth?: number | 'auto';
228
+ minHeight?: number | 'auto';
229
+ maxWidth?: number | 'auto';
230
+ maxHeight?: number | 'auto';
231
+ widthInc?: number;
232
+ heightInc?: number;
233
+ baseWidth?: number;
234
+ baseHeight?: number;
235
+ minAspect?: [number, number];
236
+ maxAspect?: [number, number];
237
+ gravity?: number;
238
+ /** `false` pins min and max size to the current size. */
239
+ resizable?: boolean;
240
+ }
241
+
242
+ export type WindowType =
243
+ | 'normal'
244
+ | 'dialog'
245
+ | 'utility'
246
+ | 'toolbar'
247
+ | 'splash'
248
+ | 'menu'
249
+ | 'dropdown_menu'
250
+ | 'popup_menu'
251
+ | 'tooltip'
252
+ | 'notification'
253
+ | 'dock'
254
+ | 'desktop'
255
+ | (string & {});
256
+
257
+ export interface WindowProps
258
+ extends
259
+ CommonProps,
260
+ InteractionProps,
261
+ A11yProps,
262
+ EventHandlers<DrawnNode>,
263
+ SizeHintProps,
264
+ ScrollProps {
265
+ ref?: Ref<NtkWindow>;
266
+ /** Window title (UTF-8, via `WM_NAME` + `_NET_WM_NAME`). */
267
+ title?: string;
268
+ /**
269
+ * Created but never self-mapped: this window is waiting to be embedded
270
+ * (XEmbed / `<foreign>` on the other side), and from the reparent on,
271
+ * mapping is the embedder's decision. What a `<Frame>` pane's root window
272
+ * sets; without an embedder the window simply never appears.
273
+ */
274
+ embeddable?: boolean;
275
+ /**
276
+ * Realized and laid out, but not on screen: the X window exists at its
277
+ * size (auto sizes included — the content is really measured), the tree
278
+ * behind it is live, and the window is simply never mapped while this is
279
+ * true. Clearing it maps the window where it stands; unlike conditional
280
+ * rendering, nothing unmounts in between, so state, subscriptions and the
281
+ * X window itself survive a hide.
282
+ *
283
+ * What `Tooltip` measures an element label in before placing it, and the
284
+ * declarative form of "minimize by unmapping". A subtree hidden by React
285
+ * (`<Activity mode="hidden">`, a suspended `<Suspense>`) composes with it:
286
+ * the window is on screen only when neither says hidden.
287
+ */
288
+ hidden?: boolean;
289
+ /**
290
+ * Window geometry — window state, not yoga style: the user may resize.
291
+ *
292
+ * `'auto'`, which is also what leaving the prop out means, sizes the
293
+ * window from its content and caps it at the screen — CSS's `width: auto`
294
+ * for a box that shrinks to fit. The two axes are independent, so
295
+ * `width={600}` with no `height` gives a window whose height follows its
296
+ * content at that width.
297
+ */
298
+ width?: number | 'auto';
299
+ height?: number | 'auto';
300
+ x?: number;
301
+ y?: number;
302
+ /** Palette that `$token` style values resolve against, for this subtree. */
303
+ theme?: Record<string, string | number>;
304
+ /** ICCCM `WM_CLASS`. */
305
+ wmClass?: string | [string, string] | { instance: string; class?: string };
306
+ /** EWMH `_NET_WM_WINDOW_TYPE`, or a list of fallbacks. */
307
+ windowType?: WindowType | WindowType[];
308
+ /**
309
+ * ICCCM `WM_TRANSIENT_FOR` — the window this one belongs to. It is what
310
+ * makes a second top-level window a *dialog* rather than an unrelated
311
+ * application window: the WM stacks it above its owner, keeps it out of
312
+ * the taskbar and pager, iconifies it alongside, places it relative to the
313
+ * owner and gives it a dialog's reduced frame.
314
+ *
315
+ * Takes a ref to a `<window>`/`<popup>`, a ref to any drawn node (resolved
316
+ * to the window that owns it), a raw XID, or `'root'` for "transient for
317
+ * this client's whole window group".
318
+ *
319
+ * Resolved in the commit phase. An owner that is not realized yet — a
320
+ * sibling `<window>` mounting in the same commit, whose ref attaches only
321
+ * in the layout phase — is retried rather than dropped.
322
+ *
323
+ * **Inert on an override-redirect window**, which is every `<popup>` by
324
+ * default: the WM does not manage those, so nothing reads the property.
325
+ * Pass `overrideRedirect={false}` to make the popup a managed window
326
+ * first. ICCCM 4.1.2.6 draws exactly that distinction.
327
+ */
328
+ transientFor?: Ref<NtkWindow | DrawnNode> | number | 'root' | null;
329
+ /**
330
+ * EWMH `_NET_WM_STATE`. **Controlled**: this is what the window is asked
331
+ * to be, and {@link WindowProps.onStatesChange} is what it actually is —
332
+ * on X the window manager changes state behind the app's back, so the two
333
+ * diverge and react-x11 does not force reality back to the prop.
334
+ *
335
+ * Applied before the window is mapped, which is the only way to open
336
+ * already fullscreen rather than flashing at the normal size first.
337
+ */
338
+ states?: WindowStateName[];
339
+ /** Sugar for `states={['fullscreen']}`; they union. */
340
+ fullscreen?: boolean;
341
+ /** Sugar for `states={['above']}`; they union. */
342
+ alwaysOnTop?: boolean;
343
+ /**
344
+ * `false` asks for no titlebar or border, via `_MOTIF_WM_HINTS`. Honoured
345
+ * by Mutter, KWin, Xfwm, Openbox and i3; a WM that ignores the hint
346
+ * simply decorates the window.
347
+ */
348
+ decorations?: boolean;
349
+ /**
350
+ * Give the window a 32-bit ARGB visual, so what it does not paint stays
351
+ * transparent and a compositor shows the desktop through it. Together with
352
+ * `style={{ borderRadius }}` this is how a `<popup>` gets rounded,
353
+ * antialiased corners — no Shape extension, no 1-bit mask.
354
+ *
355
+ * The window's own `backgroundColor` may then be translucent
356
+ * (`rgba(20, 20, 24, 0.9)`), and leaving it unset makes the window empty
357
+ * except for what the tree paints.
358
+ *
359
+ * Transparency needs a 32-bit visual (XQuartz has none) **and** a running
360
+ * compositor to blend it. When either is missing, the window is filled
361
+ * edge to edge and `borderRadius` on it is ignored — a square opaque
362
+ * popup rather than a black-cornered one, which is what painting the
363
+ * corners away would give on a server with nothing compositing. Guard the
364
+ * enhanced design with a `'@supports transparency'` style block:
365
+ *
366
+ * ```jsx
367
+ * style={{
368
+ * backgroundColor: '#1c1c22',
369
+ * '@supports transparency': {
370
+ * backgroundColor: 'rgba(24, 24, 30, 0.86)',
371
+ * borderRadius: 14,
372
+ * },
373
+ * }}
374
+ * ```
375
+ *
376
+ * The visual is still taken when nothing is compositing yet, so that a
377
+ * compositor starting mid-session turns the window transparent without a
378
+ * remount. Set at creation: a visual is a `CreateWindow` field, so
379
+ * toggling this prop on a mounted window does nothing until it remounts
380
+ * (change its `key`). Needs ntk >= 6.6.0.
381
+ */
382
+ transparent?: boolean;
383
+ /**
384
+ * Read the pointer through XI2, which is what makes scrolling smooth: the
385
+ * device's own scroll valuators instead of the whole notches the server
386
+ * emulates as button 4/5 presses, so a touchpad reports the fractions of a
387
+ * notch it measured. Ignored where the server has no XI2 — the wheel
388
+ * buttons answer as they always did.
389
+ *
390
+ * - `'auto'` (the default) selects XI2 the first time the window is
391
+ * scrolled. An XI2 selection replaces the core one for the same event
392
+ * type, and an XIMotion is ~136 bytes against a core MotionNotify's 32,
393
+ * so an eager selection bills a window that never scrolls ~8 KB/s of
394
+ * pointer traffic for nothing. The opening event of the first gesture is
395
+ * a whole notch — which is all a mouse wheel ever reports anyway, and
396
+ * all an eager selection would have delivered too, since the first
397
+ * valuator event only seeds the accumulator.
398
+ * - `true` selects at creation, for an app whose whole interaction is a
399
+ * touchpad and which wants the very first flick smooth.
400
+ * - `false` refuses the selection for the window's whole life.
401
+ *
402
+ * A `<popup>` never upgrades under `'auto'`: it holds a pointer grab, a
403
+ * core grab delivers core events, and a window whose valuators are flowing
404
+ * has its emulated wheel buttons dropped, so a menu that had selected XI2
405
+ * could not be wheeled while it was grabbing. An explicit `true` still
406
+ * wins there. Needs ntk >= 7.5.0.
407
+ */
408
+ xi2?: boolean | 'auto';
409
+ /**
410
+ * Mark this window as a drag preview: the drag router never treats it as
411
+ * the window under the pointer, so a `<popup dragPreview>` can follow the
412
+ * pointer without swallowing its own drag. See `useDragSource`.
413
+ */
414
+ dragPreview?: boolean;
415
+ /**
416
+ * ConfigureNotify — the tree reflows on its own. Fires for moves and
417
+ * reparents too: check `ev.resized` / `ev.moved` before doing work of
418
+ * your own, or a window drag pays for it per pointer step.
419
+ */
420
+ onResize?: (ev: WindowResizeEvent) => void;
421
+ onExpose?: (ev: SyntheticEvent<NtkWindow>) => void;
422
+ /**
423
+ * The states the window manager now has on the window. Subscribing is
424
+ * what makes react-x11 watch `_NET_WM_STATE`, so a window with no handler
425
+ * costs nothing.
426
+ */
427
+ onStatesChange?: (states: WindowStateName[]) => void;
428
+ /**
429
+ * The window manager's close button, and anything else that asks a window
430
+ * to close (Alt+F4, a taskbar's "Close", `wmctrl -c`).
431
+ *
432
+ * Every WM-managed window speaks `WM_DELETE_WINDOW` whether or not this
433
+ * prop is passed — without it the window manager cannot ask at all and
434
+ * kills the connection instead. This prop replaces the default answer:
435
+ *
436
+ * - **without it** the app's primary window unmounts the tree and closes
437
+ * the connection, and any other window refuses (a dialog is opened by
438
+ * app state, so only app state can close it — dev warns).
439
+ * - **with it** nothing happens except this handler, which decides:
440
+ * `setOpen(false)` for a dialog, a "save your work?" prompt, or
441
+ * `root.unmount()` for a quit of your own.
442
+ *
443
+ * The primary window is the first top-level `<window>` that is not
444
+ * `transientFor` another and has no `windowType` of its own — a lone
445
+ * window always qualifies.
446
+ */
447
+ onCloseRequest?: (ev: SyntheticEvent<NtkWindow>) => void;
448
+ /**
449
+ * Every ClientMessage addressed to this window — the carrier of EWMH,
450
+ * XEmbed, the system tray, and whatever two copies of one application
451
+ * agree between themselves.
452
+ *
453
+ * `ev.messageType` is the atom's **name**, so a handler is a `switch` over
454
+ * strings rather than a comparison against ids it had to intern first.
455
+ * Messages arrive in the order the server sent them, which the chunked
456
+ * protocols depend on.
457
+ *
458
+ * ```jsx
459
+ * <window onClientMessage={(ev) => {
460
+ * if (ev.messageType !== '_NET_SYSTEM_TRAY_OPCODE') return;
461
+ * if (ev.data[1] === SYSTEM_TRAY_REQUEST_DOCK) dock(ev.data[2]);
462
+ * }} />
463
+ * ```
464
+ *
465
+ * react-x11 answers some of these itself: `preventDefault()` stops it
466
+ * doing so for XDND. Nothing has to be armed — a ClientMessage reaches its
467
+ * window whatever event mask it selected — so a window without the prop
468
+ * costs nothing.
469
+ */
470
+ onClientMessage?: (ev: ClientMessageEvent) => void;
471
+ }
472
+
473
+ /**
474
+ * `_NET_WM_STATE` names, lower-cased without the atom prefix. `'maximized'`
475
+ * is the one that is not an atom: EWMH maximizes an axis at a time, and it
476
+ * expands to the vert/horz pair.
477
+ *
478
+ * These are what a `<window>` **asks for**. What the window manager actually
479
+ * did is `useWindowState()`, whose `states` is a list of these.
480
+ */
481
+ export type WindowStateName =
482
+ | 'modal'
483
+ | 'sticky'
484
+ | 'maximized'
485
+ | 'maximized_vert'
486
+ | 'maximized_horz'
487
+ | 'shaded'
488
+ | 'skip_taskbar'
489
+ | 'skip_pager'
490
+ | 'hidden'
491
+ | 'fullscreen'
492
+ | 'above'
493
+ | 'below'
494
+ | 'demands_attention'
495
+ | 'focused'
496
+ | (string & {});
497
+
498
+ export interface PopupProps extends WindowProps {
499
+ /**
500
+ * Hold a pointer grab while the popup is up — what makes a menu
501
+ * dismissable, since without it a press on another window never reaches
502
+ * this client at all. Needs ntk >= 3.7.0.
503
+ */
504
+ grab?: boolean;
505
+ /**
506
+ * `true` (the default) keeps the window manager out entirely, which is
507
+ * what makes a menu a menu — no frame, no repositioning, no taskbar entry.
508
+ *
509
+ * `false` makes the popup an ordinary managed window: decorated, movable,
510
+ * closable through the WM, and — with {@link WindowProps.transientFor} —
511
+ * stacked above its owner and iconified with it. That is the combination
512
+ * that turns a `<popup>` into a real dialog. Turn `grab` off with it: a
513
+ * client-side pointer grab over a window the WM is trying to let the user
514
+ * drag is a fight nobody wins.
515
+ */
516
+ overrideRedirect?: boolean;
517
+ /** A press landed outside the popup: close it. */
518
+ onDismiss?: (ev: MouseEvent<DrawnNode>) => void;
519
+ /**
520
+ * Hang this popup off a node — `anchorRect`'s options plus the node to
521
+ * measure, and the popup works out its own position from them, ignoring
522
+ * `x`/`y`.
523
+ *
524
+ * What this does that computing a rect in the application cannot: a popup
525
+ * with an `'auto'` size only knows how big it is *inside* `realize()`,
526
+ * after the content is measured and before `CreateWindow` — and which side
527
+ * it flips to and how far it is pulled back from a screen edge are both
528
+ * functions of that size. So an anchored popup is born the right size in
529
+ * the right place, and keeps up afterwards with everything that can move
530
+ * either: the anchor's own layout, an ancestor scrolling, the owner window
531
+ * being dragged, its own content growing.
532
+ *
533
+ * `at` names a rect **inside** the node — a caret, a table cell — so a
534
+ * completion list follows the line being typed on rather than the editor.
535
+ * When the anchor scrolls out of view the popup unmaps until it comes
536
+ * back, since there is no position that points at something invisible;
537
+ * an app that would rather *close* it does the placement itself with
538
+ * {@link useAnchorTracking} and its `onOutOfView`.
539
+ */
540
+ anchor?: PopupAnchor;
541
+ }
542
+
543
+ /** A node to anchor to: a ref (the usual — refs attach after the commit
544
+ * that renders them) or the node itself. */
545
+ export type AnchorTarget = DrawnNode | RefObject<DrawnNode | null> | null;
546
+
547
+ export interface PopupAnchor extends Omit<AnchorOptions, 'alignTo'> {
548
+ /** The node this popup hangs off. */
549
+ to: AnchorTarget;
550
+ /** Takes the alignment axis from another node — see
551
+ * {@link AnchorOptions.alignTo}. */
552
+ alignTo?: AnchorTarget;
553
+ }
554
+
555
+ // --- drawn elements --------------------------------------------------------
556
+
557
+ /**
558
+ * The flex container — and, with `style={{overflow: 'scroll'}}`, the scroll
559
+ * container. The scrolling props below do nothing until the style says so.
560
+ */
561
+ export interface BoxProps
562
+ extends Omit<DrawnProps<DrawnNode>, 'ref'>, ScrollProps {
563
+ /**
564
+ * Either node type, because a `<box>` is both: `DrawnNode` is what most
565
+ * refs want and what every existing one already is, and `ScrollableNode`
566
+ * is the same node with `scrollTo` / `scrollX` / `contentHeight` on it.
567
+ * Reach for the second only on a box you actually scroll —
568
+ * `useRef<ScrollableNode>(null)`.
569
+ */
570
+ ref?: Ref<ScrollableNode> | Ref<DrawnNode>;
571
+ }
572
+
573
+ /** What `overflow: 'scroll'` adds, on a `<box>` or a `<window>`. */
574
+ export interface ScrollProps {
575
+ onScroll?: (ev: ScrollEvent) => void;
576
+ /** Fired from layout, so it arrives for a list nobody has scrolled yet. */
577
+ onViewport?: (ev: ViewportEvent) => void;
578
+ /** Hide the drawn scrollbars; the content still scrolls. */
579
+ scrollbar?: boolean;
580
+ scrollbarColor?: Color;
581
+ }
582
+
583
+ export interface TextProps extends DrawnProps<DrawnNode> {
584
+ /** Strings and numbers are only legal inside `<text>`. */
585
+ children?: ReactNode;
586
+ }
587
+
588
+ export interface TextInputProps extends DrawnProps<TextInputNode> {
589
+ /** Controlled mode: the display follows the prop. */
590
+ value?: string;
591
+ /** Uncontrolled mode. */
592
+ defaultValue?: string;
593
+ /**
594
+ * Field name, echoed on the event as `ev.name` and `ev.target.name`.
595
+ * Nothing in the renderer reads it — it exists so form libraries, which
596
+ * key a field by its name, have somewhere to put one.
597
+ */
598
+ name?: string;
599
+ /**
600
+ * The value changed. `ev.target.value` and `ev.value` are both the new
601
+ * text, so `(ev) => setText(ev.target.value)` and `(ev) => setText(ev.value)`
602
+ * both read naturally.
603
+ */
604
+ onChange?: (ev: ChangeEvent<TextInputNode>) => void;
605
+ /** Enter — or Ctrl+Enter in a `<textarea>`. */
606
+ onSubmit?: (ev: SubmitEvent<TextInputNode>) => void;
607
+ placeholder?: string;
608
+ placeholderColor?: Color;
609
+ /** Code-point limit. */
610
+ maxLength?: number;
611
+ selectionColor?: Color;
612
+ caretColor?: Color;
613
+ /**
614
+ * `false` turns off the built-in right-click edit menu (Undo/Redo, Cut,
615
+ * Copy, Paste, Select All). `onContextMenu` still fires, so this is how
616
+ * you replace it with your own rather than suppressing it per-event.
617
+ */
618
+ contextMenu?: false;
619
+ /**
620
+ * The text is a secret: **nothing here reaches a selection.** Ctrl+C and
621
+ * the copy half of Ctrl+X do nothing, the edit menu offers neither Cut nor
622
+ * Copy, and selecting text does not take PRIMARY — so a middle click in
623
+ * another window cannot spend it. Pasting *in*, editing, undo and the caret
624
+ * are untouched.
625
+ *
626
+ * What is on screen stops being on screen; what is on the clipboard does
627
+ * not. `PasswordInput` sets this on the input it shows while revealed.
628
+ */
629
+ sensitive?: boolean;
630
+ }
631
+
632
+ export interface TextAreaProps extends TextInputProps {
633
+ /** Preferred height in text lines (default 3). */
634
+ rows?: number;
635
+ }
636
+
637
+ /**
638
+ * Raw, straight (non-premultiplied) RGBA pixels: the shape `getImageData`
639
+ * hands back, and what ntk's `new Image()` takes. `data` is
640
+ * `width * height * 4` bytes. Treated as immutable content — hand over a
641
+ * new object when the pixels change, or the renderer cannot tell.
642
+ */
643
+ export interface RawImageSource {
644
+ width: number;
645
+ height: number;
646
+ data: Uint8Array | Uint8ClampedArray;
647
+ }
648
+
649
+ /**
650
+ * A client-side picture source the 2d context composites as-is: an ntk
651
+ * `Image` or `Surface`, or anything with a size and a `picture(app)`.
652
+ * Passing one puts identity in your hands — the object's server upload is
653
+ * cached per connection, so many `<image>`s showing one `Image` upload once.
654
+ */
655
+ export interface DirectImageSource {
656
+ readonly width: number;
657
+ readonly height: number;
658
+ picture(app: unknown): unknown;
659
+ }
660
+
661
+ /**
662
+ * A WHATWG `URL`, matched structurally because these declarations use only
663
+ * ES lib types (no DOM, no node ambients). The useful case is a file URL
664
+ * from `new URL('./icon.png', import.meta.url)`, which finds an asset
665
+ * relative to the module however the app is launched.
666
+ */
667
+ export interface FileUrl {
668
+ readonly href: string;
669
+ readonly protocol: string;
670
+ }
671
+
672
+ /**
673
+ * What `src` accepts: a file path or file URL (PNG/JPEG, decoded in JS),
674
+ * encoded PNG/JPEG bytes, raw RGBA pixels, or an ntk `Image`/`Surface`.
675
+ */
676
+ export type ImageSource =
677
+ string | FileUrl | Uint8Array | RawImageSource | DirectImageSource;
678
+
679
+ /**
680
+ * An existing server-side Picture, named by X id. The size is stated by the
681
+ * caller because asking the server for it would be a round trip — which
682
+ * this prop exists to avoid.
683
+ */
684
+ export interface ImagePictureSource {
685
+ id: number;
686
+ width: number;
687
+ height: number;
688
+ }
689
+
690
+ /**
691
+ * An existing server-side Pixmap or Window, named by X id. `depth` picks
692
+ * the picture format it is composited through: 24 (rgb, the default — what
693
+ * a window pixmap from Composite is), 32 (argb), or 8 (alpha only,
694
+ * composited as ink through its coverage).
695
+ */
696
+ export interface ImageDrawableSource {
697
+ id: number;
698
+ width: number;
699
+ height: number;
700
+ depth?: 8 | 24 | 32;
701
+ }
702
+
703
+ export interface ImageProps extends DrawnProps<DrawnNode> {
704
+ /**
705
+ * Client-side pixels: a file path or file URL (PNG/JPEG, decoded in JS),
706
+ * encoded PNG/JPEG bytes (no temp file), raw RGBA
707
+ * (`{ width, height, data }`), or an ntk `Image`/`Surface` used as-is.
708
+ *
709
+ * One source per element: `src`, `picture` and `drawable` are mutually
710
+ * exclusive, and passing two throws.
711
+ */
712
+ src?: ImageSource;
713
+ /**
714
+ * Composite an existing server-side Picture into the element — one
715
+ * `RenderComposite`, no `PutImage`, no readback. The picture stays the
716
+ * caller's; drawing it scaled sets its transform/filter for the composite
717
+ * and resets them after.
718
+ */
719
+ picture?: ImagePictureSource;
720
+ /**
721
+ * Composite an existing server-side Pixmap or Window, through a Picture
722
+ * the element creates over it (and frees when done). The drawable stays
723
+ * the caller's.
724
+ */
725
+ drawable?: ImageDrawableSource;
726
+ /**
727
+ * The source's identity, when `src` is re-derived per render: an
728
+ * unchanged key means unchanged content, so a structurally new buffer is
729
+ * neither re-decoded nor re-uploaded — and two `<image>`s with one key
730
+ * share one decoded copy. The `<canvas cacheKey>` contract: the key must
731
+ * name the content. Not consulted for an ntk `Image` (the object is its
732
+ * own identity) and meaningless with `picture`/`drawable` (throws).
733
+ */
734
+ cacheKey?: string | number;
735
+ /** The accessible name — what a screen reader says for this image. */
736
+ alt?: string;
737
+ }
738
+ // Size is `style={{ width, height }}`, like every other element: `width`
739
+ // and `height` are style names, so they are not declared here. With no
740
+ // style size, the source's own size (stated, for the server-side sources)
741
+ // is the natural one, kept to its aspect ratio.
742
+
743
+ /** What `onDraw` is told about the node it is painting. */
744
+ export interface DrawInfo {
745
+ /**
746
+ * The node's box in **device pixels** — the browser's own canvas
747
+ * contract: the backing store is the panel's grid, and `scale` says how
748
+ * many of its pixels one logical pixel is worth (docs/scale.md). A
749
+ * drawing that works in fractions of the box needs neither; one that
750
+ * draws N-logical-px strokes multiplies by `scale`.
751
+ */
752
+ width: number;
753
+ height: number;
754
+ /**
755
+ * The node's origin in the drawable being drawn into — the translation
756
+ * already applied to the context, said out loud. Everything on the
757
+ * context works in node coordinates and never needs it; the raw-pixel
758
+ * calls are the exception, because `putImageData`/`getImageData` ignore
759
+ * the transform (the HTML canvas rule) and address the drawable
760
+ * directly: `ctx.putImageData(data, info.x + x, info.y + y)`. Zero when
761
+ * the drawing goes into a surface of its own — under a `cacheKey`, or a
762
+ * `<glarea>`'s window — so offsetting by it is correct everywhere.
763
+ */
764
+ x: number;
765
+ /** See {@link DrawInfo.x}. */
766
+ y: number;
767
+ /** Device pixels per logical pixel — `useScale()`'s number. */
768
+ scale: number;
769
+ node: DrawnNode;
770
+ }
771
+
772
+ export interface CanvasProps extends DrawnProps<DrawnNode> {
773
+ /**
774
+ * Paint the node. `ctx` is ntk's canvas-like 2d context, translated to
775
+ * the node's origin and clipped to its bounds. Runs on every repaint.
776
+ *
777
+ * One exception to "you are at the node's origin": `putImageData` (and
778
+ * `getImageData`) ignore the transform and the clip — the HTML canvas
779
+ * rule — and address the drawable itself. Offset by `info.x`/`info.y`,
780
+ * or better, draw raw pixels through an `Image` source and `drawImage`,
781
+ * which honours both and caches its upload server-side. In development,
782
+ * a write that lands outside the node warns, once. See
783
+ * docs/elements.md, "`<canvas>`".
784
+ */
785
+ onDraw?: (ctx: any, info: DrawInfo) => void;
786
+ /**
787
+ * Opt this drawing into the paint cache: render it once and composite it
788
+ * on later repaints, instead of running `onDraw` again.
789
+ *
790
+ * Opt-in because only you know what `onDraw` reads — the key has to name
791
+ * every input the drawing depends on, and a key that leaves one out shows
792
+ * stale pixels. Include the things that change the picture:
793
+ *
794
+ * <canvas cacheKey={`spark:${series.id}:${w}x${h}`} onDraw={draw} />
795
+ *
796
+ * Develop with `REACT_X11_PAINT_CACHE=verify`, which turns a key that
797
+ * misses an input into a loud complaint rather than a wrong pixel. Leave
798
+ * unset for anything animated or driven by state outside the props.
799
+ */
800
+ cacheKey?: string | number;
801
+ /**
802
+ * A promise about the drawing: everything it paints is one colour, and it
803
+ * is not the drawing's to choose. `onDraw` then names no colour at all —
804
+ * `fillStyle` and `strokeStyle` arrive preset from `style.color`.
805
+ *
806
+ * One drawing then serves every state a control puts it in — `:hover`,
807
+ * `:disabled`, a theme flip — since none of those is its business.
808
+ *
809
+ * A cached `mono` entry currently bakes its colour and keys on it: the
810
+ * coverage path that would apply the colour at composite time composites
811
+ * empty under nested non-rectangular clips. Sharing across instances is
812
+ * unaffected. See docs/elements.md#mono--one-colour-and-the-colour-out-of-the-key.
813
+ *
814
+ * Works without `cacheKey` — the ink is preset either way — but the two
815
+ * together are the point.
816
+ */
817
+ mono?: boolean;
818
+ }
819
+
820
+ // --- vector drawings -------------------------------------------------------
821
+
822
+ export interface SvgProps extends DrawnProps<DrawnNode> {
823
+ source?: string;
824
+ viewBox?: string;
825
+ }
826
+
827
+ // --- GL --------------------------------------------------------------------
828
+
829
+ export type FrameLoop = 'demand' | 'always';
830
+
831
+ export interface GlAreaProps extends DrawnProps<DrawnNode> {
832
+ /** CSS colour, or `[r, g, b, a]` floats. Default black. */
833
+ clearColor?: Color | [number, number, number, number];
834
+ /** `'demand'` (default) redraws on change; `'always'` runs continuously. */
835
+ frameLoop?: FrameLoop;
836
+ /** Visual spec for ntk's `chooseGLXConfig`, e.g. `{ DEPTH_SIZE: 24 }`. */
837
+ glx?: Record<string, unknown>;
838
+ /** Runs once, with the context current: one-time state, uploads, lists. */
839
+ onCreated?: (gl: any, info: DrawInfo) => void;
840
+ /** Draw one frame. Viewport and clear happen before, SwapBuffers after. */
841
+ onDraw?: (gl: any, info: DrawInfo) => void;
842
+ /** No GL surface — no GLX, or no matching visual. */
843
+ onError?: (err: Error) => void;
844
+ /** A click inside the surface that hit no mesh. */
845
+ onPointerMissed?: (ev: MouseEvent<DrawnNode>) => void;
846
+ }
847
+
848
+ // --- embedding -------------------------------------------------------------
849
+
850
+ /** What arrived, and how. `xembed: false` is a client that set no
851
+ * `_XEMBED_INFO` and got plain reparenting — the common case. */
852
+ export interface EmbeddedInfo {
853
+ /** the client's X window id */
854
+ id: number;
855
+ /** whether the client speaks the XEmbed protocol */
856
+ xembed: boolean;
857
+ /** the protocol version in use, or 0 */
858
+ version: number;
859
+ node: DrawnNode;
860
+ }
861
+
862
+ export interface ForeignProps extends DrawnProps<DrawnNode> {
863
+ /**
864
+ * The X window to embed. Changing it hands the old client back before the
865
+ * new one is taken. Omit it to instead **adopt** whatever is put inside
866
+ * this node, which is what `xterm -into WID` and `mpv --wid=WID` need —
867
+ * `onReady` is where that id comes from.
868
+ */
869
+ windowId?: number;
870
+ /** The container window's id, offered as soon as it exists, so a program
871
+ * can be spawned into it. Fires before there is anything embedded. */
872
+ onReady?: (info: { windowId: number; node: DrawnNode }) => void;
873
+ /** A client is in. */
874
+ onEmbedded?: (info: EmbeddedInfo) => void;
875
+ /** Destroyed, or reparented away by someone else. */
876
+ onClientGone?: (info: { node: DrawnNode }) => void;
877
+ /** `XEMBED_REQUEST_FOCUS`: the client wants the focus. It is given through
878
+ * the focus manager unless a handler prevents it by focusing elsewhere. */
879
+ onRequestFocus?: (info: { node: DrawnNode }) => void;
880
+ /** The embed failed — no such window, or it went away mid-handshake.
881
+ * Without a handler the failure is a console warning. */
882
+ onError?: (err: Error) => void;
883
+ }
884
+
885
+ export interface ReactX11Elements {
886
+ window: WindowProps;
887
+ popup: PopupProps;
888
+ box: BoxProps;
889
+ text: TextProps;
890
+ textinput: TextInputProps;
891
+ textarea: TextAreaProps;
892
+ image: ImageProps;
893
+ canvas: CanvasProps;
894
+ svg: SvgProps;
895
+ glarea: GlAreaProps;
896
+ foreign: ForeignProps;
897
+ }