react-x11 2.52.0 → 2.53.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 (112) 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 +1091 -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/layouts.js +1 -1
  76. package/src/nodes/animation.js +1 -1
  77. package/src/nodes/invalidate.js +1 -1
  78. package/src/nodes/layouthost.js +1 -1
  79. package/src/nodes/video.js +19 -8
  80. package/src/nodes/window/droptarget.js +1 -1
  81. package/src/pacing.js +1 -1
  82. package/src/refresh/index.js +2 -1
  83. package/src/types/video.d.ts +5 -2
  84. package/src/videobackend.js +28 -0
  85. package/src/videoframes.js +29 -16
  86. package/src/wayland/app.js +27 -0
  87. package/src/wayland/video.js +376 -0
  88. package/src/win32/a11y.js +1 -1
  89. package/src/win32/app.js +2 -2
  90. package/src/win32/fonts.js +1 -1
  91. package/src/win32/glarea.js +4 -4
  92. package/src/win32/overlay.js +1 -1
  93. package/src/win32/panewindow.js +3 -3
  94. package/src/win32/shell.js +1 -1
  95. package/src/win32/window.js +2 -2
  96. package/src/windowid.js +1 -1
  97. package/src/xvideo.js +189 -0
  98. package/docs/architecture/accessibility.md +0 -470
  99. package/docs/architecture/animation.md +0 -707
  100. package/docs/architecture/container-queries.md +0 -525
  101. package/docs/architecture/custom-layout.md +0 -334
  102. package/docs/architecture/drag-and-drop.md +0 -941
  103. package/docs/architecture/element-layer-contents.md +0 -622
  104. package/docs/architecture/frame-pacing.md +0 -255
  105. package/docs/architecture/grid-layout.md +0 -358
  106. package/docs/architecture/protocol-efficiency.md +0 -357
  107. package/docs/architecture/video.md +0 -1149
  108. package/docs/architecture/windowed-regions.md +0 -854
  109. package/docs/glx.md +0 -137
  110. package/docs/wayland.md +0 -545
  111. package/docs/windows-embedding.md +0 -271
  112. package/docs/windows-gl.md +0 -221
