react-x11 1.2.0 → 2.0.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 (158) hide show
  1. package/README.md +330 -81
  2. package/package.json +132 -14
  3. package/src/ClickToComponent.js +9 -2
  4. package/src/DevToolsIntegration.js +440 -32
  5. package/src/Reconciler.js +463 -165
  6. package/src/a11y.js +1299 -0
  7. package/src/acceleratorhooks.js +86 -0
  8. package/src/accelerators.js +128 -0
  9. package/src/activate.js +168 -0
  10. package/src/anchor.js +360 -0
  11. package/src/appcontext.js +161 -0
  12. package/src/appearance.js +748 -0
  13. package/src/appearancehooks.js +96 -0
  14. package/src/apphooks.js +73 -0
  15. package/src/application.js +748 -0
  16. package/src/atspi.js +1953 -0
  17. package/src/bus.js +545 -0
  18. package/src/bushooks.js +136 -0
  19. package/src/clientmessage.js +140 -0
  20. package/src/clipboard.js +121 -0
  21. package/src/components/Button.js +148 -33
  22. package/src/components/Checkbox.js +66 -32
  23. package/src/components/Dialog.js +96 -29
  24. package/src/components/FileDialog.js +491 -0
  25. package/src/components/Icon.js +329 -0
  26. package/src/components/Menu.js +797 -133
  27. package/src/components/PasswordInput.js +434 -0
  28. package/src/components/ProgressBar.js +91 -12
  29. package/src/components/Radio.js +78 -35
  30. package/src/components/Select.js +255 -65
  31. package/src/components/Slider.js +102 -41
  32. package/src/components/SplitPane.js +206 -0
  33. package/src/components/Switch.js +100 -22
  34. package/src/components/Table.js +529 -0
  35. package/src/components/Tabs.js +211 -0
  36. package/src/components/Tooltip.js +528 -54
  37. package/src/components/anchor.js +154 -107
  38. package/src/components/change.js +34 -0
  39. package/src/components/dnd.js +103 -0
  40. package/src/components/index.js +16 -3
  41. package/src/components/keys.js +17 -20
  42. package/src/components/scribble.js +247 -0
  43. package/src/components/theme.js +350 -42
  44. package/src/components/typeahead.js +12 -2
  45. package/src/compose.js +868 -0
  46. package/src/compositing.js +224 -0
  47. package/src/dbusmenu.js +384 -0
  48. package/src/debug.d.ts +63 -0
  49. package/src/debug.js +642 -0
  50. package/src/decorations.js +486 -0
  51. package/src/desktopsettings.js +209 -0
  52. package/src/desktopsettingshooks.js +62 -0
  53. package/src/dnd.js +1725 -0
  54. package/src/editmenu.js +272 -0
  55. package/src/errors.js +98 -0
  56. package/src/events.js +1406 -162
  57. package/src/extensions.js +45 -0
  58. package/src/filedialog.js +375 -0
  59. package/src/filedialoghooks.js +132 -0
  60. package/src/fonthooks.js +64 -0
  61. package/src/fonts.js +301 -0
  62. package/src/foreignnodes.js +519 -0
  63. package/src/frame/child.js +33 -0
  64. package/src/frame/childmain.js +274 -0
  65. package/src/frame/env.js +140 -0
  66. package/src/frame/index.js +444 -0
  67. package/src/frame/lifecycle.js +67 -0
  68. package/src/frame/protocol.js +179 -0
  69. package/src/frames.js +73 -0
  70. package/src/glbackend.js +114 -0
  71. package/src/glnodes.js +125 -67
  72. package/src/globalmenu.js +683 -0
  73. package/src/host.d.ts +88 -0
  74. package/src/host.js +42 -0
  75. package/src/idle.js +490 -0
  76. package/src/idlehooks.js +100 -0
  77. package/src/imagesource.js +349 -0
  78. package/src/index.d.ts +475 -0
  79. package/src/index.js +83 -9
  80. package/src/inputtime.js +206 -0
  81. package/src/jsx-dev-runtime.d.ts +21 -0
  82. package/src/jsx-dev-runtime.js +2 -0
  83. package/src/jsx-runtime.d.ts +39 -0
  84. package/src/jsx-runtime.js +6 -0
  85. package/src/keyboard.js +256 -0
  86. package/src/keyboardstate.js +278 -0
  87. package/src/keyboardstatehooks.js +58 -0
  88. package/src/keysyms.d.ts +149 -0
  89. package/src/keysyms.js +270 -0
  90. package/src/locale.js +170 -0
  91. package/src/localehooks.js +47 -0
  92. package/src/menuitem.js +223 -0
  93. package/src/node.d.ts +597 -0
  94. package/src/node.js +44 -0
  95. package/src/nodes.js +9546 -690
  96. package/src/ntk.d.ts +44 -0
  97. package/src/ntk.js +25 -0
  98. package/src/paintcache.js +366 -0
  99. package/src/palette.js +380 -0
  100. package/src/pastestate.js +66 -0
  101. package/src/portal.js +461 -0
  102. package/src/priority.js +26 -0
  103. package/src/refresh/index.d.ts +40 -0
  104. package/src/refresh/index.js +122 -0
  105. package/src/refresh/loader.d.ts +37 -0
  106. package/src/refresh/loader.js +401 -0
  107. package/src/refresh/register.d.ts +5 -0
  108. package/src/refresh/register.js +13 -0
  109. package/src/registry.js +232 -0
  110. package/src/scale.js +626 -0
  111. package/src/scalehooks.js +27 -0
  112. package/src/screencolor.js +640 -0
  113. package/src/screencolorhooks.js +101 -0
  114. package/src/screens.js +754 -0
  115. package/src/screenshooks.js +137 -0
  116. package/src/startup.js +302 -0
  117. package/src/style.d.ts +126 -0
  118. package/src/style.js +33 -0
  119. package/src/styles.js +1482 -6
  120. package/src/svgnodes.js +306 -0
  121. package/src/testing/a11y.js +484 -0
  122. package/src/testing/components.js +414 -0
  123. package/src/testing/events.js +407 -0
  124. package/src/testing/harness.js +455 -0
  125. package/src/testing/index.d.ts +558 -0
  126. package/src/testing/index.js +85 -0
  127. package/src/testing/mock-app.js +463 -0
  128. package/src/testing/pixels.js +152 -0
  129. package/src/testing/queries.js +224 -0
  130. package/src/textrange.js +83 -0
  131. package/src/textselection.js +439 -0
  132. package/src/trace-registry.js +63 -0
  133. package/src/transfer.js +93 -0
  134. package/src/types/appearance.d.ts +84 -0
  135. package/src/types/application.d.ts +173 -0
  136. package/src/types/components.d.ts +820 -0
  137. package/src/types/dbus.d.ts +177 -0
  138. package/src/types/elements.d.ts +897 -0
  139. package/src/types/events.d.ts +549 -0
  140. package/src/types/filedialog.d.ts +260 -0
  141. package/src/types/fonts.d.ts +124 -0
  142. package/src/types/frame.d.ts +146 -0
  143. package/src/types/globalmenu.d.ts +42 -0
  144. package/src/types/nodes.d.ts +199 -0
  145. package/src/types/screencolor.d.ts +84 -0
  146. package/src/types/style.d.ts +434 -0
  147. package/src/types/system.d.ts +287 -0
  148. package/src/windowid.js +151 -0
  149. package/src/windowstate.js +393 -0
  150. package/src/xsettings.js +336 -0
  151. package/src/yoga.d.ts +55 -0
  152. package/src/yoga.js +135 -0
  153. package/src/components/Canvas3D.js +0 -28
  154. package/src/geometry3d.js +0 -223
  155. package/src/pointer3d.js +0 -158
  156. package/src/raycast3d.js +0 -146
  157. package/src/richnodes.js +0 -436
  158. package/src/scene3d.js +0 -683
