react-x11 2.52.0 → 2.54.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 (115) hide show
  1. package/README.md +10 -10
  2. package/docs/README.md +160 -314
  3. package/docs/accessibility.md +176 -229
  4. package/docs/appearance.md +77 -176
  5. package/docs/click-to-component.md +18 -51
  6. package/docs/clipboard.md +40 -59
  7. package/docs/components.md +518 -1036
  8. package/docs/dbus.md +61 -133
  9. package/docs/debugging.md +152 -245
  10. package/docs/desktop-calendar.md +51 -88
  11. package/docs/desktop.md +222 -347
  12. package/docs/devtools.md +50 -167
  13. package/docs/drag-and-drop.md +126 -204
  14. package/docs/ecosystem/animation.md +91 -125
  15. package/docs/ecosystem/data-fetching.md +38 -53
  16. package/docs/ecosystem/dev-tooling.md +159 -202
  17. package/docs/ecosystem/forms.md +61 -97
  18. package/docs/ecosystem/headless.md +64 -89
  19. package/docs/ecosystem/i18n.md +18 -21
  20. package/docs/ecosystem/icons.md +93 -126
  21. package/docs/ecosystem/layout.md +42 -52
  22. package/docs/ecosystem/routing.md +18 -29
  23. package/docs/ecosystem/state.md +71 -88
  24. package/docs/ecosystem/testing.md +131 -157
  25. package/docs/ecosystem/theming.md +119 -165
  26. package/docs/ecosystem.md +145 -179
  27. package/docs/elements.md +1096 -1774
  28. package/docs/embedding.md +83 -132
  29. package/docs/events.md +412 -695
  30. package/docs/extending.md +784 -1326
  31. package/docs/eyedropper.md +63 -122
  32. package/docs/filedialog.md +71 -107
  33. package/docs/frame.md +195 -339
  34. package/docs/gl.md +65 -62
  35. package/docs/globalmenu.md +63 -146
  36. package/docs/macos.md +531 -3234
  37. package/docs/notifications.md +33 -47
  38. package/docs/packaging.md +166 -309
  39. package/docs/permissions.md +9 -14
  40. package/docs/react-features.md +81 -106
  41. package/docs/remote.md +32 -76
  42. package/docs/scale.md +72 -186
  43. package/docs/styling.md +329 -547
  44. package/docs/system.md +200 -280
  45. package/docs/testing.md +63 -83
  46. package/docs/typescript.md +6 -48
  47. package/docs/uri-schemes.md +67 -107
  48. package/docs/wayland-backend.md +200 -459
  49. package/docs/windows-integrations.md +75 -131
  50. package/docs/windows.md +343 -1367
  51. package/package.json +3 -2
  52. package/src/Reconciler.js +2 -2
  53. package/src/a11y.js +1 -1
  54. package/src/backend/context2d.js +3 -3
  55. package/src/bootstrap.js +1 -1
  56. package/src/cocoa/app.js +6 -6
  57. package/src/cocoa/bezels.js +1 -1
  58. package/src/cocoa/fonts.js +1 -1
  59. package/src/cocoa/globalmenu.js +1 -1
  60. package/src/cocoa/keymap.js +1 -1
  61. package/src/cocoa/panewindow.js +1 -1
  62. package/src/cocoa/player.js +1 -1
  63. package/src/cocoa/presenter.js +2 -2
  64. package/src/cocoa/promotion.js +1 -1
  65. package/src/cocoa/relaunch.js +1 -1
  66. package/src/cocoa/threaded.js +1 -1
  67. package/src/cocoa/video.js +1 -1
  68. package/src/cocoa/window.js +4 -4
  69. package/src/components/Menu.js +7 -2
  70. package/src/components/Switch.js +1 -1
  71. package/src/components/native.js +1 -1
  72. package/src/dnd.js +2 -2
  73. package/src/glnodes.js +1 -1
  74. package/src/grid.js +1 -1
  75. package/src/imagedecode-worker.js +32 -0
  76. package/src/imagedecode.js +140 -2
  77. package/src/layouts.js +1 -1
  78. package/src/nodes/animation.js +1 -1
  79. package/src/nodes/invalidate.js +1 -1
  80. package/src/nodes/layouthost.js +1 -1
  81. package/src/nodes/video.js +19 -8
  82. package/src/nodes/window/droptarget.js +1 -1
  83. package/src/ntk.d.ts +12 -0
  84. package/src/pacing.js +1 -1
  85. package/src/refresh/index.js +2 -1
  86. package/src/types/video.d.ts +5 -2
  87. package/src/videobackend.js +28 -0
  88. package/src/videoframes.js +29 -16
  89. package/src/wayland/app.js +27 -0
  90. package/src/wayland/video.js +376 -0
  91. package/src/win32/a11y.js +1 -1
  92. package/src/win32/app.js +2 -2
  93. package/src/win32/fonts.js +1 -1
  94. package/src/win32/glarea.js +4 -4
  95. package/src/win32/overlay.js +1 -1
  96. package/src/win32/panewindow.js +3 -3
  97. package/src/win32/shell.js +1 -1
  98. package/src/win32/window.js +2 -2
  99. package/src/windowid.js +1 -1
  100. package/src/xvideo.js +189 -0
  101. package/docs/architecture/accessibility.md +0 -470
  102. package/docs/architecture/animation.md +0 -707
  103. package/docs/architecture/container-queries.md +0 -525
  104. package/docs/architecture/custom-layout.md +0 -334
  105. package/docs/architecture/drag-and-drop.md +0 -941
  106. package/docs/architecture/element-layer-contents.md +0 -622
  107. package/docs/architecture/frame-pacing.md +0 -255
  108. package/docs/architecture/grid-layout.md +0 -358
  109. package/docs/architecture/protocol-efficiency.md +0 -357
  110. package/docs/architecture/video.md +0 -1149
  111. package/docs/architecture/windowed-regions.md +0 -854
  112. package/docs/glx.md +0 -137
  113. package/docs/wayland.md +0 -545
  114. package/docs/windows-embedding.md +0 -271
  115. package/docs/windows-gl.md +0 -221