@@ -1,334 +0,0 @@
1
- # Custom layout: layouts and positions that run inside the pass
2
-
3
- _Design record, 2026-09-10. Written against `master` at 91e1d1c (react-x11
4
- 2.11, `src/nodes.js` @ 13.5k lines) and the branch that adds the two seams.
5
- The yoga numbers are from a standalone spike (§6) run in-process on one
6
- machine (Apple M1 Pro, node 26); file references are to that branch and will
7
- drift._
8
-
9
- _[styling.md](../styling.md#custom-layouts) is the reference for the style
10
- properties and [extending.md](../extending.md#a-layout-algorithm-of-your-own)
11
- for the two registration contracts; this is why they are the shape they are._
12
-
13
- ---
14
-
15
- ## 0. TL;DR
16
-
17
- **Two seams, both run by the core inside the frame's layout pass:**
18
-
19
- - **A layout** arranges a box's children: `registerLayout(name, { layout })`,
20
- then `style={{ layout: name }}` on any box. The algorithm is asked how big
21
- the box's content is for the room on offer, and — once the pass has given
22
- the box its size — where each child goes. `grid` (which `display: 'grid'`
23
- names too), `masonry` and `equal-row` are built in.
24
- - **A position** moves one node after the pass, by an offset, resizing
25
- nothing: `registerPosition(name, { place })`, then
26
- `style={{ position: name }}`. It is CSS's positioning scheme opened up, and
27
- `position: 'sticky'` is now the built-in one, written against the same
28
- context a registered one is handed.
29
-
30
- **Why in the core rather than in an effect** (§1): an arrangement that
31
- depends on a layout output has to be computed inside the pass that produces
32
- the output. `onLayout` hears about a pass after it painted, `useLayoutEffect`
33
- runs before the pass it would need to read, and the passes a scroll, a
34
- resize, a live resize or a layout-property transition run never reach React
35
- at all.
36
-
37
- **How** (§3): a box with a `layout` is a measured leaf in yoga's tree, and
38
- each of its children a yoga tree of its own. The measure function is the
39
- algorithm's sizing answer; after the pass, the algorithm's final call places
40
- the children, each laid out at the size its rect names, and `absolutize`
41
- reads the slot. The content floors are measured tree by tree inside those
42
- children (§4). A layout that throws is reported and the box is laid out as
43
- flexbox in the same frame.
44
-
45
- ## 1. Why the core
46
-
47
- What one change costs when an arrangement is done in React, with
48
- `onLayout` → `setState`:
49
-
50
- 1. **Commit.** The nodes change and a frame is scheduled. `useLayoutEffect`
51
- runs here, before yoga has run for this commit: `abs` holds the previous
52
- frame's rect — zeros on a mount. There is no forced synchronous layout
53
- ([react-features.md](../react-features.md#measuring-a-node)).
54
- 2. **The frame** (`WindowNode._flushFrame`): layout, the container queries,
55
- `absolutize`, the placement pass, damage, paint. **This frame shows the
56
- arrangement the effect has not corrected yet.**
57
- 3. **`onLayout`** is queued with `setImmediate` from inside that pass
58
- (`Node._reportLayout`), so it fires after the paint; its `setState` is a
59
- second render, a second commit, a second layout pass and a second paint.
60
-
61
- A probe against the mock backend with production scheduling, a row whose
62
- column count is one per 100px of its width:
63
-
64
- | scenario | `useLayoutEffect` measuring `abs`, then `setState` | `onLayout` → `setState` |
65
- | ---------------------- | ----------------------------------------------------- | ------------------------ |
66
- | mount at 200px | reads 0 → paints 1 column, **stays wrong** | paints 1 column, then 2 |
67
- | React widens it to 400 | reads 200 (stale) → paints 2 columns, **stays wrong** | paints 2 columns, then 4 |
68
- | the window widens | **never runs** — nothing committed | paints 2 columns, then 4 |
69
-
70
- `useLayoutEffect` is on time and blind; `onLayout` sees and is late. The
71
- DOM gets both only because reading geometry forces a synchronous reflow,
72
- and React Native's New Architecture got both by laying out synchronously in
73
- the commit. Neither covers the third row, which is the one that matters
74
- most, because most layout passes are not React's:
75
-
76
- - **A scroll.** ntk's frame runs before React commits an `onScroll` update —
77
- the reason sticky had to be native (#523), whose probe painted every wheel
78
- notch twice. And a pure scroll takes the fast path (#405), which moves the
79
- children without walking them: only a pass run after `_absolutizeChildren`
80
- sees those frames.
81
- - **A live resize on macOS.** AppKit's modal loop owns the thread; the resize
82
- callback flushes the frame synchronously (`src/cocoa/app.js`), so an
83
- in-frame arrangement follows the drag. Timers and microtasks wait for the
84
- release, and `onLayout` is a `setImmediate`: an arrangement built on it
85
- freezes for the whole drag.
86
- - **A transition on a layout property**, which costs a layout pass per frame
87
- ([styling.md](../styling.md#transitions)) — an effect-driven arrangement
88
- trails it by a frame on every frame.
89
- - **Everything else that changes a layout inside a frame:** a container
90
- query flipping, a font or image arriving through `invalidateMeasure`.
91
-
92
- The components package was already paying for this. Its virtualization
93
- window builds a guessed number of rows because "there is always one render
94
- that has to guess"; Table measures its rows "a tick after the commit because
95
- layout runs on the frame flush"; Reorder's drop flight reads its landing
96
- place in an `afterLayout` tick. And `<Html>` implements block, inline, float
97
- and table layout itself, inside one node, to arrange children at all.
98
-
99
- The core already had five of these in-frame passes, each written by hand:
100
- the container queries (restyle and relayout, bounded and pinned), sticky
101
- (placement and blit-aware damage), anchored popups, `scrollIntoView`, and
102
- the content floors. The seams are the same slot, opened, with the
103
- mechanics — when it runs, damage, the blit, hit testing, order — left in
104
- the core and only the policy handed out. That is AGENTS.md's "decide it,
105
- then leave a way out" twice over: sticky and flexbox are the decided
106
- defaults, and these are the ways out.
107
-
108
- ## 2. Positions
109
-
110
- ```js
111
- registerPosition('parallax', {
112
- options: { rate: { type: 'number', default: 0.5 } },
113
- place: (node, { pane, options }) =>
114
- pane ? { x: 0, y: Math.round(pane.scrollY * options.rate) } : null,
115
- });
116
-
117
- <box style={{ position: { name: 'parallax', rate: 0.3 } }} />;
118
- ```
119
-
120
- A node with a registered position is laid out in flow, as `relative` is,
121
- and after the pass `place(node, context)` returns how far to move it from
122
- where layout put it, or null for not at all. The context is everything
123
- sticky needed, in window coordinates and device pixels like `abs`: the
124
- laid-out rect, the nearest scroll pane's scrollport and offsets, the box the
125
- node is contained by (its parent's content box, or the whole scrolled
126
- content for a direct child of the pane), its margins, its direction, the
127
- scale, the frame clock, and the options resolved. `sticky` is exactly a
128
- function of that context (`placeSticky` in `src/layouts.js`), and all 28
129
- sticky tests pass against it unchanged.
130
-
131
- What the core keeps, because each is easy to get wrong and was, once, while
132
- sticky was built:
133
-
134
- - **Nothing remembers an offset.** The scroll fast path moves a placed node
135
- along with its pane's content, offset and all, so each pass re-derives
136
- where layout put the node (`Node._laidOutAt`) and moves it from wherever
137
- it is.
138
- - **Its moves are claimed by hand**, because neither the layout diff nor the
139
- fast path sees them: the reach the node was shown at, moved by the pending
140
- blit, and the reach it lands at, written into the blitting pane's ledger.
141
- A node that rode the scroll lands where the blit put it and claims
142
- nothing.
143
- - **It paints and hit-tests over its siblings** of the same `zIndex`, as CSS
144
- paints a positioned box over the ones in flow.
145
- - **It may animate.** `{ x, y, again: true }` asks for another frame, which
146
- runs the placement pass with nothing to lay out — the shape a drop flight
147
- or a collapsing header wants, and the way transitions already keep the
148
- clock running.
149
-
150
- The insets are the scheme's to read, never offsets: sticky reads them as
151
- thresholds, and a registered position may read them as anything. Yoga never
152
- sees them (`applyLayoutStyle`).
153
-
154
- **Decided: `position`, not a property of its own.** The first name was
155
- `placement`, and it collided on the first full run: `placement` is the
156
- anchor vocabulary — which side of its trigger a popup opens on — in
157
- `anchor.js`, `Tooltip`, `Select`, `Menu`, `Dialog` and their docs, 58 uses,
158
- and a `<popup placement>` threw the flat-style assertion the moment the
159
- word became a style name. `offset` is the anchor's gap. And the concept
160
- already had a home: CSS's `position` picks a positioning scheme, sticky is
161
- one, and a scheme's insets are its own to interpret. What that costs is
162
- composability with `absolute` — a node has one scheme, as in CSS — and a
163
- wrapping box is the answer for the rare case that wants both.
164
-
165
- ## 3. Layouts
166
-
167
- ```js
168
- registerLayout('equal-row', {
169
- layout(children, c, options, { style }) {
170
- // …how wide a cell is, then one rect per child
171
- return { width, height, children: rects };
172
- },
173
- });
174
-
175
- <box style={{ layout: 'equal-row', gap: 8, justifyContent: 'flex-end' }} />;
176
- ```
177
-
178
- **The contract** is one function, asked two kinds of question. With
179
- `constraints` in the vocabulary `measureContent` speaks — `{ width, height,
180
- widthMode, heightMode }`, `'exactly'`, `'at-most'` or `'unconstrained'`,
181
- `Infinity` for no bound — it answers how big the box's content is; that is
182
- asked several times per pass, at sizes nothing is drawn at, so it must be a
183
- pure function of its arguments. When both modes are `'exactly'`, it also
184
- answers where each child goes: one rect per child, `{ x, y }` with an
185
- optional `width` and `height`, from the content box's corner. SwiftUI's
186
- `Layout` splits these into two methods; one function serves every layout
187
- here, and masonry places while it sizes.
188
-
189
- **What an algorithm sees of a child** (`LayoutChild`) is something to
190
- measure and read options off, never a node:
191
- `measure({ width, height, widthMode, heightMode })`, `intrinsicSizes()` →
192
- `{ minContentWidth, maxContentWidth }`, `options` (its `layoutItem`, against
193
- the layout's `childOptions`) and `index`. Every size is the child's **margin
194
- box**, which is what makes an algorithm that stacks children right about
195
- margins without knowing they exist — yoga already treats the size a root is
196
- laid out at as the margin box. An axis given a number and no mode is
197
- `'exactly'`; left out it is `'unconstrained'`; `'at-most'` is CSS's
198
- fit-content, never below the child's floor.
199
-
200
- **Options are typed.** `options` and `childOptions` declare what the style
201
- may say — `length`, `number`, `integer`, `boolean`, `string`, `any`, or a
202
- list of values — so a wrong one is reported naming the option and the type,
203
- and a `length`, written in logical pixels like every length in a style,
204
- arrives in device pixels. `info.style` is the host's own resolved style, so
205
- a layout reads `gap`, `justifyContent` and `alignItems` rather than growing
206
- options that mean the same things: `equal-row` has no options of its own.
207
-
208
- ### The mechanism
209
-
210
- - **A host is a measured leaf.** `_syncLayoutHost`, run from the style
211
- funnel, takes the node's children out of its yoga node and gives it a
212
- measure function (`_measureHost`) that asks the algorithm. Yoga can do no
213
- other: a node with a measure function may have no yoga children, and it
214
- aborts rather than refuse.
215
- - **Each child is a yoga tree of its own**, laid out by `LayoutChild` with
216
- `calculateLayout` at the size the algorithm asks for. The host's direction
217
- goes in as the third argument, the way a window's reaches its tree.
218
- - **The final call runs at the end of every layout step**
219
- (`WindowNode._placeLayoutHosts`), parents before the hosts inside their
220
- children, and before anything reads a child's size — the container
221
- queries settle on what it leaves. It lays each child out at its rect and
222
- records the slot, which `absolutize` then reads (`offsetInParent` is the
223
- one sum that, `onLayout`, `scrollIntoView` and a scroll pane's extent
224
- make). It is skipped when nothing asked the algorithm anything since the
225
- last placement and the box is the size it was: a scroll never asks.
226
- - **Dirt.** A child's yoga tree stops its dirt at its own root, so the
227
- window looks for it before every pass (`_sweepLayoutHosts`) and makes it
228
- the host's. Yoga's dirtied callback is no substitute: it fires only on the
229
- way from clean to dirty, and a child hidden since it mounted was never
230
- laid out and would never say so. A second path covers the same ground
231
- today: a changed child owes the floors pass a width, and that pass
232
- measures off the pixel grid, which has yoga ask every host again. Take
233
- either away and the tests pass; take both and they fail.
234
- - **What a child is asked is remembered.** An algorithm asks each child the
235
- same questions every run, and a host runs several times a frame — the
236
- floors passes, the pass proper, the placement. Each answer is a layout of
237
- the child's tree, and two different questions evict each other from
238
- yoga's one-entry cache, so the answers are kept (`Node._hostSizesNow`)
239
- until something inside the child changes. That shows as dirt on its root:
240
- every layout of the root goes through `layoutHostChild` or the memo, both
241
- drop the memo when they find it dirty, and nothing else clears the dirt.
242
- Answers taken off the pixel grid are kept apart from the rest. A placement
243
- leaves a tree already laid out at its rect where it is (`_hostLaidAt`),
244
- since the floors pass voids yoga's own cache for every tree in the window.
245
- Before this, a 300-card grid's frame took 3.4 times flexbox's
246
- ([grid-layout.md](grid-layout.md#3-performance)).
247
- - **Right to left is the core's.** An algorithm is written once, from the
248
- left; the placement mirrors each slot across the content box when the host
249
- reads right to left, and each child's own tree mirrors inside it.
250
- - **Absolutely positioned children** are not the algorithm's. They are laid
251
- out by yoga's own rules against a holder standing in for the padding box.
252
- - **A layout that throws**, or answers something that is not a size, is
253
- reported like a handler's throw (`reportLayoutError`, `onUncaughtError`
254
- included) and turned off: the box is laid out as flexbox in the same
255
- frame, and stays so until its style names another layout. A name nobody
256
- registered is reported once and is flexbox too.
257
- - **Not a host:** a scroll pane — whose size is its viewport's, not its
258
- content's; the layout goes on a box inside it — a window, and anything
259
- that measures its own content.
260
-
261
- ## 4. The content floors inside the children
262
-
263
- The floors (#249) are measured over the window's yoga tree: one pass with no
264
- room on offer, read back by `contentSpan`, written onto every flex item as
265
- its minimum. A host's children are not in that tree, so the window's walkers
266
- stop at a host and treat it as the leaf yoga sees — its minimum is what its
267
- algorithm answers at `at-most 0` — and the children are measured tree by
268
- tree:
269
-
270
- - **Widths**, before the window's own pass (`_measureHostChildWidths`):
271
- each stale child's tree with no room, the shrink borrowed, its span read
272
- back into the extents of everything stale inside it. The child's own
273
- extent is its min-content width — what `intrinsicSizes()` hands its
274
- algorithm, and so what `equal-row` squeezes to and `masonry`'s minimum is
275
- made of. Deepest hosts first, so a host inside a card has its children's
276
- extents when the card's pass asks it for its minimum.
277
- - **Heights**, at the width the placement gave each child
278
- (`_measureHostChildHeights`) — a minimum height is a height for a width,
279
- and the width is the algorithm's. Only for a child that can come out
280
- shorter than its content: one naming a height of its own, or one its rect
281
- gives less height than its content takes at the rect's width. A masonry's
282
- cards and a stretched grid row's items, sized to fit, squeeze nothing, so
283
- the common case pays nothing. A live resize defers them with the rest.
284
-
285
- ## 5. Cost
286
-
287
- **No layout, no cost.** Every added branch is behind `_layoutHosts.size` or
288
- a `_host !== null` read; the sweep returns on an empty registry.
289
-
290
- **With a layout.** Measured on the seam as built
291
- (`scripts/bench/grid/bench.mjs`, in the in-process server with real text
292
- shaping): 300 cards of 9 yoga nodes each, with wrapping titles, laid out by
293
- `masonry` and, for comparison, as a flex-wrap row. Layout time per frame;
294
- the changes and resizes are medians of nine:
295
-
296
- | case | masonry | flex-wrap |
297
- | ----------------------- | ------: | --------: |
298
- | mount | 72.3 ms | 64.8 ms |
299
- | one card's text changed | 1.6 ms | 13.0 ms |
300
- | window resized 1000↔900 | 4.2 ms | 20.3 ms |
301
-
302
- The two are different algorithms, so read the last two rows as "not
303
- slower" before "faster". What they do show is structural: each child's tree is a
304
- containment boundary, so a change inside one card re-lays out that card
305
- (one text measure of 300), and no cycle can form inside a pass — a child
306
- cannot reach the constraints its host gives it. Container queries needed a
307
- runtime pin for that guarantee; here it holds by construction.
308
-
309
- ## 6. The spike
310
-
311
- `calculateLayout` nested inside a measure call on another tree works — the
312
- components package's `<Html>` flex has shipped on it for months — and the
313
- questions it left were the ones above: how often yoga asks (once per pass in
314
- a column and as a shrinking row item), whether a clean frame re-asks (no),
315
- and whether a detached child's dirt can reach the host (only by a sweep or a
316
- dirtied callback, and only the sweep covers a child never laid out).
317
-
318
- ## 7. Not done
319
-
320
- - **A scroll pane or a window as the host.** Both are sized by something
321
- other than their content; a box inside them is the host today.
322
- - **Baselines** across a host (`alignItems: 'baseline'` in its parent), which
323
- needs yoga's baseline function.
324
- - **Fragmentation.** Houdini's layout API has break tokens for pages and
325
- columns; nothing here paginates.
326
- - **Min-content heights** for an algorithm to read; only widths are handed
327
- over.
328
- - **Subgrid.** [Grid](grid-layout.md) is built on this seam, and this is
329
- the part of it the seam cannot carry: a subgrid's items take part in the
330
- parent grid's track sizing, and an algorithm here sees a child as
331
- something to measure, never its children.
332
- - **Hot reload** keeps a mounted host on the definition it started with
333
- until its style names a layout again — the contract registered elements
334
- have.