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/README.md CHANGED
@@ -2,10 +2,19 @@
2
2
 
3
3
  [![CI](https://github.com/sidorares/react-x11/actions/workflows/ci.yml/badge.svg)](https://github.com/sidorares/react-x11/actions/workflows/ci.yml)
4
4
 
5
- React custom rendering where side effects are communication with an [X11
6
- server](https://www.x.org/wiki/Documentation/): react-like ergonomics on top
7
- of [ntk](https://github.com/sidorares/ntk). Build small GUI programs for the
8
- X Window environment (a linux desktop, or macOS +
5
+ **[Documentation](https://sidorares.github.io/react-x11/)** ·
6
+ **[Playground](https://sidorares.github.io/react-x11/playground)** edit
7
+ react-x11 and watch it render, in your browser, against a JavaScript X server
8
+ running on the page.
9
+
10
+ A React renderer whose host environment is an [X11
11
+ server](https://www.x.org/wiki/Documentation/). React's job in a renderer is
12
+ to compute what changed; the renderer's job is to turn that into side effects
13
+ on some host — in react-dom those are DOM mutations, here they are **X11
14
+ protocol requests** written to a socket. There is no DOM, no HTML and no
15
+ browser engine underneath: this is not Electron with a different skin, and
16
+ `<div>` is not an element that exists. Build GUI programs for the X Window
17
+ environment (a linux desktop, or macOS +
9
18
  [XQuartz](https://www.xquartz.org/)) with your React / React Native
10
19
  experience — flexbox layout, components, hooks, synthetic events.
11
20
 
@@ -19,20 +28,86 @@ in-process pure-JS X server that the tests render into and read pixels back
19
28
  from; every screenshot below was rendered that way too, by driving the real
20
29
  examples through the real event pipeline).
21
30
 
22
- | `examples/dashboard.jsx` context theming, hooks | `examples/tasks.jsx` — useReducer, textinput, scrollview |
23
- | ------------------------------------------------- | -------------------------------------------------------- |
24
- | ![dashboard](docs/img/dashboard.png) | ![tasks](docs/img/tasks.png) |
31
+ ### The wire carries drawing, not pixels
32
+
33
+ react-x11 does not rasterize a frame on the client and ship the buffer
34
+ across. React reconciles the component tree, the renderer turns that diff
35
+ into **drawing operations** — rounded rectangles, composited gradients, clip
36
+ regions, runs of glyph indices — and the X server executes them. The server
37
+ owns the pixels; the client never had them.
38
+
39
+ That is what X's RENDER extension is for. Text is shaped once and its glyphs
40
+ uploaded once, so drawing a line afterwards names them by index, about a byte
41
+ per glyph; gradients, scaling, alpha compositing and clipping are single
42
+ server-side requests rather than loops over a pixel array; nothing is read
43
+ back. An update costs what the _drawing_ costs, not what the window's area
44
+ costs. Going full-screen on a 4K panel does not multiply your bandwidth,
45
+ because you were not sending pixels at 1080p either — which is why this
46
+ stays comfortable on a display forwarded over ssh. Mounting a window with
47
+ forty rows and their labels is 86 requests and 3.6 KB on the wire.
48
+
49
+ Because that is the design it is measured rather than assumed: `npm run
50
+ bench` reports requests, bytes, replies, RENDER composites and the pixel area
51
+ those composites touch, against a checked-in baseline.
52
+
53
+ ### Where this fits
54
+
55
+ X11 is the wire protocol, which means the display can be somewhere the
56
+ program is not, and the program can be somewhere a browser engine cannot go.
57
+ That is the shape of the problem this is good at:
58
+
59
+ - **the display is elsewhere** — a headless server over `ssh -X`, a
60
+ container pointed at the host, a thin client, an X terminal, a
61
+ deliberately dumb workstation. A whole window appears for under four
62
+ kilobytes, because what crosses the link is drawing rather than pixels
63
+ ([docs/remote.md](docs/remote.md));
64
+ - **the machine cannot afford a browser engine** — a kiosk, an appliance, an
65
+ instrument panel, an ARM board with 512 MB, a locked-down box where
66
+ installing must not compile anything and root is not on offer;
67
+ - **you want the UI in the same process as the rest of your program** —
68
+ `fs`, `serialport`, `pg` and your components in one heap, one event loop,
69
+ no IPC bridge and no second bundler;
70
+ - **you want GUI tests that run in CI with no display server** — `npm test`
71
+ here renders real pixels through the real protocol into node-x11's
72
+ in-process X server, on a machine with no `$DISPLAY`, on macOS. That
73
+ harness is published as `react-x11/test`
74
+ ([docs/testing.md](docs/testing.md)).
75
+
76
+ And the shape it is not good at, so you can stop here rather than in week
77
+ three:
78
+
79
+ - **three platforms.** X11 only. macOS means XQuartz — a separate install, a
80
+ non-native look and no menu bar integration. Windows is out; if you need
81
+ Windows, use Electron or Tauri.
82
+ - **native Wayland.** There is no Wayland backend and there is not going to
83
+ be one; that would be a different renderer, not a flag. Ordinary
84
+ application windows work fine on a Wayland desktop through Xwayland, which
85
+ is not going away — but the desktop-shell half of X11 (panel struts,
86
+ global key grabs, screen capture, and the window-manager example below)
87
+ needs a real X session.
88
+ - **reusing web components.** There is no DOM. Your MUI, your Tailwind and
89
+ your `recharts` do not come with you; the state, data-fetching, validation
90
+ and math libraries mostly do. [docs/ecosystem.md](docs/ecosystem.md) says
91
+ which is which, and what the failure looks like when it is the wrong one.
92
+ - **rendering HTML.** There is no HTML element and no webview. Rich
93
+ documents are markdown, through `<Markdown>` in
94
+ [`@react-x11/components`](https://github.com/sidorares/react-x11-components).
95
+ - **text entry outside Latin.** See [Known issues](#known-issues).
96
+
97
+ | `examples/dashboard.jsx` — context theming, hooks | `examples/tasks.jsx` — useReducer, textinput, scrolling |
98
+ | ------------------------------------------------- | ------------------------------------------------------- |
99
+ | ![dashboard](docs/img/dashboard.png) | ![tasks](docs/img/tasks.png) |
25
100
 
26
101
  | `examples/form.jsx` — textinput + Select | the open Select menu (a real `<popup>` window) |
27
102
  | ---------------------------------------- | ---------------------------------------------- |
28
103
  | ![form](docs/img/form.png) | ![select menu](docs/img/select-menu.png) |
29
104
 
30
- `examples/three.jsx` — a react-three-fiber-shaped scene over **indirect
31
- GLX**: `<mesh>`, geometries, materials, lights and a texture, drawn by
32
- sending the GL protocol over the X connection. No native bindings, no GPU
105
+ `examples/viewer3d.jsx` — a model viewer over **indirect GLX**: the GL
106
+ protocol sent over the X connection, geometry compiled into a display list,
107
+ a frame costing two matrices and one `CallList`. No native bindings, no GPU
33
108
  driver bindings — the same "JavaScript all the way down" story as the rest.
34
-
35
- ![3D over indirect GLX](docs/img/three.png)
109
+ (GL renders where `GetImage` cannot read it, so there is no screenshot of it
110
+ here; see [docs/glx.md](docs/glx.md).)
36
111
 
37
112
  ## Quick start
38
113
 
@@ -47,17 +122,32 @@ import { createRoot } from 'react-x11';
47
122
  function Counter() {
48
123
  const [n, setN] = useState(0);
49
124
  return (
50
- <window width={240} height={120} title="counter" backgroundColor="#f4f4f4">
51
- <box flexGrow={1} alignItems="center" justifyContent="center" gap={10}>
52
- <text fontSize={24}>{String(n)}</text>
125
+ <window
126
+ width={240}
127
+ height={120}
128
+ title="counter"
129
+ style={{ backgroundColor: '#f4f4f4' }}
130
+ >
131
+ <box
132
+ style={{
133
+ flexGrow: 1,
134
+ alignItems: 'center',
135
+ justifyContent: 'center',
136
+ gap: 10,
137
+ }}
138
+ >
139
+ <text style={{ fontSize: 24 }}>{String(n)}</text>
53
140
  <box
54
- backgroundColor="#2980b9"
55
- borderRadius={6}
56
- padding={8}
57
- cursor="pointer"
141
+ style={{
142
+ backgroundColor: '#2980b9',
143
+ borderRadius: 6,
144
+ padding: 8,
145
+ cursor: 'pointer',
146
+ ':hover': { backgroundColor: '#1f6693' },
147
+ }}
58
148
  onClick={() => setN(n + 1)}
59
149
  >
60
- <text color="white">+1</text>
150
+ <text style={{ color: 'white' }}>+1</text>
61
151
  </box>
62
152
  </box>
63
153
  </window>
@@ -80,53 +170,80 @@ Everything else is laid out by yoga and drawn client-side into the window's
80
170
  double-buffered 2d context — see [NEXT_STEPS.md](NEXT_STEPS.md) for the architecture rationale
81
171
  and [docs/](docs/README.md) for the full API reference.
82
172
 
83
- | element | what it is |
84
- | -------------- | -------------------------------------------------------------------------------------------------------------------- |
85
- | `<window>` | a real X11 window; the flex, paint and event root |
86
- | `<popup>` | an override-redirect window at screen coordinates — menus, tooltips, dropdowns |
87
- | `<box>` | flex container: layout props → yoga, plus backgrounds, borders (solid/dashed, radius), overflow clipping, zIndex |
88
- | `<scrollview>` | clipped, wheel-scrollable viewport with a drawn scrollbar |
89
- | `<text>` | shaped, wrapped text (bidi, ligatures, font fallback); nested `<text>` elements are style spans |
90
- | `<textinput>` | single-line editor: caret/selection via ntk's TextLayout caret API, clipboard (CLIPBOARD + X11 PRIMARY), word select |
91
- | `<image>` | PNG/JPEG from `src`, natural-size aware |
92
- | `<canvas>` | escape hatch: `onDraw={(ctx, {width, height}) => …}` with ntk's canvas-like 2d context (XRender-backed) |
93
- | `<markdown>` | ntk MarkdownView: headings, tables, highlighted fences, math, mermaid; `onLink` |
94
- | `<html>` | ntk HtmlView: CSS cascade, block/flex layout, images; `onLink` |
95
- | `<svg>` | static SVG through ntk SvgView, sized like `<image>` |
96
- | `<tex>` | a KaTeX formula (ntk `layoutTex`), intrinsically sized |
97
- | `<glarea>` | an OpenGL surface over indirect GLX; the 3D scene below lives inside it |
173
+ | element | what it is |
174
+ | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
175
+ | `<window>` | a real X11 window; the flex, paint and event root |
176
+ | `<popup>` | an override-redirect window at screen coordinates — menus, tooltips, dropdowns |
177
+ | `<box>` | flex container: layout props → yoga, plus backgrounds, borders (solid/dashed, radius), overflow clipping, zIndex. `overflow: 'scroll'` makes it a wheel-scrollable viewport with a drawn scrollbar |
178
+ | `<text>` | shaped, wrapped text (bidi, ligatures, font fallback); nested `<text>` elements are style spans |
179
+ | `<textinput>` | single-line editor: caret/selection via ntk's TextLayout caret API, clipboard (CLIPBOARD + X11 PRIMARY), word select, undo/redo, right-click menu |
180
+ | `<image>` | PNG/JPEG from `src`, natural-size aware |
181
+ | `<canvas>` | escape hatch: `onDraw={(ctx, {width, height}) => …}` with ntk's canvas-like 2d context (XRender-backed) |
182
+ | `<svg>` | static SVG through ntk SvgView, sized like `<image>` |
183
+ | `<glarea>` | an OpenGL surface over indirect GLX; the 3D scene below lives inside it |
98
184
 
99
185
  Widget **components** (plain React on top of the primitives, themable via
100
186
  `ThemeProvider`): `Button`, `Checkbox`, `Radio`/`RadioGroup`, `Switch`,
101
- `Slider`, `ProgressBar`, `Select`, `Tooltip`, `MenuBar`/`ContextMenu` and
102
- `Dialog` — a modal built on `<popup trapFocus>`, which traps Tab and
103
- restores focus when it closes. See [docs/components.md](docs/components.md).
187
+ `Slider`, `ProgressBar`, `Select`, `Tooltip`, `MenuBar`/`ContextMenu`,
188
+ `Calendar`/`DatePicker` — one date or a range, with days blockable and a
189
+ seam for drawing into the cells — `PasswordInput`, whose mask is a scribble
190
+ that moves on every keystroke rather than a countable row of bullets —
191
+ `Dialog` — a modal built on
192
+ `<popup trapFocus>`, which traps Tab and restores focus when it closes —
193
+ plus the two containers an application window is built from: `Tabs`, `Tree`
194
+ and `SplitPane`. See
195
+ [docs/components.md](docs/components.md).
196
+
197
+ **The menu bar goes where the desktop keeps menus.** On Plasma, on a panel
198
+ running `vala-panel-appmenu`, or under one of the GNOME extensions that add a
199
+ menu bar, `<MenuBar menus={…}/>` hands its menu to the panel over
200
+ `com.canonical.dbusmenu` and stops drawing one; everywhere else it draws the
201
+ bar itself. No configuration, and the same line of JSX either way — the item
202
+ descriptors are dbusmenu's own vocabulary, so the array that draws the menu is
203
+ the array that serialises. `npm run globalmenu:host` is a panel in a terminal,
204
+ for seeing it work on a desktop that has none. See
205
+ [docs/globalmenu.md](docs/globalmenu.md).
206
+
207
+ **`myapp://` links open the app that is already running.**
208
+ `registerApplication({ appId, schemes })` before `createRoot()` owns the app's
209
+ name on the bus, exports `org.freedesktop.Application`, and — on the half of
210
+ all desktops where `xdg-open` ignores D-Bus activation and spawns a second copy
211
+ with the URI in `argv` — forwards it to the first copy and tells that copy to
212
+ exit. `useAppOpen()` receives the link, buffered and replayed if it arrived
213
+ before the tree mounted, and `activateWindow()` brings the window forward with
214
+ the launch's own timestamp, without which a window manager declines and the
215
+ taskbar entry just blinks. The OAuth redirect this is for has an easier answer
216
+ that needs none of it, and the page says so first. See
217
+ [docs/uri-schemes.md](docs/uri-schemes.md).
218
+
219
+ **Drag and drop** works with the rest of the desktop: `<box dropAccept={['files']}
220
+ onDrop={…}>` takes a file dragged out of Nautilus, and `<box draggable dragData={…}>`
221
+ can be dropped into a file manager or an editor. Drags that stay inside the
222
+ app never touch the wire and hand their payload over by reference, so a
223
+ reorderable list and a cross-application file drop are the same two props.
224
+ See [docs/drag-and-drop.md](docs/drag-and-drop.md).
104
225
 
105
226
  ### 3D
106
227
 
107
- Inside a `<glarea>` (or the `Canvas3D` component) the children are scene
108
- elements with react-three-fiber's names:
228
+ `<glarea>` is a GL surface in the layout, drawn over **indirect GLX**: the GL
229
+ protocol encoded into the X connection, with no native bindings and no GPU
230
+ driver bindings — the same "JavaScript all the way down" story as the rest.
231
+ Geometry belongs in a server-side display list, so a frame costs matrices and
232
+ one `CallList` whatever the triangle count.
109
233
 
110
234
  ```jsx
111
- <Canvas3D flexGrow={1} camera={{ position: [0, 2, 6], fov: 45 }}>
112
- <ambientLight intensity={0.35} />
113
- <pointLight position={[5, 6, 6]} />
114
- <mesh rotation={[0.5, 0.4, 0]} onClick={() => pick()}>
115
- <boxGeometry args={[1.4, 1.4, 1.4]} />
116
- <meshPhongMaterial color="#2980b9" shininess={60} />
117
- </mesh>
118
- </Canvas3D>
235
+ <glarea
236
+ style={{ flexGrow: 1 }}
237
+ frameLoop="always"
238
+ onCreated={(gl) => gl.Enable(gl.DEPTH_TEST)}
239
+ onDraw={(gl, { width, height }) => drawFrame(gl, width, height)}
240
+ />
119
241
  ```
120
242
 
121
- `<mesh>`, `<group>`, box/plane/sphere/cylinder/torus geometries,
122
- `<bufferGeometry>`, basic/Lambert/Phong materials with textures, four light
123
- types, and pointer events resolved by client-side raycasting. Each geometry
124
- is compiled into a **server-side display list** once, so a frame costs
125
- matrices plus one `CallList` per mesh whatever the triangle count. What the
126
- protocol cannot do — shaders, instancing, post-processing, shadows — throws
127
- with the reason rather than half-working. See
128
- [docs/elements.md](docs/elements.md#3d-scene-mesh-group-geometries-materials)
129
- and [docs/glx-plan.md](docs/glx-plan.md).
243
+ A scene graph over it — `<mesh>`, geometries, materials, lights, and shaders
244
+ or post-processing on the direct backend — is
245
+ [`@react-x11/components/three`](https://github.com/sidorares/react-x11-components).
246
+ See [docs/gl.md](docs/gl.md) and [docs/glx.md](docs/glx.md).
130
247
 
131
248
  Events are synthetic with capture/bubble phases and hit testing over the
132
249
  drawn tree: `onClick` (with DOM-style `detail` click counting),
@@ -134,43 +251,76 @@ drawn tree: `onClick` (with DOM-style `detail` click counting),
134
251
  `focusable` + `onFocus`/`onBlur` + Tab traversal, and a `cursor` prop.
135
252
  User handlers run before element default actions and can
136
253
  `ev.preventDefault()` — stopping a `<textinput>` from editing or a
137
- `<scrollview>` from scrolling, like the DOM.
254
+ a scroll container from scrolling, like the DOM.
138
255
 
139
256
  ## Examples
140
257
 
141
- All need an X server (`DISPLAY` set; XQuartz on macOS, Xvfb for automation):
258
+ All need an X server but "an X server" is a broader thing than it sounds:
259
+ your Linux desktop, XQuartz on macOS, `Xvfb` for automation, `Xephyr` for a
260
+ disposable screen, a VNC server, or your own display reached over `ssh -X`
261
+ from wherever the program actually runs — see
262
+ [docs/remote.md](docs/remote.md), which is the case this architecture is
263
+ categorically better at.
264
+ [`examples/README.md`](examples/README.md) describes each one and how to
265
+ explore them:
142
266
 
143
267
  ```sh
144
268
  npm run examples:simple # hello world (JSX via tsx)
269
+ npm run examples:app # the showcase: Tabs + SplitPane hosting the rest
270
+ npm run examples:theming # three themes x light/dark, and a size query
145
271
  npm run examples:simple-nojsx # the same, plain node — no build step
146
272
  npm run examples:xeyes # canvas drawing + hooks
147
273
  npm run examples:dashboard # context theming, custom hooks, components
148
- npm run examples:tasks # useReducer, textinput, scrollview
274
+ npm run examples:tasks # useReducer, textinput, scrolling
149
275
  npm run examples:menu # right-click context menu via <popup>
276
+ npm run examples:transparent # rounded translucent <popup transparent>
150
277
  npm run examples:form # <textinput> + Select dropdowns
151
- npm run examples:gl # raw GL in a <glarea> (display-list cube)
152
- npm run examples:three # <Canvas3D> scene: meshes, lights, textures
278
+ npm run examples:datepicker # Calendar/DatePicker: ranges, blocked days, events
279
+ npm run examples:password # PasswordInput: the scribble mask, and a custom one
280
+ npm run examples:viewer3d # raw GL in a <glarea>: a model viewer over indirect GLX
281
+ npm run examples:wm # a reparenting window manager (see below)
153
282
  ```
154
283
 
155
284
  The two GL examples additionally need a server with **indirect GLX**
156
285
  enabled (`+iglx` / `AllowIndirectGLX` — off by default on many).
157
286
 
287
+ ### Window manager
288
+
289
+ `examples:wm` is a real reparenting window manager: it takes over the root
290
+ window, puts every application's window inside a frame it draws, and moves,
291
+ resizes, focuses and closes them. The frames are ordinary react-x11
292
+ `<window>`s — the titlebar, the buttons and the eight resize handles are
293
+ components with `onMouseDown` handlers, and the application is a foreign X
294
+ window reparented inside.
295
+
296
+ Only one window manager may own a display, so run it against a nested
297
+ server rather than the one managing your desktop:
298
+
299
+ ```sh
300
+ Xephyr :10 -screen 1200x800 &
301
+ DISPLAY=:10 npm run examples:wm
302
+ DISPLAY=:10 xterm & # give it something to manage
303
+ ```
304
+
305
+ To have it manage your real session instead — including replacing
306
+ `quartz-wm` on macOS, which XQuartz has a hook for — see
307
+ [`examples/README.md`](examples/README.md#the-window-manager).
308
+
158
309
  ### Hot reloading
159
310
 
160
311
  ```sh
161
312
  npm run examples:tasks:hot # then edit examples/tasks.jsx while it runs
162
313
  ```
163
314
 
164
- Runs the tasks example under
165
- [hot-module-replacement](https://github.com/sidorares/hot-module-replacement)'s
166
- ESM hooks (Node ≥ 22.15) with React **Fast Refresh**: saving
167
- `examples/tasks.jsx` updates the edited components in place. The X11
168
- connection, the mounted window, and component state — the task list, even
169
- half-typed text in the input — survive the reload (a component whose hook
170
- signature changed remounts alone). `examples/hmr-register.mjs` wires up the
171
- transform half (babel JSX + react-refresh, chained under the HMR hooks),
172
- `examples/hmr-refresh.js` the runtime half, and `examples/tasks-hot.jsx` is
173
- the hot entry — the pattern works for any example split the same way.
315
+ Runs the tasks example under the supported hot-reload entry point,
316
+ `react-x11/refresh` (`node --import react-x11/refresh/register`, Node ≥
317
+ 22.15), with React **Fast Refresh**: saving `examples/tasks.jsx` updates
318
+ the edited components in place. The X11 connection, the mounted window,
319
+ and component state — the task list, even half-typed text in the input —
320
+ survive the reload (a component whose hook signature changed remounts
321
+ alone). Any app adopts it the same way — no accept handlers, no loader
322
+ files to copy; see
323
+ [docs/ecosystem/dev-tooling.md](docs/ecosystem/dev-tooling.md#react-refresh).
174
324
 
175
325
  ## React DevTools
176
326
 
@@ -180,10 +330,14 @@ REACT_X11_DEVTOOLS=1 npm run examples:dashboard # 2. run any example with the
180
330
  ```
181
331
 
182
332
  The component tree, props and hook state show up live in the DevTools
183
- window; selecting a component inspects it, and hovering an element in the
184
- tree tints its rect in the X11 window (highlight-on-hover).
185
- `REACT_X11_DEVTOOLS_HOST` / `REACT_X11_DEVTOOLS_PORT` override the default
186
- `localhost:8097`. See [docs/devtools.md](docs/devtools.md).
333
+ window, and editing any of them re-renders the app. Hovering an element in
334
+ the tree tints its rect in the X11 window; the toolbar's crosshair picks an
335
+ element by clicking it in the app; "highlight updates when components
336
+ render" outlines the rects that just re-rendered; the style editor edits a
337
+ selected element's style live; and the Profiler can restart the app to
338
+ record its mount. `REACT_X11_DEVTOOLS_HOST` / `REACT_X11_DEVTOOLS_PORT`
339
+ override the default `localhost:8097`. See
340
+ [docs/devtools.md](docs/devtools.md).
187
341
 
188
342
  Two more debugging aids:
189
343
 
@@ -205,23 +359,118 @@ and enables the feature in one go; `REACT_X11_CLICK_TO_COMPONENT=1` does
205
359
  the same with the default editor
206
360
  (`cursor`). See [docs/click-to-component.md](docs/click-to-component.md).
207
361
 
362
+ ## TypeScript
363
+
364
+ Types ship with the package — no `@types/react-x11` to install. Point JSX at
365
+ react-x11 and the X11 elements type-check:
366
+
367
+ ```jsonc
368
+ // tsconfig.json
369
+ {
370
+ "compilerOptions": {
371
+ "jsx": "react-jsx",
372
+ "jsxImportSource": "react-x11",
373
+ },
374
+ }
375
+ ```
376
+
377
+ ```tsx
378
+ import { createRoot, createStyles, Button } from 'react-x11';
379
+ import type { MouseEvent } from 'react-x11';
380
+
381
+ const s = createStyles({
382
+ root: { flexGrow: 1, padding: 12, gap: 8 },
383
+ // ^ flexDirection: 'sideways' would not compile,
384
+ // and neither would a layout property inside ':hover'
385
+ });
386
+
387
+ <window title="hi" width={320} height={200} style={s.root}>
388
+ <box onClick={(ev: MouseEvent) => console.log(ev.detail)}>
389
+ <text style={{ fontSize: 18 }}>hello</text>
390
+ </box>
391
+ <Button primary onPress={() => {}}>
392
+ ok
393
+ </Button>
394
+ </window>;
395
+ ```
396
+
397
+ `jsxImportSource` is what makes `<div>` an error rather than something that
398
+ compiles and then throws at runtime: react-x11 owns the JSX namespace
399
+ instead of adding to React's, which it could not do anyway — `text`,
400
+ `image`, `canvas`, `html` and `svg` are all DOM element names too, with
401
+ incompatible props.
402
+
403
+ Style props, element props, event objects, ref types and every widget are
404
+ typed; `style` accepts the same nested arrays with falsy entries the runtime
405
+ does. See [docs/typescript.md](docs/typescript.md).
406
+
208
407
  ## Developing
209
408
 
210
409
  ```sh
211
410
  npm test # hermetic: mock smoke tests + in-process X server pixels
411
+ npm run typecheck # tsc over the declarations and the type tests
212
412
  npm run lint # ESLint
213
413
  npm run format # Prettier
214
414
  npm run screenshots # regenerate docs/img/*.png headlessly (no X server)
415
+ npm run docs:dev # the documentation site (npm install in website/ first)
215
416
  ```
216
417
 
217
- `docs/img/three.png` is the exception: the headless path has no GL, so the
218
- 3D shot is captured by hand from `npm run examples:three` on a real server
418
+ 3D is the exception: the headless path has no GL, so a 3D shot has to be
419
+ captured by hand from a real server
219
420
  with indirect GLX.
220
421
 
221
422
  See [AGENTS.md](AGENTS.md) for architecture notes and contributor/agent
222
423
  guidance, [docs/](docs/README.md) for API documentation, and
223
424
  [NEXT_STEPS.md](NEXT_STEPS.md) for the roadmap.
224
425
 
225
- # See also
226
-
227
- https://github.com/chentsulin/awesome-react-renderer
426
+ The [documentation site](https://sidorares.github.io/react-x11/) lives in
427
+ [`website/`](website/) and renders `docs/` rather than repeating it — edit
428
+ `docs/` and the site follows. Its playground bundles this repo's `src/` for
429
+ the browser, so `npm run docs:test` fails when a demo stops matching the API.
430
+
431
+ ## Known issues
432
+
433
+ Two that are worth knowing before you hit them, both predating the current
434
+ release and both tracked:
435
+
436
+ - **`sans-serif` can resolve to a CJK font on macOS**
437
+ ([#86](https://github.com/sidorares/react-x11/issues/86)). Font families
438
+ are resolved by shelling out to `fc-match`, which follows `PATH`;
439
+ Homebrew's fontconfig ships no macOS system-font aliases and answers
440
+ Hiragino Sans. Latin looks fine, Cyrillic comes out on full-width
441
+ advances. Put `/opt/X11/bin` first on `PATH` until this is fixed.
442
+ - **AltGr levels do not work, and there is no input method.** `@` on a
443
+ German layout, `€`, `ł`, `ã` on US-International: the core keyboard map
444
+ cannot say whether the third and fourth keysyms on a key are a second
445
+ layout or a third and fourth level, so they are read as a layout and left
446
+ alone. Dead keys, Compose and layout switching all work
447
+ ([docs/events.md](docs/events.md)); what is absent is an ibus/fcitx/XIM
448
+ backend, so CJK is not partially working — it is structurally absent.
449
+
450
+ ## Security
451
+
452
+ X11 has no isolation between clients: any program on a display can read any
453
+ other program's window contents, grab the keyboard, and synthesize or record
454
+ input. That is the 1987 design, not a gap in this library, and it cuts both
455
+ ways — do not run untrusted programs on a display you use, and do not treat
456
+ a react-x11 window as a confidential surface. Your `$XAUTHORITY` cookie is a
457
+ bearer token; treat it like a password.
458
+
459
+ `ssh -X` runs your app as an untrusted client and restricts most of the
460
+ above; `ssh -Y` turns the restrictions off. **Prefer `-X` — react-x11 should
461
+ work under it, and if it does not, that is a bug worth filing.**
462
+
463
+ The full threat model, including what react-x11 does and does not defend
464
+ against, is [docs/security.md](docs/security.md). To report something,
465
+ [SECURITY.md](SECURITY.md).
466
+
467
+ ## See also
468
+
469
+ - [awesome-react-renderer](https://github.com/chentsulin/awesome-react-renderer)
470
+ — the catalogue of React renderers, which is how most people find things
471
+ like this.
472
+ - [ntk](https://github.com/sidorares/ntk) — the toolkit underneath: windows,
473
+ the XRender-backed 2d context, the text stack, the frame clock.
474
+ - [node-x11](https://github.com/sidorares/node-x11) — the X11 protocol in
475
+ JavaScript, including the in-process X server the tests and the playground
476
+ run against.