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.
- package/README.md +10 -10
- package/docs/README.md +160 -314
- package/docs/accessibility.md +176 -229
- package/docs/appearance.md +77 -176
- package/docs/click-to-component.md +18 -51
- package/docs/clipboard.md +40 -59
- package/docs/components.md +518 -1036
- package/docs/dbus.md +61 -133
- package/docs/debugging.md +152 -245
- package/docs/desktop-calendar.md +51 -88
- package/docs/desktop.md +222 -347
- package/docs/devtools.md +50 -167
- package/docs/drag-and-drop.md +126 -204
- package/docs/ecosystem/animation.md +91 -125
- package/docs/ecosystem/data-fetching.md +38 -53
- package/docs/ecosystem/dev-tooling.md +159 -202
- package/docs/ecosystem/forms.md +61 -97
- package/docs/ecosystem/headless.md +64 -89
- package/docs/ecosystem/i18n.md +18 -21
- package/docs/ecosystem/icons.md +93 -126
- package/docs/ecosystem/layout.md +42 -52
- package/docs/ecosystem/routing.md +18 -29
- package/docs/ecosystem/state.md +71 -88
- package/docs/ecosystem/testing.md +131 -157
- package/docs/ecosystem/theming.md +119 -165
- package/docs/ecosystem.md +145 -179
- package/docs/elements.md +1091 -1774
- package/docs/embedding.md +83 -132
- package/docs/events.md +412 -695
- package/docs/extending.md +784 -1326
- package/docs/eyedropper.md +63 -122
- package/docs/filedialog.md +71 -107
- package/docs/frame.md +195 -339
- package/docs/gl.md +65 -62
- package/docs/globalmenu.md +63 -146
- package/docs/macos.md +531 -3234
- package/docs/notifications.md +33 -47
- package/docs/packaging.md +166 -309
- package/docs/permissions.md +9 -14
- package/docs/react-features.md +81 -106
- package/docs/remote.md +32 -76
- package/docs/scale.md +72 -186
- package/docs/styling.md +329 -547
- package/docs/system.md +200 -280
- package/docs/testing.md +63 -83
- package/docs/typescript.md +6 -48
- package/docs/uri-schemes.md +67 -107
- package/docs/wayland-backend.md +200 -459
- package/docs/windows-integrations.md +75 -131
- package/docs/windows.md +343 -1367
- package/package.json +3 -2
- package/src/Reconciler.js +2 -2
- package/src/a11y.js +1 -1
- package/src/backend/context2d.js +3 -3
- package/src/bootstrap.js +1 -1
- package/src/cocoa/app.js +6 -6
- package/src/cocoa/bezels.js +1 -1
- package/src/cocoa/fonts.js +1 -1
- package/src/cocoa/globalmenu.js +1 -1
- package/src/cocoa/keymap.js +1 -1
- package/src/cocoa/panewindow.js +1 -1
- package/src/cocoa/player.js +1 -1
- package/src/cocoa/presenter.js +2 -2
- package/src/cocoa/promotion.js +1 -1
- package/src/cocoa/relaunch.js +1 -1
- package/src/cocoa/threaded.js +1 -1
- package/src/cocoa/video.js +1 -1
- package/src/cocoa/window.js +4 -4
- package/src/components/Menu.js +7 -2
- package/src/components/Switch.js +1 -1
- package/src/components/native.js +1 -1
- package/src/dnd.js +2 -2
- package/src/glnodes.js +1 -1
- package/src/grid.js +1 -1
- package/src/layouts.js +1 -1
- package/src/nodes/animation.js +1 -1
- package/src/nodes/invalidate.js +1 -1
- package/src/nodes/layouthost.js +1 -1
- package/src/nodes/video.js +19 -8
- package/src/nodes/window/droptarget.js +1 -1
- package/src/pacing.js +1 -1
- package/src/refresh/index.js +2 -1
- package/src/types/video.d.ts +5 -2
- package/src/videobackend.js +28 -0
- package/src/videoframes.js +29 -16
- package/src/wayland/app.js +27 -0
- package/src/wayland/video.js +376 -0
- package/src/win32/a11y.js +1 -1
- package/src/win32/app.js +2 -2
- package/src/win32/fonts.js +1 -1
- package/src/win32/glarea.js +4 -4
- package/src/win32/overlay.js +1 -1
- package/src/win32/panewindow.js +3 -3
- package/src/win32/shell.js +1 -1
- package/src/win32/window.js +2 -2
- package/src/windowid.js +1 -1
- package/src/xvideo.js +189 -0
- package/docs/architecture/accessibility.md +0 -470
- package/docs/architecture/animation.md +0 -707
- package/docs/architecture/container-queries.md +0 -525
- package/docs/architecture/custom-layout.md +0 -334
- package/docs/architecture/drag-and-drop.md +0 -941
- package/docs/architecture/element-layer-contents.md +0 -622
- package/docs/architecture/frame-pacing.md +0 -255
- package/docs/architecture/grid-layout.md +0 -358
- package/docs/architecture/protocol-efficiency.md +0 -357
- package/docs/architecture/video.md +0 -1149
- package/docs/architecture/windowed-regions.md +0 -854
- package/docs/glx.md +0 -137
- package/docs/wayland.md +0 -545
- package/docs/windows-embedding.md +0 -271
- package/docs/windows-gl.md +0 -221
package/docs/accessibility.md
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
>
|
|
17
|
-
>
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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;
|
|
51
|
-
|
|
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
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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`
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
144
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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.
|
|
182
|
-
tracking — an explicit call
|
|
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
|
|
198
|
-
| `Select` | combo box named by the current choice, expanded/collapsed; its menu is a listbox
|
|
199
|
-
| `MenuBar` / `ContextMenu` | menu bar and menus
|
|
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)
|
|
204
|
-
| `SplitPane` | the divider is a separator with orientation and position
|
|
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
|
|
210
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
`
|
|
243
|
-
|
|
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
|
|
251
|
-
|
|
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
|
|
255
|
-
|
|
256
|
-
|
|
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,
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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,
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
357
|
-
`
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
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
|
|
366
|
-
|
|
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
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
405
|
-
|
|
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
|
|
392
|
+
[dbus.md](dbus.md): the app stops being accessible until restarted.
|
|
419
393
|
|
|
420
394
|
## How often the bridge speaks
|
|
421
395
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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`
|
|
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
|
|
543
|
-
this
|
|
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
|
|
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
|
|
561
|
-
with no GNOME libraries and
|
|
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
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
|
|
605
|
-
|
|
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) —
|
|
612
|
-
|
|
613
|
-
- **The Selection interface** on containers —
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
-
|
|
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
|
|
626
|
-
|
|
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.
|