react-x11 2.51.1 → 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 +94 -61
- package/docs/README.md +256 -0
- package/docs/accessibility.md +575 -0
- package/docs/appearance.md +317 -0
- package/docs/click-to-component.md +92 -0
- package/docs/clipboard.md +262 -0
- package/docs/components.md +1231 -0
- package/docs/dbus.md +205 -0
- package/docs/debugging.md +371 -0
- package/docs/desktop-calendar.md +247 -0
- package/docs/desktop.md +523 -0
- package/docs/devtools.md +133 -0
- package/docs/drag-and-drop.md +585 -0
- package/docs/ecosystem/animation.md +360 -0
- package/docs/ecosystem/data-fetching.md +186 -0
- package/docs/ecosystem/dev-tooling.md +393 -0
- package/docs/ecosystem/forms.md +277 -0
- package/docs/ecosystem/headless.md +194 -0
- package/docs/ecosystem/i18n.md +86 -0
- package/docs/ecosystem/icons.md +377 -0
- package/docs/ecosystem/layout.md +206 -0
- package/docs/ecosystem/routing.md +135 -0
- package/docs/ecosystem/state.md +344 -0
- package/docs/ecosystem/testing.md +474 -0
- package/docs/ecosystem/theming.md +494 -0
- package/docs/ecosystem.md +439 -0
- package/docs/elements.md +2079 -0
- package/docs/embedding.md +239 -0
- package/docs/events.md +742 -0
- package/docs/extending.md +1700 -0
- package/docs/eyedropper.md +164 -0
- package/docs/filedialog.md +222 -0
- package/docs/frame.md +436 -0
- package/docs/gl.md +173 -0
- package/docs/globalmenu.md +195 -0
- package/docs/macos.md +592 -0
- package/docs/notifications.md +150 -0
- package/docs/packaging.md +468 -0
- package/docs/permissions.md +143 -0
- package/docs/react-features.md +405 -0
- package/docs/remote.md +179 -0
- package/docs/scale.md +225 -0
- package/docs/security.md +132 -0
- package/docs/styling.md +1710 -0
- package/docs/system.md +519 -0
- package/docs/testing.md +494 -0
- package/docs/typescript.md +75 -0
- package/docs/uri-schemes.md +340 -0
- package/docs/wayland-backend.md +332 -0
- package/docs/windows-integrations.md +177 -0
- package/docs/windows.md +380 -0
- package/package.json +11 -3
- 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/bus.js +39 -26
- package/src/cli/create.d.ts +43 -0
- package/src/cli/create.js +305 -0
- package/src/cli/env.js +484 -0
- package/src/cli/index.js +29 -2
- package/src/cli/templates/AGENTS.template.md +195 -0
- package/src/cli/templates/DESIGN.template.md +269 -0
- 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/desktopintegration.js +2 -1
- package/src/desktopsettings.js +193 -24
- package/src/dnd.js +2 -2
- package/src/filedialog.js +3 -3
- package/src/glnodes.js +1 -1
- package/src/grid.js +1 -1
- package/src/keyboardstate.js +28 -0
- package/src/layouts.js +1 -1
- package/src/node.d.ts +10 -10
- 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/portal.js +31 -0
- package/src/refresh/index.js +2 -1
- package/src/screencolor.js +5 -10
- package/src/types/system.d.ts +8 -6
- 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 +86 -0
- package/src/wayland/clipboard.js +14 -7
- package/src/wayland/connection.js +3 -0
- package/src/wayland/context2d.js +129 -10
- package/src/wayland/protocols/index.json +6 -0
- package/src/wayland/protocols/xdg-foreign-unstable-v2.json +1 -0
- package/src/wayland/seat.js +28 -1
- package/src/wayland/swapchain.js +23 -0
- package/src/wayland/video.js +376 -0
- package/src/wayland/xkb.js +137 -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/macos.md
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
# A native macOS backend
|
|
2
|
+
|
|
3
|
+
On macOS `createRoot()` renders through **Cocoa** by default: real
|
|
4
|
+
`NSWindow`s, Core Animation compositing, the system menu bar and native
|
|
5
|
+
control bezels, with no X server anywhere. The tree, components, styles and
|
|
6
|
+
hooks are the same; this page is what an app sees differently here, and what
|
|
7
|
+
it does not have yet. The design record — presenters, threads, the bridge
|
|
8
|
+
contract, measurements, the plan — is [design/macos.md](../design/macos.md).
|
|
9
|
+
|
|
10
|
+
## Choosing the backend
|
|
11
|
+
|
|
12
|
+
`createRoot({ backend })`:
|
|
13
|
+
|
|
14
|
+
- `'auto'` (the default) — Cocoa on macOS where the bridge is installed, X11
|
|
15
|
+
through `$DISPLAY` (XQuartz) where it is not.
|
|
16
|
+
- `'cocoa'` — Cocoa, with the bridge **required**.
|
|
17
|
+
- `'x11'` — X11 through XQuartz, still the way to run X-only features
|
|
18
|
+
(`<foreign>`, `examples/wm.jsx`) on a Mac.
|
|
19
|
+
|
|
20
|
+
`REACT_X11_BACKEND=x11` or `=cocoa` overrides both. `createRoot({ app })`
|
|
21
|
+
ignores `backend`: a connection already is one.
|
|
22
|
+
|
|
23
|
+
The bridge is **[`@windowkit/appkit`](https://www.npmjs.com/package/@windowkit/appkit)**,
|
|
24
|
+
an `optionalDependency` with N-API prebuilds for arm64 and x64, so it works
|
|
25
|
+
under Node and Bun. An install that skips optional dependencies has no
|
|
26
|
+
bridge, and `'auto'` falls back to X11. The range react-x11 asks for has
|
|
27
|
+
everything on this page; where an older bridge lacks a verb, the feature is
|
|
28
|
+
detected and degrades as described below.
|
|
29
|
+
|
|
30
|
+
## Root options and environment
|
|
31
|
+
|
|
32
|
+
`createRoot({ cocoa: { … } })`, ignored off macOS and when `app` is passed:
|
|
33
|
+
|
|
34
|
+
- `presenter` — `'surface'` (the default: one bitmap per window) or
|
|
35
|
+
`'layers'` (one CALayer per drawn node, opt-in while it is measured).
|
|
36
|
+
`REACT_X11_COCOA_PRESENTER` says the same.
|
|
37
|
+
- `promote` — [layer promotion](#layer-promotion-the-animated-few-on-their-own-layers-above-the-surface-presenter)
|
|
38
|
+
of animated boxes; `false` keeps every animation on the frame clock.
|
|
39
|
+
`REACT_X11_COCOA_PROMOTE=1`/`=0` says the same.
|
|
40
|
+
- `frameInterval` — ms between scheduled frames. By default each window
|
|
41
|
+
paces itself on its own display (8.3 ms at 120 Hz, 16.7 at 60); a number
|
|
42
|
+
applies to every window.
|
|
43
|
+
- `pumpInterval` — ms between AppKit event pumps (8) when the app's JS runs
|
|
44
|
+
on the main thread: the floor under input latency there. A
|
|
45
|
+
[worker](#js-on-a-worker-a-ui-thread-of-the-bridges-own) has no pump.
|
|
46
|
+
- `resizeWait` — ms AppKit may hold a live-resize tick for the app's frame at
|
|
47
|
+
the new size, on a worker (50; `0` lets the edge move without waiting).
|
|
48
|
+
- `paneStretch` — when a `<Frame>` pane's last frame is stretched while its
|
|
49
|
+
next one comes: `'behind'` (the default: once it is later than
|
|
50
|
+
`resizeWait`), `'never'` or `'always'`. `REACT_X11_COCOA_PANE_STRETCH` says
|
|
51
|
+
the same.
|
|
52
|
+
- `screenPoll` — ms between re-reads of the screen layout while a
|
|
53
|
+
`useScreens()` subscriber is mounted (500; `0` for never).
|
|
54
|
+
- `appName` — what the Dock, ⌘-Tab and the app menu print for an unbundled
|
|
55
|
+
process; a bundle's `Info.plist` wins.
|
|
56
|
+
- `activationPolicy` — `'regular'` (the default: a Dock tile and a ⌘-Tab
|
|
57
|
+
entry), `'accessory'` (a menu-bar app: windows, no tile) or `'prohibited'`.
|
|
58
|
+
On a worker, set it [at launch](#js-on-a-worker-a-ui-thread-of-the-bridges-own).
|
|
59
|
+
- `exitOnQuit` — end the process once a quit request (the Dock's Quit, ⌘Q, a
|
|
60
|
+
logout) has closed the app (`true`); `false` is for an embedder that owns
|
|
61
|
+
the process's lifetime.
|
|
62
|
+
|
|
63
|
+
Other variables: `REACT_X11_THREADED=0` (keep the JS on the main thread),
|
|
64
|
+
`APPKIT_ACTIVATION_POLICY` (the policy AppKit launches with),
|
|
65
|
+
`REACT_X11_CALAYERS_PATH` (where the bridge's addon is, for a
|
|
66
|
+
[compiled binary](#running-as-an-app-bundle)), and
|
|
67
|
+
[`REACT_X11_NO_STROKE_CHUNKING=1`](#stroking-a-path-with-many-subpaths) and
|
|
68
|
+
[`REACT_X11_NO_SHADOW_TILES=1`](#a-shadow-from-a-tile).
|
|
69
|
+
|
|
70
|
+
The `desktop` option defaults to `{ a11y: false, globalMenu: false }` here:
|
|
71
|
+
AT-SPI and the D-Bus menu export have nothing to register against on a Mac.
|
|
72
|
+
The menu bar is [the system's own](#menus-the-global-menu-finally-at-home)
|
|
73
|
+
regardless.
|
|
74
|
+
|
|
75
|
+
## Windowing semantics: what maps, what bends, what breaks
|
|
76
|
+
|
|
77
|
+
macOS has no separate window manager, so what one does is where the backends
|
|
78
|
+
part company. The per-prop list is
|
|
79
|
+
[elements.md](elements.md#on-the-cocoa-backend); in short:
|
|
80
|
+
|
|
81
|
+
- **Maps**: `<window>` is an `NSWindow` that places itself (`x`/`y` work),
|
|
82
|
+
min/max sizes are its own, and its size is a whole number of points,
|
|
83
|
+
rounded up. `resizable` and `decorations` are read once, at creation.
|
|
84
|
+
`fullscreen`, `maximized`, `hidden` and `demands_attention` are the
|
|
85
|
+
full-screen Space, the zoom, the Dock and the Dock bounce.
|
|
86
|
+
- **Better**: `transparent` and a window's `borderRadius` always work —
|
|
87
|
+
every window composites, so `useSupports('transparency')` is always true.
|
|
88
|
+
- **Inert**: the `sticky`, `below`, `skip_taskbar` and `shaded` states;
|
|
89
|
+
`appId`, `windowType`, `gravity` and the increment and aspect hints (bundle
|
|
90
|
+
identity lives in `Info.plist`); `transientFor`, for now — a managed
|
|
91
|
+
`<Dialog>` floats on its own window anyway. `onClientMessage` never fires.
|
|
92
|
+
- **`<popup>`** is a borderless panel at the popup level, placed by the same
|
|
93
|
+
anchor math. `grab` bends: there are no pointer grabs, so a press in
|
|
94
|
+
another of the app's windows reaches the popup as a press outside it,
|
|
95
|
+
which dismisses it. `grabKeyboard` makes a key-capable window, as
|
|
96
|
+
`NSPopover` does, which closes on `onBlur` ([elements.md](elements.md)).
|
|
97
|
+
- **`<glarea>`** is a CGL context rendering into IOSurfaces on a sublayer
|
|
98
|
+
([gl.md](gl.md)); **`<Frame>`** works through shared surfaces rather than
|
|
99
|
+
reparenting ([frame.md](frame.md)).
|
|
100
|
+
- **Screens** (`useScreens`) map, but macOS sends a client no event for a
|
|
101
|
+
display change, so the layout is re-read on a clock while watched
|
|
102
|
+
([system.md](system.md#on-macos-the-layout-is-asked-for-not-announced)).
|
|
103
|
+
`app.scale` is the screen's `backingScaleFactor`, read once, at startup.
|
|
104
|
+
- **Not yet**: nested `<window>` elements throw.
|
|
105
|
+
- **Gone by design**: `<foreign>` and XEmbed, a window manager
|
|
106
|
+
(`examples/wm.jsx`), `ssh -X`. A `<foreign>` reports one `onError` and
|
|
107
|
+
draws an empty box; `useSupports('embedding')` is false.
|
|
108
|
+
|
|
109
|
+
**Inert means inert, not fatal, and silent**: a prop with no meaning here is
|
|
110
|
+
accepted and does nothing, so the same JSX runs on every backend and shared
|
|
111
|
+
application code stays branch-free.
|
|
112
|
+
|
|
113
|
+
## Desktop integrations: what an app does outside its own windows
|
|
114
|
+
|
|
115
|
+
Most of these are [filedialog.md](filedialog.md)'s ladder: the platform's
|
|
116
|
+
mechanism, a shell-out beneath it, and a typed "cannot do this here" at the
|
|
117
|
+
floor.
|
|
118
|
+
|
|
119
|
+
- **File open/save**: `NSOpenPanel`/`NSSavePanel`, a sheet on the window that
|
|
120
|
+
asked ([filedialog.md](filedialog.md)).
|
|
121
|
+
- **Raise**: `activateWindow()` activates the app and orders the window front.
|
|
122
|
+
- **Deep links, documents, reopen**: Apple Events into
|
|
123
|
+
`useAppOpen`/`useAppActivate`; Quit is the primary window's close request;
|
|
124
|
+
single instance is the platform default
|
|
125
|
+
([uri-schemes.md](uri-schemes.md#on-macos)).
|
|
126
|
+
- **Drag and drop with other apps**: AppKit's dragging behind the same
|
|
127
|
+
`dropAccept`/`onDrag*`/`dragData` props
|
|
128
|
+
([drag-and-drop.md](drag-and-drop.md#interoperating-with-other-applications)).
|
|
129
|
+
- **Clipboard**: `NSPasteboard.general` as `CLIPBOARD`; `watch()` polls.
|
|
130
|
+
No `PRIMARY`: selecting copies nothing ([clipboard.md](clipboard.md)).
|
|
131
|
+
- **Tray**: `useTray()` over `NSStatusItem`, icon names as SF Symbols
|
|
132
|
+
([desktop.md](desktop.md#the-tray)).
|
|
133
|
+
- **Dock**: `setBadge`/`useBadge`, the `demands_attention` bounce,
|
|
134
|
+
`useLauncherMenu`; the name and activation policy are the `cocoa` options
|
|
135
|
+
([desktop.md](desktop.md#the-launcher-a-badge-attention-and-the-dock-menu)).
|
|
136
|
+
- **Permissions**: `usePermission()`/`requestPermission()` over TCC, and
|
|
137
|
+
`openPrivacySettings`
|
|
138
|
+
([permissions.md](permissions.md#what-each-kind-actually-does-on-macos)).
|
|
139
|
+
- **Notifications**: `notify()` over `UNUserNotificationCenter`, only in a
|
|
140
|
+
**code-signed app bundle**; `osascript` banners otherwise
|
|
141
|
+
([notifications.md](notifications.md)).
|
|
142
|
+
- **Calendar**: `useDesktopCalendarEvents()` over EventKit
|
|
143
|
+
([desktop-calendar.md](desktop-calendar.md)).
|
|
144
|
+
- **Eyedropper**: `pickScreenColor()` over `NSColorSampler`; the system draws
|
|
145
|
+
the loupe, so no Screen Recording grant is needed. A bridge older than 0.9
|
|
146
|
+
has no rung, and `useEyedropper().supported` is false
|
|
147
|
+
([eyedropper.md](eyedropper.md)).
|
|
148
|
+
- **Idle time**: `useIdle()`, from the bridge or `ioreg`
|
|
149
|
+
([system.md](system.md#useidle-and-usekeepawake--is-anyone-there)).
|
|
150
|
+
- **Appearance**: dark or light, the accent, and the desktop's whole palette
|
|
151
|
+
([appearance.md](appearance.md#the-desktops-palette)).
|
|
152
|
+
- **Keep-awake**: no macOS rung — `useKeepAwake()` holds nothing here.
|
|
153
|
+
- **Accessibility**: none yet ([below](#not-supported-yet)).
|
|
154
|
+
|
|
155
|
+
## Text
|
|
156
|
+
|
|
157
|
+
Text goes through CoreText behind the same API: the same `<text>` measures,
|
|
158
|
+
wraps, elides, selects and hit-tests on both backends, with the platform's
|
|
159
|
+
metrics, which differ from X11's. Layout can wrap differently across
|
|
160
|
+
backends; compare structure and behaviour across them, not pixels.
|
|
161
|
+
|
|
162
|
+
- **Fonts are the system's.** The CSS generic families map to system faces
|
|
163
|
+
(`sans-serif` is San Francisco), `opsz` is CoreText's own optical sizing,
|
|
164
|
+
and `-apple-system` and `BlinkMacSystemFont` read as `system-ui`, so a font
|
|
165
|
+
stack written for the web sets in SF. Labels and emoji measure as AppKit
|
|
166
|
+
sets them.
|
|
167
|
+
- **OpenType features** (`fontVariantNumeric`, `fontFeatureSettings`) and
|
|
168
|
+
`letterSpacing` work; `letterSpacing` turns optional ligatures off, as on
|
|
169
|
+
X11. A bridge too old for one draws the plain face and says so once in
|
|
170
|
+
development.
|
|
171
|
+
- **Lines break** at the same UAX#14 opportunities as on X11, so a `<text>`
|
|
172
|
+
in a flex item shrinks to the same min-content width; one that elides
|
|
173
|
+
(`textOverflow: 'ellipsis'`) floors at its `…`. An empty paragraph is one
|
|
174
|
+
empty line, so an empty field's caret has a line's height.
|
|
175
|
+
- **Antialiasing is greyscale**, as macOS has drawn it since Mojave.
|
|
176
|
+
- **Glyph-level drawing** (`drawGlyphs`, `positioned`, a face's
|
|
177
|
+
`glyphIdFor`/`advanceOf`/`shape`/`metrics`, `fallbackFor`) answers ntk's
|
|
178
|
+
contract, so a renderer written against ntk's context runs unchanged.
|
|
179
|
+
Three differences: `shape()` substitutes fallback faces, so an emoji in
|
|
180
|
+
Menlo comes out as the emoji where ntk shapes `.notdef`; every op draws as
|
|
181
|
+
`Over`; and a transform scales glyphs as well as their origins.
|
|
182
|
+
- **`coverage()`** is the signed-area fill X11 uses, so both give the same
|
|
183
|
+
bytes for the same outlines; colour emoji come out as silhouettes. A bridge
|
|
184
|
+
without the verb answers `null`.
|
|
185
|
+
- **No input method yet**: dead keys, Press-and-Hold accents and CJK input
|
|
186
|
+
wait on `NSTextInputClient` ([events.md](events.md#input-methods) says what
|
|
187
|
+
a dead key types meanwhile).
|
|
188
|
+
|
|
189
|
+
## Input and the run loop
|
|
190
|
+
|
|
191
|
+
- **Pointer**: top-left coordinates, `clickCount` as `detail`, and a
|
|
192
|
+
trackpad's precise deltas as the wheel's `smooth` flag.
|
|
193
|
+
- **Hover belongs to the active window**: a background window gets no
|
|
194
|
+
`:hover`, enter, leave or motion until it is active, the platform's
|
|
195
|
+
convention ([events.md](events.md#when-the-response-is-painted)). A first
|
|
196
|
+
click on a background window still acts, and the wheel still scrolls it.
|
|
197
|
+
- **Modifiers**: ⌘ is `metaKey`, ⌥ is `altKey`. ⌘ is the app's primary
|
|
198
|
+
modifier (`app.primaryModifier` is `'Super'`), so the text controls answer
|
|
199
|
+
⌘Z/⌘C/⌘V, their edit menu prints ⌘C, and a chord written with `Control`
|
|
200
|
+
means ⌘ ([events.md](events.md#primary-modifier)).
|
|
201
|
+
- **Keys** arrive as the same `XK_*` keysyms as on X11, and shortcuts keep
|
|
202
|
+
their Latin letters under a non-Latin layout. The lock state is readable
|
|
203
|
+
before the first key.
|
|
204
|
+
- **Frames** are paced per window on its own display. A window that is
|
|
205
|
+
ordered out, miniaturized or entirely covered by another app's window
|
|
206
|
+
paints nothing — its frames and `requestAnimationFrame` callbacks wait —
|
|
207
|
+
and gets one catch-up frame when it is back. A window fed off a stream (a
|
|
208
|
+
terminal under `cat`) paints every refresh unless it sets
|
|
209
|
+
`frameRate: 'adaptive'`
|
|
210
|
+
([elements.md](elements.md#framerate--pacing-the-frames-under-a-flood)).
|
|
211
|
+
|
|
212
|
+
## JS on a worker: a UI thread of the bridge's own
|
|
213
|
+
|
|
214
|
+
AppKit owns the process main thread and runs its menus, drags, live resizes
|
|
215
|
+
and modal panels there. So on macOS react-x11 **moves the app's JS onto a
|
|
216
|
+
`worker_threads` Worker** by default, and the main thread sits in a real
|
|
217
|
+
`[NSApp run]`: the JS keeps running through every menu and resize, and input
|
|
218
|
+
reaches it as it happens rather than at the next pump. It needs a bridge with
|
|
219
|
+
`runMain()` (`@windowkit/appkit` 0.10).
|
|
220
|
+
|
|
221
|
+
`node app.js` and `bun app.jsx` move with nothing asked for, and so does
|
|
222
|
+
`tsx app.jsx` on Node 24 and later. The launcher moves the app before its
|
|
223
|
+
entry is loaded at all, which is cheaper:
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
node --import react-x11/cocoa-main app.js
|
|
227
|
+
bun --preload react-x11/cocoa-main app.jsx
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The app **stays on the main thread**, with the pump, off macOS; with
|
|
231
|
+
`REACT_X11_THREADED=0` or `REACT_X11_BACKEND=x11`; in a REPL or under `-e`;
|
|
232
|
+
in a single executable (a Node SEA, `bun build --compile`); in a `<Frame>`
|
|
233
|
+
pane; under a test runner; when react-x11 is imported after the app started
|
|
234
|
+
running (an `import()` the entry awaits, or one from a timer); and on Node 20
|
|
235
|
+
and 22 under an ES module loader (`--import`, `--loader`, tsx). There a menu,
|
|
236
|
+
a drag, a live resize or a modal panel stops the JS while it is open. The
|
|
237
|
+
startup cost of each route and the matrix by Node version are in
|
|
238
|
+
[packaging.md](packaging.md#on-macos-the-app-moves-onto-a-worker).
|
|
239
|
+
|
|
240
|
+
**A menu-bar app sets its policy at launch.** On a worker AppKit is up before
|
|
241
|
+
the app's code runs, so `cocoa: { activationPolicy }` can only switch it
|
|
242
|
+
afterwards, when a Regular launch has already shown a Dock tile. Launch with
|
|
243
|
+
the variable, or put `LSUIElement` in the bundle's `Info.plist`; an app whose
|
|
244
|
+
option disagrees with its launch is told so in development.
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
APPKIT_ACTIVATION_POLICY=accessory node --import react-x11/cocoa-main app.js
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### What changes for an app
|
|
251
|
+
|
|
252
|
+
- About **16 MB** of RSS for the worker's isolate, and **26–31 ms** before
|
|
253
|
+
the worker runs its first line.
|
|
254
|
+
- The idle app stops waking 125 times a second: the pump is an 8 ms interval
|
|
255
|
+
whether anything happens or not.
|
|
256
|
+
- Inside a Worker, `process.chdir()` throws, `process.on('SIGINT')` is never
|
|
257
|
+
called (the launcher forwards signals: a signal is re-emitted on the
|
|
258
|
+
worker's `process`, and one nobody listens for exits with 128 + n),
|
|
259
|
+
`process.env` is a copy unless it is shared, and `process.stdin` forwards
|
|
260
|
+
through the main thread the way stdout does.
|
|
261
|
+
- **What the entry imports ahead of react-x11 runs twice**: on the main
|
|
262
|
+
thread, then on the worker. A module that does work as it loads — opens a
|
|
263
|
+
port, writes a file, logs — does it twice. A call only a main thread may
|
|
264
|
+
make — `process.chdir()`, `process.umask(mask)` — goes through the first
|
|
265
|
+
time and throws the second, and the app stops with that error and a word
|
|
266
|
+
on why. Make it on the main thread only (`isMainThread`, from
|
|
267
|
+
`node:worker_threads`): what it changes is the process's, so the worker
|
|
268
|
+
starts with it. One made after the import runs on the worker alone and
|
|
269
|
+
throws there. Import react-x11 first in the entry, or set
|
|
270
|
+
`REACT_X11_THREADED=0` to keep the whole app on the main thread.
|
|
271
|
+
- `process.exit()` and an uncaught error end the app as they would a process
|
|
272
|
+
of its own: exit handlers run, and the error is printed with exit code 1.
|
|
273
|
+
- `--inspect` attaches to the main thread; the app is the worker target
|
|
274
|
+
beside it.
|
|
275
|
+
- A single executable does not move, and runs on the pump
|
|
276
|
+
([packaging.md](packaging.md)).
|
|
277
|
+
|
|
278
|
+
Not done yet on a worker:
|
|
279
|
+
|
|
280
|
+
- A dropped image, or any binary type, reads as nothing: the drop carries its
|
|
281
|
+
text and URL forms.
|
|
282
|
+
- The layer presenter's colour transitions start from the declared value.
|
|
283
|
+
- A transparent window's shadow is recomputed a frame interval after a flip,
|
|
284
|
+
not once the flip has committed.
|
|
285
|
+
- A worker that cannot load the entry stops the app: under tsx before 4.21.1
|
|
286
|
+
on Node 24 and later, or tsx on Node 20 and 22 under the launcher, it stops
|
|
287
|
+
as it starts on `ERR_UNKNOWN_FILE_EXTENSION`. Update tsx, or set
|
|
288
|
+
`REACT_X11_THREADED=0`.
|
|
289
|
+
- Under Bun, react-x11 required from a callback before the entry has
|
|
290
|
+
finished — a `nextTick` or microtask a CommonJS entry queues, a timer while
|
|
291
|
+
an ES module entry awaits at top level — reads as the entry's first run,
|
|
292
|
+
and that code runs again on the worker.
|
|
293
|
+
- Not yet tried on the worker: `<glarea>`'s GL context and
|
|
294
|
+
`react-x11/refresh`'s module hooks.
|
|
295
|
+
|
|
296
|
+
## Native controls
|
|
297
|
+
|
|
298
|
+
`Button`, `Checkbox`, `Radio`, `Switch`, `Slider` and `Select` wear
|
|
299
|
+
**AppKit's own bezels** here by default; interaction, focus, keyboard and
|
|
300
|
+
accessibility stay the shared implementation. A bezel is an image drawn
|
|
301
|
+
through the ordinary paint path, not an embedded `NSView`
|
|
302
|
+
([components.md](components.md#native-bezels)).
|
|
303
|
+
|
|
304
|
+
- `ThemeProvider`'s `controls`: `'auto'` (the default) takes bezels wherever
|
|
305
|
+
the backend has them — Cocoa, and win32 while the desktop is light — and
|
|
306
|
+
draws elsewhere; `'native'` asks outright, and on a backend without them
|
|
307
|
+
warns once and draws; `'drawn'` is this library's rendering everywhere.
|
|
308
|
+
`native={false}` on one control is the escape hatch.
|
|
309
|
+
- `useSupports('nativeControls')` says whether the backend has them.
|
|
310
|
+
- A native control is **two boxes**: the footprint `style` sizes, and the
|
|
311
|
+
control at AppKit's metrics, centred in it. Role, handlers, focus ring and
|
|
312
|
+
ref are on the control, so a press in the slack around it does nothing.
|
|
313
|
+
- A `<Button>` label **wraps** when its row is too narrow, and the control
|
|
314
|
+
grows with it; past one line it is AppKit's flexible push button, whose
|
|
315
|
+
default-button accent is flat. A bridge too old for that bezel keeps the
|
|
316
|
+
push and says so once in development.
|
|
317
|
+
- Text controls stay drawn: a native `NSTextField` would fork the shared
|
|
318
|
+
editing model.
|
|
319
|
+
- Drawn content sits beside the bezels through the desktop's palette
|
|
320
|
+
([appearance.md](appearance.md#the-desktops-palette)).
|
|
321
|
+
|
|
322
|
+
## Menus: the global menu, finally at home
|
|
323
|
+
|
|
324
|
+
**The menu bar is the system's, always.** A window's `menus` go to the macOS
|
|
325
|
+
menu bar as soon as they are installed, the drawn bar never renders, and
|
|
326
|
+
`onGlobalMenuChange(true)` reports it ([globalmenu.md](globalmenu.md)). Item
|
|
327
|
+
icon names are SF Symbols.
|
|
328
|
+
|
|
329
|
+
- An **application menu** goes in front of the app's, carrying Quit (⌘Q).
|
|
330
|
+
Quit is a quit request: the primary window's close request, which
|
|
331
|
+
`onCloseRequest` can intercept, then, with `exitOnQuit`, the end of the
|
|
332
|
+
process. Nothing else is synthesized — no About, Hide, Edit or Window
|
|
333
|
+
menu — so an app that wants them names them in `menus`.
|
|
334
|
+
- **Accelerators**: `Control` in a chord is ⌘, so `[['Control', 'S']]` is ⌘S;
|
|
335
|
+
there is no separate `Primary` token. The menu bar answers the chords it
|
|
336
|
+
can spell — one chord, one character key — and reserves them so no window
|
|
337
|
+
binding fires them twice; the rest (F5, `plus`, a second alternative) are
|
|
338
|
+
matched by the window ([events.md](events.md#the-macos-menu-bar)).
|
|
339
|
+
- Where native controls render, `<ContextMenu>`, a `<MenuBar>`'s dropdowns
|
|
340
|
+
and a `<Select>`'s drawn list are **sized as NSMenu is**: 22pt rows, 5pt
|
|
341
|
+
padding, 11pt separators, the 13pt menu font.
|
|
342
|
+
- **A `<Select>`'s list is the real NSMenu**, dropped from the trigger as a
|
|
343
|
+
browser drops a `<select>`'s; `nativeMenu` chooses
|
|
344
|
+
([components.md](components.md)).
|
|
345
|
+
- The Dock menu and a tray item's menu take the same item vocabulary
|
|
346
|
+
([desktop.md](desktop.md)).
|
|
347
|
+
|
|
348
|
+
## Custom drawing on a layer tree
|
|
349
|
+
|
|
350
|
+
`<canvas onDraw>`, `<svg>` and a registered element's `paint(ctx)` run
|
|
351
|
+
unchanged, through a CoreGraphics context that speaks the whole dialect:
|
|
352
|
+
canvas2d plus `fillRects`, `drawGlyphs`, `positioned`, `layoutSubtree` and
|
|
353
|
+
`vw`/`vh`/`em`.
|
|
354
|
+
|
|
355
|
+
**Offscreen surfaces.** `react-x11/ntk`'s `Surface` is a CoreGraphics bitmap
|
|
356
|
+
here, in device pixels. Unlike ntk:
|
|
357
|
+
|
|
358
|
+
- A surface has **one context for its life**: `getContext('2d')` answers the
|
|
359
|
+
same object every time, its `destroy()` is a no-op, and `render()` brackets
|
|
360
|
+
its callback in `save`/`restore` from the identity.
|
|
361
|
+
- `format: 'a8'` **throws**: allocate `argb32` and tint through
|
|
362
|
+
`fillStyle`/`globalAlpha`.
|
|
363
|
+
- `Picture`, `<image picture>` and `<image drawable>` are X server
|
|
364
|
+
resources: they draw nothing here and warn once in development. An `Image`
|
|
365
|
+
works.
|
|
366
|
+
|
|
367
|
+
**Context properties** that differ or matter here:
|
|
368
|
+
|
|
369
|
+
- **`globalCompositeOperation`** is canvas's whole set, including blend
|
|
370
|
+
modes (`multiply` and the rest) that X11 lacks. An op applies inside what
|
|
371
|
+
the draw covers, as on X11 and unlike a browser's `copy`. A bridge without
|
|
372
|
+
the verb refuses all but `source-over`: assign, then read it back. A
|
|
373
|
+
`drawGlyphs` call's `op` is ignored.
|
|
374
|
+
- **`'copy'` plus `drawImage(surface, x, y)`** at the surface's own size, a
|
|
375
|
+
whole-pixel translate, no alpha, no shadow and at most a rectangular clip
|
|
376
|
+
is a row memcpy, not a blend — the cheap way to present a surface every
|
|
377
|
+
frame (a 125×45 terminal grid in 0.42 ms rather than 1.37). Anything else
|
|
378
|
+
is drawn, with the same result.
|
|
379
|
+
- **`globalAlpha`** below 1 on a surface draw is cheap where
|
|
380
|
+
`ctx.fadesSurfacesCheaply` is true.
|
|
381
|
+
- **`imageSmoothingEnabled`/`imageSmoothingQuality`** are honoured; a context
|
|
382
|
+
starts at `'medium'`, not canvas's `'low'`. `'low'` makes a surface drawn
|
|
383
|
+
tile by tile through a matrix far cheaper (784 tiles: 200 ms → 27 ms).
|
|
384
|
+
- **`filter`** applies the colour functions (`grayscale()`, `sepia()`,
|
|
385
|
+
`saturate()`, `hue-rotate()`, `invert()`, `brightness()`, `contrast()`),
|
|
386
|
+
`opacity()`, and `blur()` on drawn images only; `drop-shadow()` and `url()`
|
|
387
|
+
do not stick. Read `filter` back to know.
|
|
388
|
+
- **`clip(rule)`** honours `'evenodd'`; **`roundRect`** takes canvas's radii,
|
|
389
|
+
`{ x, y }` corners included; **`createPattern`** is not supported.
|
|
390
|
+
|
|
391
|
+
An element can hand parts of its drawing to the render server as `sprites()`
|
|
392
|
+
([extending.md](extending.md#parts-of-your-drawing-on-layers-of-their-own)).
|
|
393
|
+
One that composites its own surface over its box every frame — a terminal, a
|
|
394
|
+
chart on a socket — pays that composite and a copy behind it; a layer of the
|
|
395
|
+
element's own is [designed, not built](../design/architecture/element-layer-contents.md).
|
|
396
|
+
|
|
397
|
+
### Stroking a path with many subpaths
|
|
398
|
+
|
|
399
|
+
CoreGraphics strokes in time quadratic in a path's subpaths, so the context
|
|
400
|
+
splits a large stroke at subpath boundaries: a path of 4,000 rings strokes
|
|
401
|
+
in 113 ms rather than 986. Left whole: a hairline (1 device pixel or less)
|
|
402
|
+
and anything that composites — a translucent `strokeStyle`, `globalAlpha`
|
|
403
|
+
below 1, a shadow. The one visible difference: where two subpaths' strokes
|
|
404
|
+
overlap, the antialiased fringe reads a little lighter.
|
|
405
|
+
`ctx.strokeChunking = false` turns it off for one context,
|
|
406
|
+
`REACT_X11_NO_STROKE_CHUNKING=1` for the process. `fill` is never split.
|
|
407
|
+
|
|
408
|
+
### A shadow from a tile
|
|
409
|
+
|
|
410
|
+
On macOS and Windows the context draws the shadow of a rect, a `roundRect`,
|
|
411
|
+
a `fillRect` or an inset frame of two from a **tile** made once and
|
|
412
|
+
stretched, shared by every card with the same shadow — a page header's inset
|
|
413
|
+
shadow went from 130–380 ms to 4 ms.
|
|
414
|
+
|
|
415
|
+
- A tiled shadow is the canvas spec's gaussian (σ half the blur, three σ
|
|
416
|
+
wide), as Chrome and X11 draw it. Other shapes, rotations and composite
|
|
417
|
+
ops get CoreGraphics' own, which is a little narrower.
|
|
418
|
+
- `<box boxShadow>` draws on macOS and Windows.
|
|
419
|
+
|
|
420
|
+
`REACT_X11_NO_SHADOW_TILES=1`, or `ctx._shadowTiles = false` on one context,
|
|
421
|
+
puts every shadow back on CoreGraphics.
|
|
422
|
+
|
|
423
|
+
## Animations and transforms
|
|
424
|
+
|
|
425
|
+
`transition` and `animation` styles work as everywhere. Where a layer can
|
|
426
|
+
express the property — a plain `<box>`'s `backgroundColor`, `borderColor`,
|
|
427
|
+
`borderWidth`, `borderRadius` and `opacity` — the **render server runs the
|
|
428
|
+
animation**: no JS frames, and it keeps moving while the JS thread is blocked
|
|
429
|
+
(`examples/animation.jsx` blocks it for two seconds). Everything else — text
|
|
430
|
+
colour, layout properties, a node that paints as a raster — runs on the frame
|
|
431
|
+
clock exactly as on X11.
|
|
432
|
+
|
|
433
|
+
- **Timing** matches the frame clock's easings.
|
|
434
|
+
- **Interruption**: a length retargets smoothly from where it is; a colour
|
|
435
|
+
restarts from the value on screen.
|
|
436
|
+
- **Not offloaded**: `boxShadow`. `transform` is not a style property on any
|
|
437
|
+
backend yet.
|
|
438
|
+
|
|
439
|
+
On the default presenter the offload is
|
|
440
|
+
[layer promotion](#layer-promotion-the-animated-few-on-their-own-layers-above-the-surface-presenter).
|
|
441
|
+
`cocoa: { presenter: 'layers' }` gives every drawn node a CALayer instead; it
|
|
442
|
+
is opt-in while it is measured, and a pan there re-rasters the whole scene.
|
|
443
|
+
|
|
444
|
+
## Layer promotion: the animated few on their own layers above the surface presenter
|
|
445
|
+
|
|
446
|
+
The default presenter keeps one bitmap per window and gives the few nodes
|
|
447
|
+
that animate a CALayer of their own above it while they do. Hit testing never
|
|
448
|
+
moves: promotion changes pixels and nothing else. With no prop to set, a node
|
|
449
|
+
is promoted when it is
|
|
450
|
+
|
|
451
|
+
- a plain `<box>` with a transition or a loop on `backgroundColor`,
|
|
452
|
+
`borderColor`, `borderWidth`, `borderRadius` or `opacity` — for as long as
|
|
453
|
+
it has one and a second after; or
|
|
454
|
+
- a box out of the flow that no pointer lands on (`position: 'absolute'`,
|
|
455
|
+
`pointerEvents: 'none'`) with a loop on an inset, which moves it and
|
|
456
|
+
nothing else — `<ProgressBar indeterminate>`'s block costs no frames;
|
|
457
|
+
- and **only where nothing paints over it**: no later sibling's ink in its
|
|
458
|
+
bounds, no ancestor's border or focus ring, no ancestor that fades. A
|
|
459
|
+
clipping ancestor, round corners included, clips the layer. A node in a
|
|
460
|
+
pane that scrolled in the last 250 ms waits until the pane stops.
|
|
461
|
+
|
|
462
|
+
A node that stops qualifying returns to the bitmap in the frame that finds
|
|
463
|
+
it, and its animation carries on on the frame clock from where it was. A
|
|
464
|
+
scrollbar thumb over promoted rows goes on a layer of its own.
|
|
465
|
+
|
|
466
|
+
It costs a frame to start, one to come back a second after the last
|
|
467
|
+
animation ends, and meanwhile a CALayer, plus for a node with children a
|
|
468
|
+
bitmap the size of their reach. `cocoa: { promote: false }` or
|
|
469
|
+
`REACT_X11_COCOA_PROMOTE=0` keeps every animation on the frame clock; `true`
|
|
470
|
+
or `=1` forces it on whatever the bridge (it is on by default where the
|
|
471
|
+
bridge reports sRGB colours, 0.5.1 and later).
|
|
472
|
+
|
|
473
|
+
**On a wide-gamut display** (a MacBook's built-in panel, a monitor with its
|
|
474
|
+
own profile) every window is colour-managed, as a browser manages a page, so
|
|
475
|
+
it looks less saturated than an unmanaged one. Opaque colours agree to the
|
|
476
|
+
pixel between bitmap and layer; translucent ones differ slightly, because
|
|
477
|
+
Core Animation blends in the display's space. `window.snapshot()` reads the
|
|
478
|
+
display's numbers and carries its profile, so a script comparing raw
|
|
479
|
+
snapshot pixels with CSS colours sees a shift.
|
|
480
|
+
|
|
481
|
+
Design notes and measurements:
|
|
482
|
+
[design/macos.md](../design/macos.md#layer-promotion-the-animated-few-on-their-own-layers-above-the-surface-presenter).
|
|
483
|
+
|
|
484
|
+
### Video on a layer
|
|
485
|
+
|
|
486
|
+
A [`<video>`](elements.md#video) goes on a layer of its own under the same
|
|
487
|
+
rule: nothing painted over it, no fade, no rounded clip that cannot hold it.
|
|
488
|
+
|
|
489
|
+
- **`<video frames>`**: lifted, each pushed frame goes straight to a surface
|
|
490
|
+
the render server shows, at no cost to the window; declined, the element
|
|
491
|
+
draws the newest frame into the window. The colours are `AVPlayerLayer`'s.
|
|
492
|
+
Over a bridge without the video-surface verbs the frame is converted in
|
|
493
|
+
JavaScript, and `app.videoFormats()` lists `BGRA` first.
|
|
494
|
+
- **`<video src>`** plays through `AVPlayer`: lifted, its player layer shows
|
|
495
|
+
every frame at no cost to the window; covered, each new frame is copied
|
|
496
|
+
into the window. Over a bridge without the player verbs
|
|
497
|
+
`useSupports('mediaPlayback')` is false and `src` refuses, as on X11. In a
|
|
498
|
+
`<Frame>` pane the host plays the video and sends the frames down.
|
|
499
|
+
|
|
500
|
+
## Running as an app bundle
|
|
501
|
+
|
|
502
|
+
AppKit names, icons and identifies an app by the **bundle its executable
|
|
503
|
+
lives in**. Run from the shell, the menu bar says "node", the Dock shows the
|
|
504
|
+
generic icon, the defaults domain (window frames, the tab bar) is shared with
|
|
505
|
+
every script that runtime ever ran, and the notification centre refuses the
|
|
506
|
+
app. `cocoa: { appName }` fixes the name alone; a bundle fixes all of it.
|
|
507
|
+
`examples/form/` is the example:
|
|
508
|
+
|
|
509
|
+
```sh
|
|
510
|
+
npm run examples:form:app # builds examples/form/build/Guestbook.app and opens it
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
[`make-app.sh`](../examples/form/make-app.sh) makes the executable with
|
|
514
|
+
`bun build --compile` at `Contents/MacOS/Guestbook`: AppKit takes the main
|
|
515
|
+
bundle from the running executable's path, so it must be a real binary, not
|
|
516
|
+
a script.
|
|
517
|
+
|
|
518
|
+
- **The bridge's addon cannot be compiled in.** The bundle carries it as
|
|
519
|
+
`Contents/Resources/calayers.node`, and the entry,
|
|
520
|
+
[`main.js`](../examples/form/main.js), points `REACT_X11_CALAYERS_PATH` at
|
|
521
|
+
it relative to `process.execPath` before importing the app, so the bundle
|
|
522
|
+
can be moved.
|
|
523
|
+
- **`Info.plist`** carries the name, the identifier, the icon,
|
|
524
|
+
`NSHighResolutionCapable` (without it AppKit renders at 1×) and
|
|
525
|
+
`LSEnvironment` naming the Cocoa backend. `lsappinfo` then reports the
|
|
526
|
+
process under the app's name and identifier.
|
|
527
|
+
|
|
528
|
+
**The tab bar under bun.** Unbundled, a `bun` app's windows live in the
|
|
529
|
+
`bun` defaults domain, and once anything turns the tab bar on there (View →
|
|
530
|
+
Show Tab Bar, a stray shortcut) every window the bridge opens wears a 28pt
|
|
531
|
+
tab bar and every popup lands that much off. Clear it, or run as a bundle:
|
|
532
|
+
|
|
533
|
+
```sh
|
|
534
|
+
defaults delete bun "NSWindowTabbingShoudShowTabBarKey-NSWindow-CALBackendDelegate-(null)-VT-FS"
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### Distributing the bundle
|
|
538
|
+
|
|
539
|
+
`make-app.sh` builds a developer's bundle, which runs where it was built;
|
|
540
|
+
[packaging.md](packaging.md#tier-5--a-macos-app) has the recipes. What
|
|
541
|
+
differs on macOS:
|
|
542
|
+
|
|
543
|
+
- **Signing.** A downloaded app needs a Developer ID signature with the
|
|
544
|
+
hardened runtime, then notarization. Sign inside-out, not with `--deep`:
|
|
545
|
+
`calayers.node` first, without entitlements, then the app with Bun's five
|
|
546
|
+
entitlements ([packaging.md](packaging.md#developer-id-or-the-app-store)).
|
|
547
|
+
For a colleague, ad-hoc signing (`--sign -`) and
|
|
548
|
+
`xattr -dr com.apple.quarantine` on their end are the short path.
|
|
549
|
+
- **The Mac App Store** requires App Sandbox, under which a
|
|
550
|
+
`bun build --compile` binary does not start. A Node single executable in
|
|
551
|
+
the same bundle shape does, so that is the store build; Bun is fine for
|
|
552
|
+
Developer ID.
|
|
553
|
+
- **Architecture.** A compiled binary and the bridge's prebuild are each for
|
|
554
|
+
one CPU; a universal bundle is two builds and `lipo`, for the executable
|
|
555
|
+
and `calayers.node` alike.
|
|
556
|
+
- **Size.** About 70 MB with bun, nearly all of it the runtime; 142 MB as a
|
|
557
|
+
Node single executable.
|
|
558
|
+
|
|
559
|
+
## Testing
|
|
560
|
+
|
|
561
|
+
`react-x11/test` drives the **X11 backend on every platform, macOS
|
|
562
|
+
included**; there is no `'cocoa'` backend for `renderX11()`. Test the tree,
|
|
563
|
+
queries and behaviour there, which are shared. Pixels are per-backend
|
|
564
|
+
(CoreText is not ntk's rasterizer), so native bezels, the `NSMenu` and
|
|
565
|
+
pasteboard flavours are checked by running on a Mac
|
|
566
|
+
([testing.md](testing.md)). A window's `snapshot()` resolves to its
|
|
567
|
+
composited pixels, in the display's colour profile.
|
|
568
|
+
|
|
569
|
+
## Not supported yet
|
|
570
|
+
|
|
571
|
+
- **VoiceOver**: no `NSAccessibility` bridge, and AT-SPI is off here, so a
|
|
572
|
+
Cocoa app is not reachable by assistive technology yet. Write roles and
|
|
573
|
+
labels anyway; the bridge will read them ([accessibility.md](accessibility.md)).
|
|
574
|
+
- **Input methods**: no `NSTextInputClient` — no marked text, Press-and-Hold
|
|
575
|
+
accents or CJK input ([events.md](events.md#input-methods)).
|
|
576
|
+
- **Nested `<window>` elements** throw; a `<glarea>` is the only child
|
|
577
|
+
surface.
|
|
578
|
+
- **`Surface` `format: 'a8'`** throws — allocate `argb32` and tint through
|
|
579
|
+
`fillStyle`/`globalAlpha`.
|
|
580
|
+
- **`createPattern`**, and the X-only `Picture`, `<image picture>` and
|
|
581
|
+
`<image drawable>`.
|
|
582
|
+
- **The eyedropper over a bridge older than 0.9**: `useEyedropper().supported`
|
|
583
|
+
is false — leave the eyedropper button undrawn.
|
|
584
|
+
- **`useKeepAwake()`**: no macOS rung.
|
|
585
|
+
- **`transform`** as a style property (on any backend).
|
|
586
|
+
- **`transientFor`** and the `sticky`, `below`, `skip_taskbar` and `shaded`
|
|
587
|
+
states: inert.
|
|
588
|
+
- **A `'cocoa'` backend for `react-x11/test`.**
|
|
589
|
+
- **Dropped images** while the app runs on a worker.
|
|
590
|
+
- **Never, by design**: `<foreign>` and XEmbed, a window manager
|
|
591
|
+
(`examples/wm.jsx`), remoting over `ssh -X` — run the X11 backend through
|
|
592
|
+
XQuartz for those.
|