@@ -0,0 +1,260 @@
1
+ /**
2
+ * The file dialog and the portal machinery under it. See docs/filedialog.md.
3
+ */
4
+
5
+ import type { ReactNode, RefObject } from 'react';
6
+ import type { MessageBus } from './dbus.js';
7
+ import type { DrawnNode, NtkWindow } from './nodes.js';
8
+
9
+ /**
10
+ * The shape of `AbortSignal` this uses.
11
+ *
12
+ * Named structurally rather than reaching for the global: these declarations
13
+ * compile with `types: []` and no `DOM` lib — adding `DOM` to get one name
14
+ * would also make `<div>` legal JSX, which owning the element namespace exists
15
+ * to prevent. A real `AbortSignal` satisfies this.
16
+ */
17
+ export interface AbortSignalLike {
18
+ readonly aborted: boolean;
19
+ readonly reason?: unknown;
20
+ addEventListener(
21
+ type: 'abort',
22
+ listener: () => void,
23
+ options?: { once?: boolean },
24
+ ): void;
25
+ removeEventListener(type: 'abort', listener: () => void): void;
26
+ }
27
+
28
+ /** Which rung of the ladder answered — or would. */
29
+ export type FileDialogBackend = 'portal' | 'osascript' | 'builtin';
30
+
31
+ /**
32
+ * One entry in a dialog's type filter.
33
+ *
34
+ * Give `extensions` where you can: they translate to every backend. MIME types
35
+ * reach the portal exactly and macOS not at all — see docs/filedialog.md.
36
+ */
37
+ export interface FileFilter {
38
+ name: string;
39
+ /** `['png', 'jpg']` — with or without the leading dot. */
40
+ extensions?: string[];
41
+ /** `['image/png']`. Portal only. */
42
+ mimeTypes?: string[];
43
+ }
44
+
45
+ /** Anything a `<window>`/`<popup>` ref or XID can be given as. */
46
+ export type WindowTarget =
47
+ | NtkWindow
48
+ | DrawnNode
49
+ | RefObject<NtkWindow | DrawnNode | null>
50
+ | number
51
+ | null;
52
+
53
+ export interface FileDialogOptions {
54
+ title?: string;
55
+ /** Several files at once. Ignored when saving — that is always one file. */
56
+ multiple?: boolean;
57
+ filters?: FileFilter[];
58
+ /** Where the dialog opens. */
59
+ defaultFolder?: string;
60
+ /** The name a save dialog starts with. */
61
+ defaultName?: string;
62
+ /** The file a save dialog starts on, replacing `defaultName`. */
63
+ defaultPath?: string;
64
+ /** The confirm button's text — 'Import', 'Attach', … */
65
+ acceptLabel?: string;
66
+ /**
67
+ * The window the dialog belongs to. `transientFor` for the built-in dialog,
68
+ * `parent_window` for the portal; macOS has no cross-process equivalent and
69
+ * ignores it. Worth the one line — without it the dialog floats.
70
+ */
71
+ parentWindow?: WindowTarget;
72
+ /** Abort the dialog. Closes the portal request or kills `osascript`. */
73
+ signal?: AbortSignalLike;
74
+ /**
75
+ * Force a rung instead of taking the best available one. The seam for a
76
+ * kiosk that must never show the desktop's dialog, and for tests.
77
+ */
78
+ backend?: FileDialogBackend;
79
+ /** `false` lets the portal dialog be non-modal. Default `true`. */
80
+ modal?: boolean;
81
+ }
82
+
83
+ /**
84
+ * Nothing on this machine can show a file dialog, and the caller cannot draw
85
+ * one either. A **typed** rejection, so it reads as "fall back to your own UI"
86
+ * rather than as a crash — {@link useFileDialog} never throws it, because it
87
+ * has a tree to draw in.
88
+ */
89
+ export declare class NoFileDialogError extends Error {
90
+ readonly name: 'NoFileDialogError';
91
+ readonly cause?: unknown;
92
+ }
93
+
94
+ /** There is no portal here — no bus, no service, or a version too old. */
95
+ export declare class NoPortalError extends Error {
96
+ readonly name: 'NoPortalError';
97
+ readonly cause?: unknown;
98
+ }
99
+
100
+ /** The user dismissed the dialog. Surfaced as `null`, not thrown, by the
101
+ * functions below; exported for code driving {@link portalRequest} directly. */
102
+ export declare class PortalCancelledError extends Error {
103
+ readonly name: 'PortalCancelledError';
104
+ /** 1 when the user cancelled, 2 when it ended some other way. */
105
+ readonly response: number;
106
+ }
107
+
108
+ /**
109
+ * Which rung this machine lands on, without showing anything.
110
+ *
111
+ * Acquires a bus reference and releases it, so it is cheap but not free —
112
+ * cache it rather than calling it on a render path.
113
+ */
114
+ export declare function fileDialogBackend(): Promise<FileDialogBackend>;
115
+
116
+ /**
117
+ * Ask for existing files. Resolves to absolute paths, or `null` when the user
118
+ * cancelled.
119
+ *
120
+ * Rejects with {@link NoFileDialogError} where there is no portal and no
121
+ * `osascript`; {@link useFileDialog} draws its own dialog instead.
122
+ */
123
+ export declare function openFile(
124
+ options?: FileDialogOptions,
125
+ ): Promise<string[] | null>;
126
+
127
+ /** Ask where to write a file. One absolute path, which need not exist yet. */
128
+ export declare function saveFile(
129
+ options?: FileDialogOptions,
130
+ ): Promise<string | null>;
131
+
132
+ /** Ask for directories. Absolute paths, or `null` when cancelled. */
133
+ export declare function selectFolder(
134
+ options?: FileDialogOptions,
135
+ ): Promise<string[] | null>;
136
+
137
+ export interface FileDialogs {
138
+ openFile(options?: FileDialogOptions): Promise<string[] | null>;
139
+ saveFile(options?: FileDialogOptions): Promise<string | null>;
140
+ selectFolder(options?: FileDialogOptions): Promise<string[] | null>;
141
+ }
142
+
143
+ /**
144
+ * The window this component is in, resolved when it is read rather than at
145
+ * render time — a `<window>` has no XID until the commit phase.
146
+ *
147
+ * Returns a **ref-like object**, so it drops into anything that already takes
148
+ * a ref (`parentWindow`, `transientFor`) and `windowIdOf()` resolves it.
149
+ * `useFileDialog()` uses it for you; reach for it directly when something
150
+ * else needs the owner window.
151
+ *
152
+ * Exact when the tree has one top-level window, which is nearly every app.
153
+ * With several it prefers the focused one and warns in development when it
154
+ * has to guess — see docs/filedialog.md.
155
+ */
156
+ export declare function useTopLevelWindow(): {
157
+ readonly current: NtkWindow | DrawnNode | null;
158
+ };
159
+
160
+ /**
161
+ * File dialogs that never run out of rungs: the desktop's own where there is
162
+ * one, `osascript` on macOS, and a browser react-x11 draws itself everywhere
163
+ * else. Cancelling resolves to `null` on all three.
164
+ *
165
+ * ```tsx
166
+ * const { openFile } = useFileDialog({ parentWindow: windowRef });
167
+ * const files = await openFile({ filters: [{ name: 'Text', extensions: ['txt'] }] });
168
+ * if (files) load(files[0]);
169
+ * ```
170
+ */
171
+ export declare function useFileDialog(
172
+ defaults?: FileDialogOptions,
173
+ ): FileDialogs;
174
+
175
+ export interface FileDialogProps {
176
+ kind?: 'open' | 'save' | 'folder';
177
+ title?: string;
178
+ startFolder?: string;
179
+ defaultName?: string;
180
+ filters?: FileFilter[];
181
+ multiple?: boolean;
182
+ acceptLabel?: string;
183
+ /** An XID. Resolved for you when this is rendered by `useFileDialog()`. */
184
+ parentWindow?: number;
185
+ /** Called exactly once: the chosen paths, or `null` for cancel. */
186
+ onDone?: (paths: string[] | null) => void;
187
+ /** The palette, for when this is rendered on a root with no provider. */
188
+ theme?: Record<string, unknown>;
189
+ width?: number;
190
+ height?: number;
191
+ }
192
+
193
+ /**
194
+ * The built-in file browser, as a component. `useFileDialog()` renders this
195
+ * for you; reach for it directly only when this dialog is the one you want
196
+ * regardless of what the desktop offers.
197
+ */
198
+ export declare function FileDialog(props: FileDialogProps): ReactNode;
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // The portal floor
202
+ // ---------------------------------------------------------------------------
203
+
204
+ /**
205
+ * Is `name` reachable — owned now, **or activatable on demand**?
206
+ *
207
+ * `NameHasOwner` is the wrong question: `org.freedesktop.portal.Desktop` is
208
+ * D-Bus-activatable, so on a healthy desktop where no app has touched a portal
209
+ * yet it has no owner, and a feature gated on that takes the fallback path
210
+ * forever.
211
+ */
212
+ export declare function hasService(
213
+ name: string,
214
+ busRef?: { bus: MessageBus; uniqueName: string },
215
+ ): Promise<boolean>;
216
+
217
+ export interface PortalRequestOptions {
218
+ iface: string;
219
+ member: string;
220
+ /** `'x11:1a00007'`, or `''` for none. */
221
+ parentWindow?: string;
222
+ title?: string;
223
+ /**
224
+ * The method's full argument signature, ending in the options dict —
225
+ * `'sa{sv}'` for `Screenshot.PickColor`. Travels with `args`; omitting
226
+ * both means FileChooser's `'ssa{sv}'` with `[parentWindow, title]`.
227
+ */
228
+ signature?: string;
229
+ /** The leading arguments the signature describes, before the options dict. */
230
+ args?: unknown[];
231
+ options?: Record<string, unknown>;
232
+ signal?: AbortSignalLike;
233
+ }
234
+
235
+ /**
236
+ * Call a portal method that answers through a `Request`, with the
237
+ * subscription in place **before** the call goes out — which is the whole
238
+ * reason this exists rather than each portal doing it slightly wrong.
239
+ *
240
+ * There is deliberately no timeout on the answer: a dialog can be open for an
241
+ * hour. Only the initial call is deadlined.
242
+ */
243
+ export declare function portalRequest(
244
+ busRef: { bus: MessageBus; uniqueName: string },
245
+ options: PortalRequestOptions,
246
+ ): Promise<{ response: number; results: Record<string, unknown> }>;
247
+
248
+ /**
249
+ * The version of one portal *interface*, or `0` when it is not there.
250
+ *
251
+ * {@link hasService} answers a different question: the portal service being
252
+ * reachable says nothing about which interfaces its backends provide.
253
+ * Capability lives in the interface's own `version` property — `PickColor`
254
+ * needs `org.freedesktop.portal.Screenshot` at 2, and XFCE's portal has no
255
+ * Screenshot interface at all.
256
+ */
257
+ export declare function portalVersion(
258
+ iface: string,
259
+ busRef?: { bus: MessageBus; uniqueName: string },
260
+ ): Promise<number>;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Font files an application opens for itself — reading one, and registering
3
+ * one so `fontFamily` can name it. See docs/styling.md#a-font-file-of-your-own.
4
+ */
5
+
6
+ import type { NtkApp } from './nodes.js';
7
+
8
+ /**
9
+ * ntk's `Font`, as much of it as is worth declaring by hand.
10
+ *
11
+ * Deliberately loose, for the same reason `react-x11/ntk` is: ntk ships no
12
+ * types, and a full mirror written here would drift out of date silently.
13
+ * The members below are the ones an application reads; everything else the
14
+ * class has is still there at runtime.
15
+ */
16
+ export interface Font {
17
+ /** The family the file calls itself — not necessarily what it is
18
+ * registered as; see {@link loadFont}. */
19
+ readonly familyName: string;
20
+ readonly postscriptName: string;
21
+ readonly unitsPerEm: number;
22
+ /** Stable cache key — the same file opened twice has the same one. */
23
+ readonly key: string;
24
+ /** The path it was opened from, or null for a font opened from bytes. */
25
+ readonly path: string | null;
26
+ /** fontkit's own font object, for what this interface does not cover
27
+ * (`namedVariations`, the raw tables). */
28
+ readonly fk: Record<string, unknown> & {
29
+ namedVariations?: Record<string, Record<string, number>>;
30
+ };
31
+ /**
32
+ * `{}` for a static face, so `Object.keys(font.variationAxes).length` is
33
+ * the whole of "is this variable?".
34
+ */
35
+ readonly variationAxes: Record<
36
+ string,
37
+ { name?: string; min: number; default: number; max: number }
38
+ >;
39
+ /** Scaled to a pixel size. A metric the face never declared is `null`
40
+ * rather than 0 — a cap height it did not state is not a cap height of
41
+ * zero. */
42
+ metrics(size: number): {
43
+ ascent: number;
44
+ descent: number;
45
+ lineGap: number;
46
+ capHeight: number | null;
47
+ xHeight: number | null;
48
+ [key: string]: number | null;
49
+ };
50
+ hasGlyph(codepoint: number): boolean;
51
+ /** This face at a point in its design space: `variation({ wght: 460 })`. */
52
+ variation(settings: Record<string, number>): Font;
53
+ }
54
+
55
+ export interface OpenFontOptions {
56
+ /** Which face of a `.ttc` collection; the first otherwise. */
57
+ postscriptName?: string;
58
+ }
59
+
60
+ export interface LoadFontOptions extends OpenFontOptions {
61
+ /** Register under this name instead of the font's own, and use it
62
+ * verbatim. */
63
+ family?: string;
64
+ /** Override the weight the file declares. */
65
+ weight?: number | string;
66
+ /** `'italic'` / `'oblique'`, overriding what the file declares. */
67
+ style?: string;
68
+ }
69
+
70
+ export interface LoadedFont {
71
+ readonly font: Font;
72
+ /** What to put in `fontFamily` — the font's own name, scoped to
73
+ * `Inter 2` only where a second file would otherwise be unreachable. */
74
+ readonly family: string;
75
+ }
76
+
77
+ /**
78
+ * Read a font file — metrics, coverage, variation axes — through the
79
+ * connection's font cache, so it is read once however often this is called.
80
+ *
81
+ * ```ts
82
+ * const font = openFont(app, '/usr/share/fonts/truetype/inter/Inter.ttf');
83
+ * font.metrics(30);
84
+ * font.variationAxes;
85
+ * ```
86
+ *
87
+ * Nothing about the app changes: the face is not registered, so it is
88
+ * neither a `fontFamily` candidate nor part of the fallback chain. Use
89
+ * {@link loadFont} to draw with it.
90
+ */
91
+ export function openFont(
92
+ app: NtkApp,
93
+ source: string | Uint8Array,
94
+ opts?: OpenFontOptions,
95
+ ): Font;
96
+
97
+ /**
98
+ * Read a font file and register it, returning the face and the family name
99
+ * to draw it with.
100
+ *
101
+ * ```tsx
102
+ * const { family } = loadFont(app, '/path/to/Inter.ttf');
103
+ * <text style={{ fontFamily: family }}>Handgloves</text>;
104
+ * ```
105
+ */
106
+ export function loadFont(
107
+ app: NtkApp,
108
+ source: string | Uint8Array,
109
+ opts?: LoadFontOptions,
110
+ ): LoadedFont;
111
+
112
+ /**
113
+ * {@link loadFont} from a component, memoized per app and file. Null when
114
+ * `source` is null, so a picker can call it before anything is picked.
115
+ *
116
+ * ```tsx
117
+ * const picked = useFont(path);
118
+ * <text style={{ fontFamily: picked?.family }}>{sample}</text>;
119
+ * ```
120
+ */
121
+ export function useFont(
122
+ source?: string | Uint8Array | null,
123
+ opts?: LoadFontOptions,
124
+ ): LoadedFont | null;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * `<Frame>` — a pane of this application in its own process — and the
3
+ * context bridge that follows the app into it. See docs/frame.md.
4
+ */
5
+
6
+ import type { Context, ReactElement, ReactNode, Ref } from 'react';
7
+ import type { StyleProp } from './style.js';
8
+
9
+ /**
10
+ * The props bag a `<Frame>` sends its pane. Values cross by structured
11
+ * clone; functions become fire-and-forget stubs on the pane side, so a
12
+ * callback's return value is always `undefined` over there.
13
+ */
14
+ export type FrameProps = Record<string, unknown>;
15
+
16
+ /** Why a pane is not running, for `fallback` and `onExit` to read. */
17
+ export interface FrameError extends Error {
18
+ /** Where it went wrong: `'spawn' | 'load' | 'connect' | 'handshake' |
19
+ * 'runtime' | 'send' | 'embed' | 'exit'`. */
20
+ phase?: string;
21
+ code?: number | null;
22
+ signal?: string | null;
23
+ }
24
+
25
+ export interface FrameHandle {
26
+ /** Start a fresh pane process; what `fallback` receives to retry with. */
27
+ restart(): void;
28
+ /** The pane's pid, `null` before the first spawn and after an exit. */
29
+ pid: number | null;
30
+ }
31
+
32
+ export interface FrameComponentProps {
33
+ /**
34
+ * The pane module: its default export is mounted as the pane's root
35
+ * component. A `URL` (`new URL('./pane.js', import.meta.url)` — typed
36
+ * structurally so no DOM lib is needed) or an absolute path — never
37
+ * relative, which would resolve against react-x11's own files rather
38
+ * than the caller's.
39
+ */
40
+ src: { href: string } | string;
41
+ /** Data for the pane, snapshotted and sent whole once per commit that
42
+ * changes it. Functions anywhere in the bag become RPC stubs. */
43
+ props?: FrameProps;
44
+ /** The embedded pane's rect in this tree — sized like any other child. */
45
+ style?: StyleProp;
46
+ /** X display for the pane process; defaults to the inherited `$DISPLAY`. */
47
+ display?: string;
48
+ /**
49
+ * Which bridged contexts follow the app into this pane: `true` (default)
50
+ * for every registered one — the theme included — `false` for none, or an
51
+ * allowlist of keys.
52
+ */
53
+ bridge?: boolean | string[];
54
+ /**
55
+ * What this rect shows when the pane crashed or could not start: an
56
+ * element, or a function of the error and a `restart` that respawns.
57
+ * Without one the rect is an empty box in its `style`.
58
+ */
59
+ fallback?:
60
+ | ReactElement
61
+ | ((info: { error: FrameError | null; restart: () => void }) => ReactNode);
62
+ /** Whether the pane joins the tab order, like any `<foreign>`. */
63
+ focusable?: boolean;
64
+ /** The pane mounted and its window is being embedded. */
65
+ onStarted?(info: { pid: number | null; windowId: number }): void;
66
+ /** The pane process ended. `expected` is true when this side asked —
67
+ * unmount, `restart()`, a `src` change — and false for a crash. */
68
+ onExit?(info: {
69
+ code: number | null;
70
+ signal: string | null;
71
+ expected: boolean;
72
+ }): void;
73
+ /**
74
+ * Replace the fork with a transport of your own — the seam the tests use
75
+ * (a loopback pair into `runFrameChild`), and the door to running a pane
76
+ * somewhere other than a child process. Advanced; see docs/frame.md.
77
+ */
78
+ transport?(options: { src: string; display?: string }): FrameTransport;
79
+ ref?: Ref<FrameHandle>;
80
+ }
81
+
82
+ /** The parent's end of the wire to a pane. */
83
+ export interface FrameTransport {
84
+ /** May throw when the channel is gone or the message will not clone. */
85
+ send(msg: object): void;
86
+ onMessage(cb: (msg: object) => void): () => void;
87
+ onExit(
88
+ cb: (info: {
89
+ code?: number | null;
90
+ signal?: string | null;
91
+ error?: Error | null;
92
+ }) => void,
93
+ ): () => void;
94
+ kill?(signal: string): void;
95
+ pid?: number;
96
+ }
97
+
98
+ /**
99
+ * A module of this application, mounted in its own process, its window
100
+ * embedded here (over `<foreign>`). Props cross as data, callbacks as
101
+ * fire-and-forget stubs, context through the bridge — and the process
102
+ * boundary contains the pane's failures, not its intentions: it is not a
103
+ * security boundary (docs/security.md).
104
+ */
105
+ export function Frame(props: FrameComponentProps): ReactElement | null;
106
+
107
+ export interface FrameContext<T> {
108
+ /** The name this value travels under; unique per application. */
109
+ key: string;
110
+ /** The ordinary React context, for `useContext` and class consumers. */
111
+ Context: Context<T>;
112
+ /** The provider that also publishes the value to `<Frame>` panes. */
113
+ Provider: (props: { value: T; children?: ReactNode }) => ReactElement;
114
+ /** `useContext(Context)`, as a method. */
115
+ use(): T;
116
+ }
117
+
118
+ /**
119
+ * A React context whose value follows the app into its `<Frame>` panes:
120
+ * in-process an ordinary context, across a frame a structured-clone
121
+ * snapshot recreated as a real provider around the pane. One direction,
122
+ * data only — a value that will not clone is dropped with a warning, and
123
+ * dispatchers travel in `props` where the wiring is visible.
124
+ *
125
+ * Both sides must import the module that calls this — which the pane
126
+ * already does, since its components read the context from it.
127
+ */
128
+ export function createFrameContext<T>(
129
+ key: string,
130
+ defaultValue: T,
131
+ options?: {
132
+ serialize?: (value: T) => unknown;
133
+ revive?: (wire: unknown) => T;
134
+ },
135
+ ): FrameContext<T>;
136
+
137
+ /**
138
+ * Runs when the host is letting this pane go — the `<Frame>` unmounted,
139
+ * the host app is exiting — bounded by the host's patience (it escalates
140
+ * to signals). May return a promise. Not called on a crash; outside a
141
+ * frame it never runs.
142
+ */
143
+ export function useFrameClose(handler: () => void | Promise<void>): void;
144
+
145
+ /** Whether this process is a `<Frame>` pane. */
146
+ export function isFramed(): boolean;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The global menu — handing a window's menu bar to the desktop's panel.
3
+ *
4
+ * `MenuBar` does all of this itself. These names are for a component that
5
+ * draws its own bar and wants the same behaviour. See docs/globalmenu.md.
6
+ */
7
+
8
+ import type { MenuBarMenu, MenuItem } from './components.js';
9
+
10
+ export interface GlobalMenuOptions {
11
+ /** Runs after the item's own `onSelect`, exactly as `MenuBar`'s does. */
12
+ onSelect?: (item: MenuItem) => void;
13
+ /**
14
+ * A submenu is about to be shown. dbusmenu wants a synchronous answer that
15
+ * React cannot give, so this is a notification rather than a chance to
16
+ * fill the menu in time — update state from it and the new items reach the
17
+ * panel one round trip later. See docs/globalmenu.md.
18
+ */
19
+ onAboutToShow?: (item: MenuItem) => void;
20
+ /** `false` keeps the menu in the window. Defaults to `true`. */
21
+ enabled?: boolean;
22
+ }
23
+
24
+ /**
25
+ * Is this window's menu being drawn by the desktop?
26
+ *
27
+ * `false` until proven otherwise, and the proof is a registrar answering
28
+ * `RegisterWindow` — so no bus, no panel, or a panel that refused the
29
+ * registration all keep the menu where the app drew it.
30
+ *
31
+ * ```tsx
32
+ * const exported = useGlobalMenu(menus, { onSelect });
33
+ * if (exported) return null;
34
+ * ```
35
+ */
36
+ export function useGlobalMenu(
37
+ menus: readonly MenuBarMenu[] | undefined,
38
+ options?: GlobalMenuOptions,
39
+ ): boolean;
40
+
41
+ /** The bus name a panel owns while it is showing application menus. */
42
+ export const REGISTRAR_NAME: string;