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
@@ -2,30 +2,19 @@
2
2
 
3
3
  react-x11 applications are visible to screen readers. The renderer carries a
4
4
  built-in [AT-SPI2](https://gitlab.gnome.org/GNOME/at-spi2-core) bridge — the
5
- same interface GTK and Qt applications register themselves through — so Orca
6
- reads a react-x11 window the way it reads any other: roles, names, states,
7
- focus, live text editing, and the actions to drive controls without a
8
- pointer.
9
-
10
- It is on by default and costs nothing to have: with no accessibility bus on
11
- the machine the bridge never comes up, and the render path pays one property
12
- read per event for the hooks it would have filled.
13
-
14
- > **This is the Linux bridge; the win32 backend has one of its own and Cocoa
15
- > has none.** On **win32** the same tree is pushed to UI Automation instead
16
- > (`src/win32/a11y.js`, [windows.md](windows.md#accessibility-ui-automation)),
17
- > because UIA asks synchronously and is answered from a pushed copy rather
18
- > than from the live tree. AT-SPI
19
- > is a freedesktop protocol with nothing to register against on macOS, so on
20
- > the **Cocoa** backend `createRoot()` leaves it off (`desktop: { a11y:
21
- false }` by default there) and no `NSAccessibility` bridge has been built
22
- > in its place — a Cocoa-backend app is not yet reachable by VoiceOver.
23
- > Everything else on this page still earns its keep there: `role`, `aria-*`
24
- > and `announce()` are properties of the shared node tree, keyboard
25
- > traversal and the widgets' own semantics are shared code, and all of it is
26
- > what an `NSAccessibility` bridge will read when it lands
27
- > ([macos.md](macos.md), Phase 5). Write them now; nothing changes when it
28
- > does.
5
+ interface GTK and Qt register through — so Orca reads a react-x11 window like
6
+ any other: roles, names, states, focus, live text editing, and the actions
7
+ that drive controls without a pointer. It is on by default; with no
8
+ accessibility bus the bridge never comes up, and the render path pays one
9
+ property read per event.
10
+
11
+ > **Per backend.** On **win32** the same tree is pushed to UI Automation
12
+ > instead ([windows.md](windows.md#accessibility-ui-automation)). On
13
+ > **Cocoa** there is no bridge yet: `createRoot()` leaves AT-SPI off there
14
+ > (`desktop: { a11y: false }` by default) and a Cocoa-backend app is not
15
+ > reachable by VoiceOver. Write `role`, `aria-*` and `announce()` anyway:
16
+ > they live on the shared node tree, and an `NSAccessibility` bridge will
17
+ > read them unchanged.
29
18
 
30
19
  ```jsx
31
20
  <box role="button" aria-label="Close" onClick={close} focusable>
@@ -34,21 +23,19 @@ false }` by default there) and no `NSAccessibility` bridge has been built
34
23
  ```
35
24
 
36
25
  That is the entire API for most code. `role` and `aria-*` are the web's
37
- names, accepted on every host element — the same vocabulary react-dom takes
38
- and React Native adopted — so a component library that sets them is
39
- accessible here with no react-x11-specific knowledge, and the built-in
40
- widgets (`Button`, `Checkbox`, `Select`, `Menu`, `Tabs`, …) already
41
- carry them.
26
+ names, accepted on every host element — the vocabulary react-dom takes — so
27
+ a component library that sets them is accessible here with no
28
+ react-x11-specific knowledge, and the built-in widgets (`Button`,
29
+ `Checkbox`, `Select`, `Menu`, `Tabs`, …) already carry them.
42
30
 
43
- > This page is the reference. The design record — why there is no mirror
44
- > tree, why the vocabulary is the web's rather than React Native's, why
45
- > discovery dials its own socket, what was rejected and what is deferred —
46
- > is [architecture/accessibility.md](architecture/accessibility.md).
31
+ Design notes: [design/architecture/accessibility.md](../design/architecture/accessibility.md)
32
+ (no mirror tree, the web's vocabulary, what was rejected) and
33
+ [design/accessibility.md](../design/accessibility.md).
47
34
 
48
35
  ## What a screen reader hears without any props
49
36
 
50
- Accessibility is not opt-in; the defaults are chosen so an unlabelled tree
51
- is _boring_ to a screen reader rather than broken:
37
+ Accessibility is not opt-in; an unlabelled tree is _boring_ to a screen
38
+ reader rather than broken:
52
39
 
53
40
  | element | reads as |
54
41
  | ----------------------- | ----------------------------------------------------------- |
@@ -99,58 +86,49 @@ rowheader scrollbar search searchbox separator slider spinbutton status
99
86
  switch tab table tablist tabpanel term textbox timer toolbar tooltip tree
100
87
  treegrid treeitem window`, plus `none`/`presentation` to erase a wrapper
101
88
  from the accessible tree while keeping its children. An unknown role warns
102
- in development and falls back to the element default — a typo must not
103
- silently turn a button into a filler, and it must not crash either.
89
+ in development and falls back to the element default — a typo neither turns
90
+ a button into a filler nor crashes.
104
91
 
105
- Two deliberate absences. There is no `aria-disabled`: `disabled` is already
106
- a host prop with real behaviour (it blocks focus and drives `:disabled`
107
- style blocks), and a parallel prop that only _said_ disabled would let the
108
- two disagree. And there is no `aria-labelledby`/`aria-describedby`: there
109
- are no element ids to point them at — use `aria-label`, or lean on
110
- name-from-contents.
92
+ There is no `aria-disabled`: use `disabled`, which blocks focus and drives
93
+ `:disabled` style blocks, so the two cannot disagree. There is no
94
+ `aria-labelledby`/`aria-describedby` either: there are no element ids to
95
+ point them at — use `aria-label`, or lean on name-from-contents.
111
96
 
112
97
  ### Names
113
98
 
114
99
  The accessible name resolves in order: `aria-label` → what the element
115
100
  itself carries (a window's `title`, an image's `alt`, an input's
116
- `placeholder` — the fallback the web engines also use) → for roles whose
117
- contents are their label (buttons, checkboxes, menu items, tabs, links,
118
- options, tree items, …), the visible text inside. So
119
- `<Button>Save</Button>` is named "Save" with nobody writing a label, and
120
- `announce`-worthy custom controls need one prop, not three.
101
+ `placeholder`) → for roles whose contents are their label (buttons,
102
+ checkboxes, menu items, tabs, links, options, tree items, …), the visible
103
+ text inside. So `<Button>Save</Button>` is named "Save" with nobody writing
104
+ a label.
121
105
 
122
106
  ### AT-driven controls
123
107
 
124
- Activation needs no wiring at all: every element whose role promises it
125
- (button, checkbox, menu item, tab, …) or that has an `onClick` exposes an
126
- AT-SPI `activate` action, and the bridge performs it by dispatching a
127
- synthetic click through the normal event path — capture, bubble, default
128
- actions, the discrete-priority commit — so Orca pressing a button is
129
- indistinguishable from a finger.
130
-
131
- (Under the hood the activation is a full press gesture — mousedown, mouseup,
132
- click — because several controls act on the press: `Select` and `MenuBar`
133
- drop their menus on it, the way real menus do.)
108
+ Activation needs no wiring: every element whose role promises it (button,
109
+ checkbox, menu item, tab, …) or that has an `onClick` exposes an AT-SPI
110
+ `activate` action. The bridge performs it as a synthetic click through the
111
+ normal event path — capture, bubble, default actions, the discrete-priority
112
+ commit — so Orca pressing a button is indistinguishable from a pointer. It
113
+ is a full press gesture (mousedown, mouseup, click), because `Select` and
114
+ `MenuBar` open their menus on the press.
134
115
 
135
116
  **The keyboard shares that click.** Space and Enter on a focused element with
136
- an `onClick` dispatch the same gesture through the same function, so a
137
- keyboard user and a screen-reader user are never served differently by the
138
- same element — see [events.md](events.md#space-and-enter-are-a-click). The
139
- role half of the rule above stays the AT's: a role _advertises_ an action to
140
- something that cannot press a key, and a `role="button"` with no `onClick`
141
- has no click for a key to be.
117
+ an `onClick` dispatch the same gesture through the same function
118
+ ([events.md](events.md#space-and-enter-are-a-click)). A role _advertises_
119
+ an action to an AT that cannot press a key; a `role="button"` with no
120
+ `onClick` has no click for a key to trigger.
142
121
 
143
- The one thing that does need a handler is a _value_ write — Orca adjusting
144
- a slider through the Value interface. The handler receives one argument:
122
+ The one thing that needs a handler is a _value_ write — Orca adjusting a
123
+ slider through the Value interface. The handler receives one argument:
145
124
 
146
125
  ```ts
147
126
  { action: 'setValue', value: number }
148
127
  ```
149
128
 
150
- (`action` is a string so the shape can grow; `'setValue'` is the only
151
- action delivered today — activation never arrives here, it goes through the
152
- event system above.) Wire it into the same state setter as the pointer and
153
- the keyboard:
129
+ `'setValue'` is the only action delivered today (`action` is a string so
130
+ the shape can grow; activation never arrives here). Wire it into the same
131
+ state setter as the pointer and the keyboard:
154
132
 
155
133
  ```jsx
156
134
  <box
@@ -178,83 +156,84 @@ announce('Connection lost', { assertive: true });
178
156
  The explicit counterpart of an ARIA live region: say something through the
179
157
  screen reader without moving focus. Returns `true` when a bridge was live
180
158
  to carry it; `false` means nobody is listening and a visible fallback may
181
- be warranted. (Orca ≥ 45 speaks these; there is no `aria-live` region
182
- tracking — an explicit call is smaller, and it cannot fire on renders you
183
- did not mean.)
159
+ be warranted. Orca ≥ 45 speaks these. There is no `aria-live` region
160
+ tracking — an explicit call cannot fire on renders you did not mean.
184
161
 
185
162
  ## What the built-in widgets announce
186
163
 
187
164
  Every widget in [components.md](components.md) is wired; none of them need
188
165
  anything from you beyond their ordinary props. What a screen reader hears:
189
166
 
190
- | widget | reads as |
191
- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
192
- | `Button` | button, named by its label; disabled buttons say so |
193
- | `Checkbox` | check box, checked/unchecked, named by its label |
194
- | `Radio` / `RadioGroup` | radio buttons with the selected one checked |
195
- | `Switch` | switch, on/off — **give it an `aria-label`**, it has no visible label of its own |
196
- | `Slider` | horizontal slider with value and range; Orca can adjust it |
197
- | `ProgressBar` | progress bar; the value reads as a percentage of its range, and an `indeterminate` one is busy with no value rather than 0% |
198
- | `Select` | combo box named by the current choice, expanded/collapsed; its menu is a listbox of options, each "n of m" and the current one selected |
199
- | `MenuBar` / `ContextMenu` | menu bar and menus; items carry their shortcut as the standard `keyshortcuts` attribute ("Control+N" — UI Events spelling, where the row draws "Ctrl+N" and the key path [binds it](events.md#accelerators)), a `toggleType` as checked/radio, `toggleState: -1` as `mixed`, submenus as expandable |
200
- | `Tooltip` | tooltip |
201
- | `Dialog` | modal dialog, named by its title |
202
- | `Tabs` | tab list with orientation; each tab selected or not; the panel is a tab panel |
203
- | `Table` | table with column headers and rows ("n of m", selected). Virtualization means only rendered rows exist in the tree — the same rows a sighted user can see |
204
- | `SplitPane` | the divider is a separator with orientation and position — the ARIA window-splitter pattern |
167
+ | widget | reads as |
168
+ | ------------------------- | -------------------------------------------------------------------------------- |
169
+ | `Button` | button, named by its label; disabled buttons say so |
170
+ | `Checkbox` | check box, checked/unchecked, named by its label |
171
+ | `Radio` / `RadioGroup` | radio buttons with the selected one checked |
172
+ | `Switch` | switch, on/off — **give it an `aria-label`**, it has no visible label of its own |
173
+ | `Slider` | horizontal slider with value and range; Orca can adjust it |
174
+ | `ProgressBar` | progress bar, its value as a percentage of its range; `indeterminate` is busy |
175
+ | `Select` | combo box named by the current choice, expanded/collapsed; its menu is a listbox |
176
+ | `MenuBar` / `ContextMenu` | menu bar and menus (below) |
177
+ | `Tooltip` | tooltip |
178
+ | `Dialog` | modal dialog, named by its title |
179
+ | `Tabs` | tab list with orientation; each tab selected or not; the panel is a tab panel |
180
+ | `Table` | table with column headers and rows ("n of m", selected) |
181
+ | `SplitPane` | the divider is a separator with orientation and position (ARIA window splitter) |
182
+
183
+ An `indeterminate` `ProgressBar` reports no value rather than 0%. A
184
+ `Select`'s options each read "n of m", the current one selected. Menu items
185
+ carry their shortcut as the standard `keyshortcuts` attribute ("Control+N" —
186
+ UI Events spelling, where the row draws "Ctrl+N" and the key path
187
+ [binds it](events.md#accelerators)), a `toggleType` as checked/radio,
188
+ `toggleState: -1` as `mixed`, and submenus as expandable. A virtualized
189
+ `Table` has only its rendered rows in the tree — the same rows a sighted
190
+ user can see.
205
191
 
206
192
  ## Text controls
207
193
 
208
194
  `<textinput>` and `<textarea>` implement the AT-SPI `Text` and
209
- `EditableText` interfaces in full working order: character count, reading
210
- by character/word/line, caret position (offsets are code points, on both
195
+ `EditableText` interfaces in full: character count, reading by
196
+ character/word/line, caret position (offsets are code points, on both
211
197
  sides), selections, per-character screen rectangles for magnifiers and
212
198
  caret tracking, and edits — a screen reader can read, navigate, select,
213
199
  and type. Every edit and caret move emits the precise
214
200
  `text-changed`/`text-caret-moved` deltas Orca narrates from.
215
201
 
216
- One honest limit: "line" granularity follows hard newlines, not the soft
217
- wraps of a `<textarea>`'s layout. Orca still reads everything; a
218
- line-by-line walk of one long wrapped paragraph is one stop, not several.
202
+ Limit: "line" granularity follows hard newlines, not the soft wraps of a
203
+ `<textarea>`'s layout. Orca still reads everything; a line-by-line walk of
204
+ one long wrapped paragraph is one stop, not several.
219
205
 
220
- A `<text>` has the `Text` interface too — readable, with no caret to move.
221
- What it now also reports is its **selection**: a paragraph inside a
222
- `selectable` surface ([elements.md](elements.md#selecting-text)) hands over
223
- the range the user has dragged across, and says so as it moves, so a
224
- magnifier's highlight and a braille display's cursor sit on the same
225
- characters the highlight is painted over.
206
+ A `<text>` has the `Text` interface too — readable, with no caret — and
207
+ reports its **selection**: a paragraph inside a `selectable` surface
208
+ ([elements.md](elements.md#selecting-text)) hands over the range the user
209
+ has dragged across, and says so as it moves, so a magnifier's highlight and
210
+ a braille display's cursor sit on the highlighted characters.
226
211
 
227
212
  ### While a composition is open
228
213
 
229
214
  A dead key or a half-typed Compose sequence puts text on the screen that is
230
215
  in nobody's value yet ([events.md](events.md#composition)). The `Text`
231
216
  interface answers with **what is drawn**, that preedit included, and its
232
- offsets index that string — because every geometric answer beside it is
233
- read off the layout of the same string, and a magnifier tracking character
234
- 3 has to land on the glyph a sighted user sees. `CaretOffset` is at the far
235
- end of the composition, where the next keystroke of the sequence appears.
236
-
237
- Which part of it is uncommitted is said twice, so a reader can act on
238
- either:
239
-
240
- - the preedit is its own **attribute run** — `underline: single`, the
241
- registered AT-SPI attribute for what is literally drawn, plus
242
- `composition: true`, which is this renderer's own because AT-SPI
243
- registers nothing for a preedit;
244
- - its churn is **`:system`** text — `text-changed:insert:system` when the
245
- accent appears, `delete:system` when it changes or is abandoned. That is
246
- the suffix Gecko established for a change the user did not type, and it
247
- is what lets a reader stay quiet through a composition.
217
+ offsets index that string, so a magnifier tracking a character lands on the
218
+ glyph a sighted user sees. `CaretOffset` is at the far end of the
219
+ composition, where the next keystroke of the sequence appears.
220
+
221
+ The uncommitted part is marked twice:
222
+
223
+ - as its own **attribute run** — `underline: single` (the registered AT-SPI
224
+ attribute) plus `composition: true` (this renderer's own; AT-SPI
225
+ registers nothing for a preedit);
226
+ - as **`:system`** text — `text-changed:insert:system` when the accent
227
+ appears, `delete:system` when it changes or is abandoned — which lets a
228
+ reader stay quiet through a composition.
248
229
 
249
230
  What the sequence **commits** is a plain `insert` of the finished
250
- character: `é` is what the user typed, and it is spoken, once, as one
251
- insertion — the same event an ordinary keystroke produces, which is also
252
- why it is one undo step and not two.
231
+ character: `é` is spoken once, as one insertion — the same event an
232
+ ordinary keystroke produces, and one undo step.
253
233
 
254
- Nothing here is announced through `announce()`. A live-region announcement
255
- on top of the text-changed feed would have a screen reader say the accent
256
- twice; an application that wants to narrate composition state can call
257
- `announce()` itself from `onCompositionUpdate`.
234
+ Nothing here goes through `announce()` (the screen reader would say the
235
+ accent twice); an application that wants to narrate composition state can
236
+ call `announce()` itself from `onCompositionUpdate`.
258
237
 
259
238
  ### Text of your own
260
239
 
@@ -291,40 +270,37 @@ class EditorNode extends Node {
291
270
  | `a11yReplaceText(start, end, text)` | an AT edited — insert, delete and replace all arrive as this |
292
271
  | `a11yRole` | the role the element is when the app writes none; `role` still wins |
293
272
 
294
- Two tiers, because the read-only one is a real element and not a
295
- degenerate editor. `{ value, selectionStart, selectionEnd }` alone is a
296
- document: the `Text` interface, a `document` role, a selection an AT reads
297
- and sets — which is what a markdown view with Ctrl+C needs and all it
298
- needs. `editable: true` adds the EDITABLE state, an `entry` role and, once
299
- `a11yReplaceText` exists, the `EditableText` interface. Nothing is exposed
300
- that would not work: an element that reports editable text but implements
301
- no write is announced as editable and reports its own edits, but is not
302
- offered to an AT as one to type into.
273
+ Two tiers. `{ value, selectionStart, selectionEnd }` alone is a document:
274
+ the `Text` interface, a `document` role, a selection an AT reads and sets —
275
+ all a markdown view with Ctrl+C needs. `editable: true` adds the EDITABLE
276
+ state, an `entry` role and, once `a11yReplaceText` exists, the
277
+ `EditableText` interface. An element that reports editable text but
278
+ implements no write is announced as editable and reports its own edits, but
279
+ is not offered to an AT to type into.
303
280
 
304
281
  Offsets are code points and index the string the element **draws** — an
305
- open composition included, for the same reason `<textinput>`'s do. Report
306
- which part is uncommitted as `preedit: { offset, text }` and its churn is
307
- marked `:system`, so a reader stays quiet through a dead key and speaks the
308
- character it commits. Offsets are clamped on the way out, so a stale caret
309
- cannot become an out-of-range answer on the wire.
310
-
311
- The known limits of the tier, none of which stop a screen reader working:
312
- per-character screen rectangles fall back to the element's own rect (a
313
- magnifier tracks the element, not the glyph), and AT-driven paste and copy
314
- are the built-ins' — an element's own Ctrl+C/Ctrl+V is unaffected. Both
315
- want a layout the element alone has; say so on the issue if you have one.
316
-
317
- The seam is observed by the test spy exactly as core's is, so an editor in
318
- another package can assert what it says with no bus and no desktop —
282
+ open composition included, as with `<textinput>`. Report the uncommitted
283
+ part as `preedit: { offset, text }` and its churn is marked `:system`, so a
284
+ reader stays quiet through a dead key and speaks the character it commits.
285
+ Offsets are clamped on the way out, so a stale caret cannot become an
286
+ out-of-range answer.
287
+
288
+ Known limits, none of which stop a screen reader working: per-character
289
+ screen rectangles fall back to the element's own rect (a magnifier tracks
290
+ the element, not the glyph), and AT-driven paste and copy are the
291
+ built-ins' — an element's own Ctrl+C/Ctrl+V is unaffected.
292
+
293
+ The test spy observes this seam exactly as it observes core's, so an editor
294
+ in another package can assert what it says with no bus and no desktop —
319
295
  [below](#asserting-what-a-screen-reader-would-hear).
320
296
 
321
297
  ### A scene of your own
322
298
 
323
- The other half of the same gap. A [registered element](extending.md) that
324
- _draws_ interactive things — a graph pane, a chart, a timeline, a seating
325
- plan — is one node, so it is one accessible: "Flow graph, group", with
326
- three hundred selectable nodes inside it that no assistive technology can
327
- see. It says what it drew and each one becomes a child:
299
+ A [registered element](extending.md) that _draws_ interactive things — a
300
+ graph pane, a chart, a timeline, a seating plan — is one node, so it is one
301
+ accessible: "Flow graph, group", with three hundred selectable nodes inside
302
+ it that no assistive technology can see. It says what it drew and each one
303
+ becomes a child:
328
304
 
329
305
  ```js
330
306
  class FlowNode extends Node {
@@ -353,17 +329,16 @@ class FlowNode extends Node {
353
329
  | `notifyA11ySceneChanged()` | the scene moved between commits — a drag, an animation, your own arrow keys |
354
330
  | `a11ySceneAction(id, action)` | an AT sent `'activate'`, `'focus'` or `'scroll'`. `true` claims it; anything else keeps core's answer |
355
331
 
356
- They are read by the same model everything else is: `role` and the
357
- `aria-*`-shaped states above become the AT-SPI role and state set, `rect`
358
- becomes the Component extents a magnifier tracks and focus is drawn around,
359
- and a change to any of it is announced as that child changing rather than as
360
- the pane being replaced. Keyboard navigation _between_ items stays the
361
- element's own — the window's focus manager holds the element, which is why
332
+ `role` and the `aria-*`-shaped states become the AT-SPI role and state set,
333
+ `rect` becomes the Component extents a magnifier tracks and focus is drawn
334
+ around, and a change to any of it is announced as that child changing
335
+ rather than as the pane being replaced. Keyboard navigation _between_ items
336
+ stays the element's own — the window's focus manager holds the element, so
362
337
  `states.focused` is the only way the cursor inside it is reported.
363
338
 
364
339
  Activation with no `a11ySceneAction` is a synthetic click at the item's own
365
- rect, which is exactly what a mouse user does to it; the seam is for the
366
- elements where that is not the truth. See
340
+ rect, which is what a mouse user does to it; the seam is for elements where
341
+ that is not the truth. See
367
342
  [extending.md](extending.md#a-scene-a-screen-reader-can-walk) for the whole
368
343
  contract.
369
344
 
@@ -379,11 +354,10 @@ contract.
379
354
  4. connect, export the tree, `Embed` with the AT-SPI registry.
380
355
 
381
356
  Every "no" is a normal, **silent** off: ssh sessions, CI, containers, bare
382
- `startx`, macOS and Windows X servers — react-x11's own flagship
383
- configurations — simply have no accessibility stack, and an app must not
384
- log, slow down or grow a mode because of it. The bridge holds no
385
- event-loop reference either, so a process that is done exits; the registry
386
- sees the name drop and forgets the app.
357
+ `startx`, macOS and Windows X servers have no accessibility stack, and the
358
+ app does not log, slow down or change behaviour because of it. The bridge
359
+ holds no event-loop reference, so a process that is done exits; the
360
+ registry sees the name drop and forgets the app.
387
361
 
388
362
  Environment switches, in priority order:
389
363
 
@@ -401,8 +375,8 @@ Environment switches, in priority order:
401
375
  `react-x11/test` sets it, so a test run on a desktop does not parade
402
376
  phantom applications through a running screen reader.
403
377
 
404
- And one that is not environmental, for an app that has to turn the bridge off
405
- for itself without setting a variable every child process will inherit:
378
+ To turn the bridge off from the app itself, without a variable every child
379
+ process inherits:
406
380
 
407
381
  ```jsx
408
382
  await createRoot({ desktop: { a11y: false } });
@@ -415,21 +389,13 @@ Pass it on the first root: the climb happens once per process, and a second
415
389
  root's switch cannot unstart a bridge that is already up.
416
390
 
417
391
  A dead accessibility bus is not redialled — the same contract as
418
- [dbus.md](dbus.md): the app simply stops being accessible until restarted.
392
+ [dbus.md](dbus.md): the app stops being accessible until restarted.
419
393
 
420
394
  ## How often the bridge speaks
421
395
 
422
- A bridge mirrors the tree for a platform that reads it on its own schedule,
423
- and pushing the mirror on every commit is paid by everything that commits
424
- often: a scroll, a pan, a value ticking. That was mostly paid for nobody.
425
- An AT-SPI bus runs in most Linux sessions whether or not a screen reader
426
- does, and a 100,000-row `<Table>` flung through it exported, diffed and
427
- unexported every row that scrolled past: 31 frames a second against 38 with
428
- the bridge off. On Windows the same push was 1.6 ms of every frame of a
429
- graph pane's pan. So both bridges pace what they push (`src/a11ypace.js`),
430
- the way browsers do. Chromium batches non-interactive accessibility updates
431
- 150 ms apart once a page has loaded, and bounds 500 ms apart, and serializes
432
- focus and selection at once.
396
+ Both bridges (AT-SPI and UI Automation) pace what they push, the way
397
+ browsers do, so a scroll, a pan or a ticking value does not pay for a
398
+ platform update on every commit:
433
399
 
434
400
  - **A change after a quiet spell is pushed at once.** A click or a key that
435
401
  changes one thing is heard immediately.
@@ -449,19 +415,10 @@ back to a push per change.
449
415
 
450
416
  ### With nobody listening
451
417
 
452
- Pacing bounds what a push costs. It does not help when nobody hears the
453
- push, which is the usual case: the bus runs whether or not a screen reader
454
- does. Dragging a scrollbar thumb down a 100,000-row `<Table>` ran at 19
455
- frames a second with the AT-SPI bridge on and 24 with it off, and nothing on
456
- that desktop was listening. So the bridge asks the registry.
457
-
458
- An AT registers the events it wants with the registry. The registry answers
459
- `GetRegisteredEvents` and announces each new listener, and GTK 4 and
460
- at-spi2-atk use that to hold back the events nobody asked for. Both still
461
- send the cache's signals and `children-changed` regardless, because libatspi
462
- listens for those in every client without registering. So an empty list is
463
- not the whole answer. An AT that registered for nothing still has to call in
464
- to read anything, and a call from anybody but the registry counts too.
418
+ An AT-SPI bus runs in most Linux sessions whether or not a screen reader
419
+ does, so the bridge asks the registry who is listening. A listener is an AT
420
+ that registered for events, or any client other than the registry that
421
+ calls in.
465
422
 
466
423
  - **With no listener and no caller, the bridge falls silent.** Nothing is
467
424
  queued, pushed or exported, and what it had exported before the registry
@@ -524,23 +481,22 @@ Every entry is a precise fact (`{ type, state, on, node, … }`) plus a
524
481
  one-line `summary`; `at.transcript()` is the summaries, made for
525
482
  `deepEqual`. `at.focused()` and `at.focusables()` describe nodes the way an
526
483
  AT would — name, role, states, and an `utterance` string following the
527
- model documented on this page (name, role, the states worth speaking, a
528
- value as a percentage). The utterance is deliberately **this library's
529
- model, not an imitation of Orca**, whose wording is presentation policy;
530
- what it buys you is that a control nobody named renders as
531
- `"(no accessible name)"` instead of passing because no assertion happened
532
- to mention its name.
484
+ model on this page (name, role, the states worth speaking, a value as a
485
+ percentage). The utterance is **this library's model, not an imitation of
486
+ Orca**, whose wording is presentation policy; a control nobody named
487
+ renders as `"(no accessible name)"` instead of passing because no assertion
488
+ happened to mention its name.
533
489
 
534
490
  Four tests worth copying into any application — each one a check a sighted
535
- test author cannot make by looking (`test/a11y-spy.test.js` is the worked
536
- example of all four):
491
+ test author cannot make by looking (`test/a11y-spy.test.js` in the
492
+ repository is the worked example of all four):
537
493
 
538
494
  - **Focus order** — tab through, `deepEqual` the focus summaries.
539
495
  - **No keyboard trap** — `at.focusables().length` + 1 Tabs lands back on
540
496
  the first stop, and Shift+Tab wraps the other way.
541
497
  - **Nothing nameless** — for each of `at.focusables()`, assert the
542
- utterance is not `"(no accessible name)"`. The highest value per line in
543
- this document.
498
+ utterance is not `"(no accessible name)"`. The highest value per line on
499
+ this page.
544
500
  - **State changes are announced** — press Space on a checkbox, assert a
545
501
  `checked` state entry arrived; a widget that repaints without one is the
546
502
  most common accessibility regression there is.
@@ -552,13 +508,13 @@ The wire half is testable without a desktop too: point
552
508
  `org.a11y.atspi.Registry` on it with a stub `Embed`, and walk your app with
553
509
  real D-Bus calls — `test/atspi.test.js` is the worked example, and the
554
510
  model half (roles, names, states as pure functions over the tree) needs no
555
- bus at all (`test/a11y.test.js`). Applications rarely need this layer; it
556
- is how react-x11 proves the transport.
511
+ bus at all (`test/a11y.test.js`). Applications rarely need this layer.
557
512
 
558
513
  ### Seeing it without learning a screen reader
559
514
 
560
- `npm run a11y:probe` is the client side of AT-SPI — the side Orca is on —
561
- with no GNOME libraries and nothing to configure:
515
+ `npm run a11y:probe`, from a checkout of the react-x11 repository, is the
516
+ client side of AT-SPI — the side Orca is on — with no GNOME libraries and
517
+ nothing to configure:
562
518
 
563
519
  ```bash
564
520
  npm run a11y:probe # what is on the accessibility bus
@@ -568,12 +524,10 @@ npm run a11y:probe -- widgets --watch --speak # ...said out loud
568
524
  npm run a11y:probe -- nautilus # someone else's toolkit, for comparison
569
525
  ```
570
526
 
571
- It talks to whatever is on the bus, so pointing it at a GTK app and at
572
- yours is the sharpest check available: if the shapes match, a screen reader
573
- will treat them the same. The `say:` lines it prints are a **toy** model —
574
- what Orca really utters is Orca's own policy — but they make a missing
575
- accessible name audible instead of merely absent, which is the failure that
576
- matters most and is easiest to miss.
527
+ Point it at a GTK app and at yours: if the shapes match, a screen reader
528
+ will treat them the same. The `say:` lines it prints are a **toy** model,
529
+ not what Orca utters, but they make a missing accessible name audible
530
+ instead of merely absent.
577
531
 
578
532
  ### With a real screen reader
579
533
 
@@ -601,28 +555,21 @@ app = next(a for a in apps if a and a.get_name() == 'myapp')
601
555
  # walk it, read roles/names/states, do_action(0), set values…
602
556
  ```
603
557
 
604
- Accerciser gives the same view interactively, and running `orca` while
605
- Tab-walking your app is the ground truth. Under `react-x11/test`,
606
- remember, the bridge is off by default (`NO_AT_BRIDGE`) so tests stay
607
- deterministic.
558
+ Under `react-x11/test` the bridge is off by default (`NO_AT_BRIDGE`), so
559
+ tests stay deterministic.
608
560
 
609
561
  ## What is deliberately not there (yet)
610
562
 
611
- - **Relations** (`aria-labelledby`, label-for) — needs an id registry;
612
- `aria-label` and name-from-contents cover the widgets.
613
- - **The Selection interface** on containers — Orca reads per-item
614
- SELECTED states and selection is driven through actions and focus, so
615
- this is polish, not a gap in coverage.
616
- - **The Table interface** — `Table` exposes honest `table`/`row`/`cell`
617
- roles with positions, but not the row/column navigation calls; note that
618
- virtualization means only rendered rows exist in the accessible tree,
619
- which is also all a sighted user can see.
563
+ - **Relations** (`aria-labelledby`, label-for) — use `aria-label` or
564
+ name-from-contents.
565
+ - **The Selection interface** on containers — per-item SELECTED states are
566
+ exposed, and selection is driven through actions and focus.
567
+ - **The Table interface** — `Table` exposes `table`/`row`/`cell` roles with
568
+ positions, but not the row/column navigation calls; only rendered rows
569
+ exist in the accessible tree.
620
570
  - **Key-event forwarding** (`DeviceEventController.NotifyListenersSync`) —
621
- Orca's per-keystroke echo of _keys themselves_; typed characters already
622
- arrive through text-changed events.
571
+ typed characters arrive through text-changed events.
623
572
  - **Soft-wrap line granularity** in `<textarea>`, per above.
624
573
  - **An input method** — dead keys and Compose are the whole composition
625
- story today, so a script that needs a candidate window (any CJK) cannot
626
- be typed at all, accessibly or otherwise. What an IME would report is
627
- already here: the preedit is a text run and its own `:system` feed, so
628
- the work left is the input method, not its accessibility.
574
+ story, so a script that needs a candidate window (any CJK) cannot be
575
+ typed.