@@ -1,28 +1,27 @@
1
1
  # Icons
2
2
 
3
3
  react-x11 has a real `<svg>` host, so icons get further than most DOM
4
- libraries — which is exactly why the failures here are unusual. Two renderer
5
- facts shape every entry on this page:
6
-
7
- 1. **ntk's `SvgView` ignores presentation attributes on the root `<svg>`
8
- element.** Inheritance starts from its own defaults, so an icon that puts
9
- `fill="none" stroke="currentColor" stroke-width="2"` on the root renders
10
- as solid black blobs — wrong glyphs, not just the wrong colour. Moving
11
- those attributes onto an inner `<g>` fixes it, because `<g>` does
12
- inherit.
4
+ libraries. Two renderer facts shape every entry on this page:
5
+
6
+ 1. **ntk's `SvgView`, which rasterizes `<svg>`, ignores presentation
7
+ attributes on the root element.** Inheritance starts from its own
8
+ defaults, so an icon with `fill="none" stroke="currentColor"
9
+ stroke-width="2"` on the root renders as solid black blobs — wrong
10
+ glyphs, not just the wrong colour. Moving those attributes onto an inner
11
+ `<g>`, which does inherit, fixes it.
13
12
  2. **Flat style props on `<svg>` throw.** `<svg width={24}>` is
14
13
  `react-x11: <svg width=…> is a style property — pass it in style`. Size
15
14
  with `style={{ width: 24 }}`; the `viewBox` keeps the aspect ratio.
16
15
 
17
16
  Fact 2 is why `lucide-react`, `@tabler/icons-react` and
18
- `@phosphor-icons/react` cannot work at all — see the
19
- [negative-results register](../ecosystem.md#render-time). `@heroicons/react`
20
- is the exception, and it is documented below.
17
+ `@phosphor-icons/react` cannot work
18
+ ([register](../ecosystem.md#render-time)); `@heroicons/react` is the
19
+ exception.
21
20
 
22
21
  ## The `iconSource` wrapper {#iconsource}
23
22
 
24
23
  Four of the packages below ship whole SVG _strings_ with paint attributes on
25
- the root. They all need the same twelve lines, so write it once:
24
+ the root. They all need the same twelve lines:
26
25
 
27
26
  ```js
28
27
  // icon-source.js — re-root an icon's paint attributes onto a <g> so they
@@ -41,14 +40,11 @@ export function iconSource(svgText, color = '#000') {
41
40
  }
42
41
  ```
43
42
 
44
- `SvgView` maps `currentColor` to the inherited `color`, which defaults to
45
- `#000` — so the `color` attribute on the `<g>` is what makes recolouring
46
- work. Note that `svgText.replaceAll('currentColor', color)` on its own is
47
- _not_ enough: it fixes the colour but not the dropped `fill="none"`, so
48
- stroke icons still paint as filled blobs.
49
-
50
- Cache the wrapped strings per (name, style, colour); parsing per mount is
51
- avoidable work.
43
+ `currentColor` resolves to the inherited `color`, which defaults to `#000`,
44
+ so the `color` attribute on the `<g>` is what makes recolouring work.
45
+ `svgText.replaceAll('currentColor', color)` alone is _not_ enough: it fixes
46
+ the colour but not the dropped `fill="none"`, so stroke icons still paint as
47
+ filled blobs. Cache the wrapped strings per (name, style, colour).
52
48
 
53
49
  ## Iconify {#iconify}
54
50
 
@@ -56,14 +52,11 @@ avoidable work.
56
52
 
57
53
  Iconify publishes every major icon set as JSON data — `@iconify-json/tabler`,
58
54
  `@iconify-json/mdi`, `@iconify-json/ph`, `@iconify-json/lucide`,
59
- `@iconify-json/heroicons` — plus `@iconify/utils`, a framework-free toolkit
60
- that looks an icon up and builds SVG markup from it.
61
-
62
- This is the highest-leverage route: one pipeline, 200,000+ icons across every
63
- set, no React wrapper to fight. It is also the only string-SVG route that
64
- needs **no re-rooting adapter**, because `iconToSVG` returns the icon body
65
- separately from the root attributes, and the paint attributes live inside the
66
- body rather than on the root.
55
+ `@iconify-json/heroicons` — plus `@iconify/utils`, which looks an icon up and
56
+ builds SVG markup from it. The highest-leverage route: one pipeline, 200,000+
57
+ icons, and the only string-SVG route that needs **no re-rooting adapter**,
58
+ because `iconToSVG` returns the body separately from the root attributes and
59
+ the paint attributes live inside the body.
67
60
 
68
61
  ```jsx
69
62
  import { getIconData, iconToSVG, iconToHTML, replaceIDs } from '@iconify/utils';
@@ -86,10 +79,9 @@ function Icon({ name, size = 24, color }) {
86
79
  // <Icon name="settings" size={16} color="#0891b2" />
87
80
  ```
88
81
 
89
- - `replaceIDs` matters for icons containing gradients or clip paths;
90
- duplicate `id`s across two mounted icons would collide inside one
91
- rasterized document. Harmless across separate `<svg>` nodes, but free to
92
- keep.
82
+ - `replaceIDs` matters for icons with gradients or clip paths, whose `id`s
83
+ would collide inside one rasterized document. Harmless across separate
84
+ `<svg>` nodes, but free to keep.
93
85
  - Pick per-set packages (`@iconify-json/tabler` is about 5,000 icons in one
94
86
  JSON) rather than the monolithic `@iconify/json`, which is all 200,000+ and
95
87
  roughly 100 MB.
@@ -100,11 +92,10 @@ function Icon({ name, size = 24, color }) {
100
92
 
101
93
  **Out of the box — size it with `style`.** @heroicons/react@2.2.0.
102
94
 
103
- The exception among the `*-react` icon packages. Heroicons' React components
104
- size themselves with a Tailwind class name rather than flat `width`/`height`
105
- attributes, so react-x11's `<svg>` host accepts them and the path paints. Do
106
- not lump this one in with lucide-react, `@tabler/icons-react` or
107
- `@phosphor-icons/react`, all three of which throw.
95
+ The exception among the `*-react` icon packages: its components size
96
+ themselves with a Tailwind class name rather than flat `width`/`height`
97
+ attributes, so the `<svg>` host accepts them. lucide-react,
98
+ `@tabler/icons-react` and `@phosphor-icons/react` all throw.
108
99
 
109
100
  ```jsx
110
101
  import { BeakerIcon } from '@heroicons/react/24/solid';
@@ -114,25 +105,20 @@ import { BeakerIcon } from '@heroicons/react/24/solid';
114
105
  </box>;
115
106
  ```
116
107
 
117
- The `className` it sets is ignored by the renderer, so you must pass
118
- `style={{ width, height }}` yourself — an icon with no size will lay out at
119
- its natural size or collapse, depending on the surrounding flex box.
120
-
121
- There is no recolour channel through the component, so if you need a themed
122
- icon use the asset package below, or `@iconify-json/heroicons`.
108
+ The renderer ignores the `className`, so pass `style={{ width, height }}`
109
+ yourself — an icon with no size lays out at its natural size or collapses,
110
+ depending on the surrounding flex box. There is no recolour channel; for a
111
+ themed icon use the asset package below, or `@iconify-json/heroicons`.
123
112
 
124
113
  ## `lucide` {#lucide}
125
114
 
126
115
  **Adapter, ~10 lines.** lucide@1.28.0.
127
116
 
128
- Lucide's core package. Alongside the DOM-oriented `createIcons`, every icon
129
- is exported as plain data: an array of `[tag, attrs]` pairs. No DOM, no
130
- React, tree-shakeable per icon.
131
-
132
- This is the best-fitting icon package for react-x11, because the data shape
133
- skips string parsing entirely — each pair becomes a JSX child of `<svg>`,
134
- which react-x11 serializes through `SvgChildNode` (camelCase to kebab
135
- attributes) into ntk's `SvgView`.
117
+ Lucide's core package exports every icon as plain data — an array of
118
+ `[tag, attrs]` pairs — alongside the DOM-oriented `createIcons`.
119
+ Tree-shakeable per icon, and the best fit for react-x11: no string parsing,
120
+ each pair becomes a JSX child of `<svg>` (camelCase props serialize as
121
+ kebab-case attributes).
136
122
 
137
123
  ```jsx
138
124
  import { createElement } from 'react';
@@ -160,8 +146,7 @@ function Icon({ icon, size = 24, color = '#000', strokeWidth = 2 }) {
160
146
  // <Icon icon={theme === 'dark' ? Sun : House} color={accent} />
161
147
  ```
162
148
 
163
- Prop changes on SVG children invalidate and repaint correctly — swapping
164
- `icon` or `color` in state re-renders the icon.
149
+ Swapping `icon` or `color` in state repaints the icon.
165
150
 
166
151
  - Omitting the `<g>` and putting `stroke`/`fill` on the `<svg>` silently
167
152
  renders black filled blobs.
@@ -175,10 +160,9 @@ Prop changes on SVG children invalidate and repaint correctly — swapping
175
160
 
176
161
  **Adapter — the [`iconSource` wrapper](#iconsource).** lucide-static@1.28.0.
177
162
 
178
- The framework-free build of Lucide: every icon is a named export that is just
179
- an SVG markup string. 24×24 viewBox, stroke-based, `stroke="currentColor"`
180
- with round caps and joins — all on the root element, which is why it needs
181
- the wrapper.
163
+ Every Lucide icon as a named export holding an SVG markup string: 24×24,
164
+ stroke-based, `stroke="currentColor"` with round caps and joins — all on the
165
+ root element, hence the wrapper.
182
166
 
183
167
  ```jsx
184
168
  import * as lucide from 'lucide-static';
@@ -192,25 +176,23 @@ const Icon = ({ name, size = 24, color = '#000' }) => (
192
176
  // <Icon name="Sun" size={16} color="#eab308" />
193
177
  ```
194
178
 
195
- Use the `lucide` data package above where you can; reach for this one when
196
- you want the markup string itself (asset pipelines, caching a pre-built
197
- sheet).
179
+ Prefer the `lucide` data package; use this one when you want the markup
180
+ string itself (asset pipelines, a pre-built sheet).
198
181
 
199
182
  ## `react-icons` {#react-icons}
200
183
 
201
184
  **Adapter, ~15 lines.** react-icons@5.7.0.
202
185
 
203
- The standard React icon aggregator — Font Awesome, Material, Lucide,
204
- Bootstrap, Simple Icons and about fifty more, tens of thousands of icons
205
- behind one API. Out of the box it throws, because `IconBase` always sets
206
- `height`/`width` (default `"1em"`) as flat props on `<svg>`.
186
+ The React icon aggregator — Font Awesome, Material, Lucide, Bootstrap,
187
+ Simple Icons and about fifty more behind one API. Unadapted it throws,
188
+ because `IconBase` always sets `height`/`width` (default `"1em"`) as flat
189
+ props on `<svg>`.
207
190
 
208
- The adapter sidesteps `IconBase` entirely. An icon component called with
209
- empty props returns the `IconBase` element, whose props carry exactly what is
210
- needed: `attr` (the set's root attributes — `viewBox`, and for stroke-based
211
- sets the paint attributes) and `children` (the icon's paths as ready-made
212
- React elements). Re-rooting those attributes onto an inner `<g>` handles
213
- fact 1 above.
191
+ The adapter sidesteps `IconBase`. An icon component called with empty props
192
+ returns the `IconBase` element, whose props carry `attr` (the set's root
193
+ attributes — `viewBox`, and for stroke sets the paint attributes) and
194
+ `children` (the paths as React elements). Re-rooting `attr` onto an inner
195
+ `<g>` handles fact 1.
214
196
 
215
197
  ```jsx
216
198
  import { FaHeart } from 'react-icons/fa';
@@ -239,25 +221,23 @@ function RIcon({ icon: I, size = 24, color = '#000' }) {
239
221
  // <RIcon icon={LuHouse} color="#2563eb" /> stroke-based
240
222
  ```
241
223
 
242
- - `I({})` inside render leans on each icon being a plain function component
243
- wrapping `GenIcon`. That has been react-icons' shape since v4, but it is an
244
- internal one — if it changes, prefer `lucide` or the Iconify pipeline,
245
- which have no such seam.
224
+ - `I({})` relies on each icon being a plain function component wrapping
225
+ `GenIcon` — react-icons' shape since v4, but an internal one. `lucide` and
226
+ the Iconify pipeline have no such seam.
246
227
  - `IconContext` (global size/colour config) is bypassed; pass props instead.
247
- - The `title` prop emits an SVG `<title>` child, and there is no tooltip or
248
- assistive-technology surface behind a rasterized SVG. Omit it.
249
- - react-icons is heavy on disk — every set is bundled. If you only need one
250
- style, `lucide` or `@iconify-json/<set>` is a much smaller install.
228
+ - The `title` prop emits an SVG `<title>` child, which reaches no tooltip or
229
+ assistive technology from a rasterized SVG. Omit it.
230
+ - react-icons bundles every set and is heavy on disk; for one style,
231
+ `lucide` or `@iconify-json/<set>` is a much smaller install.
251
232
 
252
233
  ## `@tabler/icons` {#tabler-icons}
253
234
 
254
235
  **Adapter — the [`iconSource` wrapper](#iconsource).** @tabler/icons@3.46.0.
255
236
 
256
- The framework-free Tabler package: 5,000+ MIT icons as SVG files under
257
- `icons/outline/` (stroke-based, 24×24) and `icons/filled/`, with an `exports`
258
- map that resolves file paths directly. It also ships
259
- `tabler-nodes-outline.json`/`-filled.json` — per-icon node arrays, the same
260
- idea as `lucide`'s data exports.
237
+ 5,000+ MIT icons as SVG files under `icons/outline/` (stroke-based, 24×24)
238
+ and `icons/filled/`, with an `exports` map that resolves file paths, plus
239
+ `tabler-nodes-outline.json`/`-filled.json` — per-icon node arrays like
240
+ `lucide`'s data exports.
261
241
 
262
242
  ```jsx
263
243
  import fs from 'node:fs';
@@ -280,30 +260,25 @@ const Icon = ({ name, style, size = 24, color = '#000' }) => (
280
260
  // <Icon name="settings" style="filled" size={16} />
281
261
  ```
282
262
 
283
- Honestly, though: **prefer `@iconify-json/tabler` + `@iconify/utils`**, which
284
- serves identical art with no adapter at all. This package earns its place
285
- when you want the files themselves (design pipelines, asset preprocessing) or
286
- the JSON node arrays.
263
+ **Prefer `@iconify-json/tabler` + `@iconify/utils`**: identical art, no
264
+ adapter. Use this package when you want the files themselves or the JSON
265
+ node arrays.
287
266
 
288
- - Outline icons start with a `<path stroke="none" d="M0 0h24v24H0z"
289
- fill="none"/>` bounds rect; it inherits nothing and paints nothing.
290
- Harmless.
291
- - `@tabler/icons-react` throws. See the
292
- [register](../ecosystem.md#render-time).
267
+ - Outline icons start with a
268
+ `<path stroke="none" d="M0 0h24v24H0z" fill="none"/>` bounds rect, which
269
+ paints nothing. Harmless.
270
+ - `@tabler/icons-react` throws ([register](../ecosystem.md#render-time)).
293
271
 
294
272
  ## `@phosphor-icons/core` {#phosphor-icons}
295
273
 
296
274
  **Adapter — the [`iconSource` wrapper](#iconsource).**
297
275
  @phosphor-icons/core@2.1.1.
298
276
 
299
- Phosphor's framework-free package: raw SVG files under
300
- `assets/<weight>/<name>.svg` for six weights (`thin`, `light`, `regular`,
301
- `bold`, `fill`, `duotone`), 256×256 viewBox, fill-based.
302
-
303
- Because Phosphor is fill-based, the raw file _does_ render legible black
304
- glyphs — the default fill happens to match — so the wrapper is only
305
- mandatory for colour. The `duotone` weight relies on per-path opacity, which
306
- survives fine either way.
277
+ Raw SVG files under `assets/<weight>/<name>.svg` for six weights (`thin`,
278
+ `light`, `regular`, `bold`, `fill`, `duotone`), 256×256 viewBox,
279
+ fill-based. Because the default fill matches, the raw file renders legible
280
+ black glyphs; the wrapper is only needed for colour. `duotone`'s per-path
281
+ opacity survives either way.
307
282
 
308
283
  ```jsx
309
284
  import fs from 'node:fs';
@@ -331,19 +306,17 @@ const Icon = ({ name, weight, size = 24, color = '#000' }) => (
331
306
  ```
332
307
 
333
308
  - Non-`regular` weights use suffixed filenames
334
- (`assets/bold/house-bold.svg`); the helper above accounts for it.
309
+ (`assets/bold/house-bold.svg`); the helper above handles it.
335
310
  - `@iconify-json/ph` serves the same set with no adapter and no filesystem
336
311
  access. Prefer it unless you want the files.
337
- - `@phosphor-icons/react` throws. See the
338
- [register](../ecosystem.md#render-time).
312
+ - `@phosphor-icons/react` throws ([register](../ecosystem.md#render-time)).
339
313
 
340
314
  ## `heroicons` (the asset package) {#heroicons-assets}
341
315
 
342
316
  **Adapter — the [`iconSource` wrapper](#iconsource).** heroicons@2.2.0.
343
317
 
344
- The Tailwind-house set as plain SVG files: `24/outline/*.svg` (stroke-based),
345
- `24/solid/`, `20/solid/` and `16/solid/`. No JS at all — the `heroicons` npm
346
- package is just the files.
318
+ The Tailwind set as plain SVG files, no JS: `24/outline/*.svg`
319
+ (stroke-based), `24/solid/`, `20/solid/` and `16/solid/`.
347
320
 
348
321
  ```jsx
349
322
  import fs from 'node:fs';
@@ -366,22 +339,18 @@ const Icon = ({ name, style, size = 24, color = '#000' }) => (
366
339
  // <Icon name="home" style="24/solid" color="#0ea5e9" />
367
340
  ```
368
341
 
369
- Use this rather than `@heroicons/react` when you need to recolour — the React
370
- components render fine but give you no colour channel. `heroicons` has no JS
371
- index, so enumerate the directory if you need a picker.
372
-
373
- **Prefer `@iconify-json/heroicons`** through the [Iconify](#iconify)
374
- pipeline: it is the same art, with no adapter and no filesystem access. Reach
375
- for the asset package when you specifically want the files.
342
+ Use this rather than `@heroicons/react` when you need to recolour. There is
343
+ no JS index, so enumerate the directory for a picker. **Prefer
344
+ `@iconify-json/heroicons`** through the [Iconify](#iconify) pipeline — same
345
+ art, no adapter, no filesystem access — unless you want the files.
376
346
 
377
347
  ## `feather-icons` {#feather-icons}
378
348
 
379
349
  **Adapter, ~5 lines.** feather-icons@4.29.2.
380
350
 
381
- The classic 24×24 stroke set, 287 icons. Feather exposes `.contents` — the
382
- inner SVG markup, without a root element — which makes the adapter a template
383
- literal instead of a regex: build the root and the `<g>` yourself and fact 1
384
- never applies.
351
+ The classic 24×24 stroke set, 287 icons. `.contents` is the inner SVG
352
+ markup without a root element, so the adapter is a template literal: build
353
+ the root and the `<g>` yourself and fact 1 never applies.
385
354
 
386
355
  ```jsx
387
356
  import feather from 'feather-icons';
@@ -399,12 +368,10 @@ const Icon = ({ name, size = 24, color, strokeWidth }) => (
399
368
  // <Icon name="alert-circle" color="#f59e0b" />
400
369
  ```
401
370
 
402
- - **Prefer Lucide.** Feather is in maintenance mode upstream — no new icons
403
- in years. Lucide is its actively-developed fork with five times the
404
- coverage and the same visual language. Reach for Feather only if you are
405
- matching an existing design.
406
- - Raw `feather.icons.x.toSvg()` into `<svg source>` renders wrong, because
407
- `toSvg()` puts every paint attribute on the root. `toSvg({ color })` sets
408
- `color` on the root too, where it is equally ignored. Always go through
409
- `.contents`.
371
+ - **Prefer Lucide**, Feather's actively developed fork with five times the
372
+ coverage and the same look; Feather has had no new icons in years. Use it
373
+ only to match an existing design.
374
+ - `feather.icons.x.toSvg()` into `<svg source>` renders wrong: `toSvg()`
375
+ puts every paint attribute, and `toSvg({ color })`'s `color`, on the root,
376
+ where they are ignored. Always go through `.contents`.
410
377
  - `feather.replace()` (the DOM `<i data-feather>` swapper) is browser-only.
@@ -1,31 +1,25 @@
1
1
  # Layout and positioning
2
2
 
3
3
  Layout itself is yoga's job — see [styling](../styling.md). The two packages
4
- here cover the parts yoga does not: placing a popup relative to a trigger
5
- across screen edges, and rendering a long list without building a node per
6
- row.
7
-
8
- Both are the _core_ half of a library whose React wrapper is DOM-bound. That
9
- is the recurring shape in this section of the ecosystem: the algorithm is
10
- portable, the wrapper is not, and the core is published separately precisely
11
- so that non-DOM hosts can use it.
4
+ here cover what yoga does not: placing a popup relative to a trigger across
5
+ screen edges, and rendering a long list without a node per row. Both are the
6
+ _core_ half of a library whose React wrapper is DOM-bound; the core is
7
+ published separately so non-DOM hosts can use it.
12
8
 
13
9
  ## `@floating-ui/core` {#floating-ui}
14
10
 
15
11
  **Adapter, ~35 lines (a platform object).** @floating-ui/core@1.8.0.
16
12
 
17
- The positioning engine underneath Floating UI, published without any DOM
18
- code. You hand `computePosition` a `platform` object — `getElementRects`,
19
- `getDimensions`, `getClippingRect`, `isRTL` — and get back the full
20
- middleware pipeline (`offset`, `flip`, `shift`, `size`, `arrow`,
21
- `autoPlacement`, `hide`) with the placement vocabulary every web developer
22
- already knows.
13
+ Floating UI's positioning engine without DOM code. Hand `computePosition` a
14
+ `platform` object — `getElementRects`, `getDimensions`, `getClippingRect`,
15
+ `isRTL` — and get the full middleware pipeline (`offset`, `flip`, `shift`,
16
+ `size`, `arrow`, `autoPlacement`, `hide`) and its placement vocabulary.
23
17
 
24
- The seam is exactly where `anchorRect()` already works: a drawn node's
25
- `node.abs` rect plus the owning window's screen origin gives the reference
26
- rect in screen coordinates, `screenOf(node)` gives the clipping rect, and the
27
- popup-to-be is just `{width, height}`. The result's `{x, y, placement}` drops
28
- straight into `<popup x y>`.
18
+ As in the built-in `anchorRect()`, a drawn node's `node.abs` rect plus the
19
+ window's screen origin is the reference rect in screen coordinates,
20
+ `screenOf(node)` gives the clipping rect, and the popup-to-be is just
21
+ `{width, height}`. The result's `{x, y, placement}` drops straight into
22
+ `<popup x y>`.
29
23
 
30
24
  ```jsx
31
25
  import { computePosition, offset, flip, shift } from '@floating-ui/core';
@@ -78,40 +72,37 @@ const { x, y, placement } = await computePosition(
78
72
  // -> <popup x={x} y={y}> ; placement tells you which side won
79
73
  ```
80
74
 
81
- What this buys over the built-in `anchorRect` is middleware composition
82
- (`shift` with padding, `size` to clamp long menus, `arrow`) and alignment
83
- fallback — `flip` with `crossAxis: 'alignment'` switches `bottom-start` to
84
- `bottom-end` when that is what fits. `anchorRect`'s `placement`, `align` and
85
- `offset` options map 1:1 onto `placement` plus the `offset()` middleware.
75
+ Over `anchorRect` this adds middleware composition (`shift` with padding,
76
+ `size` to clamp long menus, `arrow`) and alignment fallback — `flip` with
77
+ `crossAxis: 'alignment'` switches `bottom-start` to `bottom-end` when that
78
+ fits. `anchorRect`'s `placement`, `align` and `offset` options map 1:1 onto
79
+ `placement` plus the `offset()` middleware.
86
80
 
87
- - `computePosition` is async — `await` it, even though this platform is fully
81
+ - `computePosition` is async — `await` it, though this platform is
88
82
  synchronous.
89
83
  - `getClippingRect` above is screen 0's full rect, the same single-monitor
90
- simplification `screenOf` has today. On a dual-head X display, popups clamp
91
- to the bounding box of both monitors.
92
- - **`@floating-ui/dom` and `@floating-ui/react` are not usable** — they are
93
- the DOM platform and DOM interactions respectively. Only `core` and its
94
- `@floating-ui/utils` dependency belong in an X11 app.
95
- - The safe-polygon hover logic in `anchor.js` has no equivalent in `core`;
96
- that lives in `@floating-ui/react`'s `useHover`, which is DOM-bound. Keep
97
- the built-in one.
84
+ simplification as `screenOf`. On a dual-head X display, popups clamp to
85
+ the bounding box of both monitors.
86
+ - **`@floating-ui/dom` and `@floating-ui/react` are not usable** (DOM
87
+ platform and DOM interactions). Only `core` and its `@floating-ui/utils`
88
+ dependency belong in an X11 app.
89
+ - Safe-polygon hover lives in `@floating-ui/react`'s DOM-bound `useHover`,
90
+ not in `core`; keep react-x11's built-in one.
98
91
 
99
92
  ## `@tanstack/virtual-core` {#tanstack-virtual}
100
93
 
101
94
  **Adapter, ~40 lines.** @tanstack/virtual-core@3.17.7.
102
95
 
103
- The virtualization engine behind `useVirtualizer`: given a count, an
104
- `estimateSize`, the viewport rect and the scroll offset, it computes which
105
- items are visible (`getVirtualItems()`), the total scrollable size
106
- (`getTotalSize()`), and offsets for `scrollToIndex`. All DOM touchpoints are
107
- constructor options, which is what makes this work.
96
+ The engine behind `useVirtualizer`: from a count, an `estimateSize`, the
97
+ viewport rect and the scroll offset it computes the visible items
98
+ (`getVirtualItems()`), the total size (`getTotalSize()`), and offsets for
99
+ `scrollToIndex`. Every DOM touchpoint is a constructor option.
108
100
 
109
101
  **Do not import `@tanstack/react-virtual`.** Its first line is
110
- `import { flushSync } from "react-dom"`. Without react-dom installed it fails
111
- to resolve; _with_ react-dom installed it silently flushes into the wrong
112
- reconciler — see the
113
- [silent-failures table](../ecosystem.md#silent-failures). Everything the
114
- React wrapper adds is the hook below.
102
+ `import { flushSync } from "react-dom"`: without react-dom it fails to
103
+ resolve; _with_ it, it silently flushes into the wrong reconciler
104
+ ([silent failures](../ecosystem.md#silent-failures)). The hook below is
105
+ everything the React wrapper adds.
115
106
 
116
107
  Three seams wire to a scrolling `<box>`:
117
108
 
@@ -121,9 +112,9 @@ Three seams wire to a scrolling `<box>`:
121
112
  - `observeElementOffset` → `<box onScroll>` (`scrollY`);
122
113
  - `scrollToFn` → `ref.scrollTo(offset)`.
123
114
 
124
- One non-obvious requirement: `getScrollElement()` must return something with
125
- `scrollHeight`/`clientHeight`, because virtual-core duck-types Element vs
126
- Window with `'scrollHeight' in el`. A getter shim over the scroll box node's
115
+ `getScrollElement()` must return something with
116
+ `scrollHeight`/`clientHeight` (virtual-core duck-types Element vs Window
117
+ with `'scrollHeight' in el`); a getter shim over the node's
127
118
  `contentHeight`/`abs.height` satisfies it.
128
119
 
129
120
  ```jsx
@@ -207,10 +198,9 @@ function BigList() {
207
198
  - **First paint shows zero rows.** Layout runs on the frame clock after the
208
199
  mount commit, `onViewport` reports a tick later, and the list fills in on
209
200
  the next frame. Fine interactively; tests must tick a few times.
210
- - `estimateSize` is the only measurement. Dynamic per-item measurement
211
- (`measureElement`) is built on `ResizeObserver` and
212
- `getBoundingClientRect`. For variable-height rows, pass exact sizes:
213
- `estimateSize: (i) => heights[i]`.
201
+ - `estimateSize` is the only measurement; `measureElement` is built on
202
+ `ResizeObserver` and `getBoundingClientRect`. For variable-height rows
203
+ pass exact sizes: `estimateSize: (i) => heights[i]`.
214
204
  - `_didMount`/`_willUpdate` are underscored but are the documented pattern
215
- for custom framework adapters — the official React, Vue and Svelte adapters
216
- call exactly these.
205
+ for custom adapters — the official React, Vue and Svelte adapters call
206
+ exactly these.
@@ -1,25 +1,17 @@
1
1
  # Routing
2
2
 
3
- There is no URL bar and no `history` object, so every router on this page
4
- runs on an in-memory location. That is not a workaround — both libraries ship
5
- a first-party memory backend, because both already support React Native and
6
- testing.
7
-
8
- What consistently does not survive is the _link_ element. `<Link>` renders an
9
- `<a>` host in both routers, and there is no `<a>` here. Navigate imperatively
10
- from a `<box onClick>` instead; that is two lines and it is all you lose.
11
-
12
- Also worth saying up front: a desktop app usually wants stack or tab
13
- navigation semantics rather than URL paths. These libraries give you the
14
- matching machinery, not a back stack — build that on top if you need it.
3
+ There is no URL bar and no `history` object, so both routers here run on
4
+ their first-party in-memory location. What does not survive is `<Link>`,
5
+ which renders an `<a>` host in both; navigate from a `<box onClick>`
6
+ instead. These libraries give you matching, not a back stack — build stack
7
+ or tab navigation on top if you need it.
15
8
 
16
9
  ## wouter {#wouter}
17
10
 
18
11
  **Out of the box — with `wouter/memory-location`.** wouter@3.10.0.
19
12
 
20
13
  A 2 kB router: `<Router>`, `<Route>`, `<Switch>`, `useLocation`, pattern
21
- params. Location sources are pluggable hooks and the package ships a
22
- non-browser one, so there is no adapter to write.
14
+ params, and a pluggable location hook with a non-browser one shipped.
23
15
 
24
16
  ```jsx
25
17
  import React from 'react';
@@ -66,13 +58,12 @@ const App = () => (
66
58
  - **Always mount the memory hook at the top.** A `<Route>` outside a
67
59
  `<Router hook={...}>` falls back to `useBrowserLocation` and crashes
68
60
  immediately with `ReferenceError: location is not defined`.
69
- - **`<Link>` does not work.** It renders an `<a>` host element.
70
- `<Link asChild>` avoids the `<a>` but still does not navigate: its click
71
- guard checks `event.button !== 0`, and react-x11's synthetic
72
- `MouseEvent.button` is the X button number, which is `1` for left.
73
- - `useLocation()[1]` gives you the same `navigate` from inside components;
74
- the module-level one from `memoryLocation` is just more convenient with a
75
- single router.
61
+ - **`<Link>` does not work**: it renders an `<a>`. `<Link asChild>` avoids
62
+ the `<a>` but does not navigate, because its click guard checks
63
+ `event.button !== 0` and react-x11's `MouseEvent.button` is the X button
64
+ number, `1` for left.
65
+ - `useLocation()[1]` is the same `navigate` from inside components; the
66
+ module-level one is handier with a single router.
76
67
  - `useHashLocation` and `useBrowserLocation` are browser-only. Everything
77
68
  else — matching, params, nesting, `useRoute` — is pure.
78
69
  - `memoryLocation({ record: true })` keeps a `history` array, which is the
@@ -85,8 +76,7 @@ react-router 7.18.2.
85
76
 
86
77
  The router core — `useRoutes`, `useNavigate`, `useParams`, `useLocation`,
87
78
  loaders, actions — is DOM-free. Import from `react-router`, not
88
- `react-router-dom`; the DOM package re-exports the core plus the browser
89
- entry points, and the browser entry points are exactly what fails.
79
+ `react-router-dom`, whose browser entry points are what fails.
90
80
 
91
81
  ```jsx
92
82
  import React from 'react';
@@ -131,16 +121,15 @@ function About() {
131
121
  );
132
122
  ```
133
123
 
134
- - **`BrowserRouter` and `HashRouter` both fail** — they call
135
- `createBrowserHistory`, which needs `window.history`. The error is
124
+ - **`BrowserRouter` and `HashRouter` fail** — `createBrowserHistory` needs
125
+ `window.history`. The error is
136
126
  `react-x11: uncaught error ReferenceError: document is not defined` at
137
- `getUrlBasedHistory`, and note that `render()` does not throw: react-x11
138
- logs it and the window count stays 0.
127
+ `getUrlBasedHistory`; `render()` does not throw, react-x11 logs it and the
128
+ window count stays 0.
139
129
  - **`<Link>`, `<NavLink>` and `<Form>` all render `<a>`/`<form>` hosts** and
140
130
  cannot work. Use `useNavigate()` on a `<box onClick>`.
141
131
  - A `<Link>` with a bare string child hits the raw-text error before the
142
132
  element error: `react-x11: raw text "go" must be wrapped in a <text>
143
133
  element`. Both are in the
144
134
  [register](../ecosystem.md#render-time).
145
- - If you do not need loaders and actions, wouter above is the smaller
146
- dependency by a wide margin.
135
+ - Without loaders and actions, wouter is the much smaller dependency.