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.
- package/README.md +330 -81
- package/package.json +132 -14
- package/src/ClickToComponent.js +9 -2
- package/src/DevToolsIntegration.js +440 -32
- package/src/Reconciler.js +463 -165
- package/src/a11y.js +1299 -0
- package/src/acceleratorhooks.js +86 -0
- package/src/accelerators.js +128 -0
- package/src/activate.js +168 -0
- package/src/anchor.js +360 -0
- package/src/appcontext.js +161 -0
- package/src/appearance.js +748 -0
- package/src/appearancehooks.js +96 -0
- package/src/apphooks.js +73 -0
- package/src/application.js +748 -0
- package/src/atspi.js +1953 -0
- package/src/bus.js +545 -0
- package/src/bushooks.js +136 -0
- package/src/clientmessage.js +140 -0
- package/src/clipboard.js +121 -0
- package/src/components/Button.js +148 -33
- package/src/components/Checkbox.js +66 -32
- package/src/components/Dialog.js +96 -29
- package/src/components/FileDialog.js +491 -0
- package/src/components/Icon.js +329 -0
- package/src/components/Menu.js +797 -133
- package/src/components/PasswordInput.js +434 -0
- package/src/components/ProgressBar.js +91 -12
- package/src/components/Radio.js +78 -35
- package/src/components/Select.js +255 -65
- package/src/components/Slider.js +102 -41
- package/src/components/SplitPane.js +206 -0
- package/src/components/Switch.js +100 -22
- package/src/components/Table.js +529 -0
- package/src/components/Tabs.js +211 -0
- package/src/components/Tooltip.js +528 -54
- package/src/components/anchor.js +154 -107
- package/src/components/change.js +34 -0
- package/src/components/dnd.js +103 -0
- package/src/components/index.js +16 -3
- package/src/components/keys.js +17 -20
- package/src/components/scribble.js +247 -0
- package/src/components/theme.js +350 -42
- package/src/components/typeahead.js +12 -2
- package/src/compose.js +868 -0
- package/src/compositing.js +224 -0
- package/src/dbusmenu.js +384 -0
- package/src/debug.d.ts +63 -0
- package/src/debug.js +642 -0
- package/src/decorations.js +486 -0
- package/src/desktopsettings.js +209 -0
- package/src/desktopsettingshooks.js +62 -0
- package/src/dnd.js +1725 -0
- package/src/editmenu.js +272 -0
- package/src/errors.js +98 -0
- package/src/events.js +1406 -162
- package/src/extensions.js +45 -0
- package/src/filedialog.js +375 -0
- package/src/filedialoghooks.js +132 -0
- package/src/fonthooks.js +64 -0
- package/src/fonts.js +301 -0
- package/src/foreignnodes.js +519 -0
- package/src/frame/child.js +33 -0
- package/src/frame/childmain.js +274 -0
- package/src/frame/env.js +140 -0
- package/src/frame/index.js +444 -0
- package/src/frame/lifecycle.js +67 -0
- package/src/frame/protocol.js +179 -0
- package/src/frames.js +73 -0
- package/src/glbackend.js +114 -0
- package/src/glnodes.js +125 -67
- package/src/globalmenu.js +683 -0
- package/src/host.d.ts +88 -0
- package/src/host.js +42 -0
- package/src/idle.js +490 -0
- package/src/idlehooks.js +100 -0
- package/src/imagesource.js +349 -0
- package/src/index.d.ts +475 -0
- package/src/index.js +83 -9
- package/src/inputtime.js +206 -0
- package/src/jsx-dev-runtime.d.ts +21 -0
- package/src/jsx-dev-runtime.js +2 -0
- package/src/jsx-runtime.d.ts +39 -0
- package/src/jsx-runtime.js +6 -0
- package/src/keyboard.js +256 -0
- package/src/keyboardstate.js +278 -0
- package/src/keyboardstatehooks.js +58 -0
- package/src/keysyms.d.ts +149 -0
- package/src/keysyms.js +270 -0
- package/src/locale.js +170 -0
- package/src/localehooks.js +47 -0
- package/src/menuitem.js +223 -0
- package/src/node.d.ts +597 -0
- package/src/node.js +44 -0
- package/src/nodes.js +9546 -690
- package/src/ntk.d.ts +44 -0
- package/src/ntk.js +25 -0
- package/src/paintcache.js +366 -0
- package/src/palette.js +380 -0
- package/src/pastestate.js +66 -0
- package/src/portal.js +461 -0
- package/src/priority.js +26 -0
- package/src/refresh/index.d.ts +40 -0
- package/src/refresh/index.js +122 -0
- package/src/refresh/loader.d.ts +37 -0
- package/src/refresh/loader.js +401 -0
- package/src/refresh/register.d.ts +5 -0
- package/src/refresh/register.js +13 -0
- package/src/registry.js +232 -0
- package/src/scale.js +626 -0
- package/src/scalehooks.js +27 -0
- package/src/screencolor.js +640 -0
- package/src/screencolorhooks.js +101 -0
- package/src/screens.js +754 -0
- package/src/screenshooks.js +137 -0
- package/src/startup.js +302 -0
- package/src/style.d.ts +126 -0
- package/src/style.js +33 -0
- package/src/styles.js +1482 -6
- package/src/svgnodes.js +306 -0
- package/src/testing/a11y.js +484 -0
- package/src/testing/components.js +414 -0
- package/src/testing/events.js +407 -0
- package/src/testing/harness.js +455 -0
- package/src/testing/index.d.ts +558 -0
- package/src/testing/index.js +85 -0
- package/src/testing/mock-app.js +463 -0
- package/src/testing/pixels.js +152 -0
- package/src/testing/queries.js +224 -0
- package/src/textrange.js +83 -0
- package/src/textselection.js +439 -0
- package/src/trace-registry.js +63 -0
- package/src/transfer.js +93 -0
- package/src/types/appearance.d.ts +84 -0
- package/src/types/application.d.ts +173 -0
- package/src/types/components.d.ts +820 -0
- package/src/types/dbus.d.ts +177 -0
- package/src/types/elements.d.ts +897 -0
- package/src/types/events.d.ts +549 -0
- package/src/types/filedialog.d.ts +260 -0
- package/src/types/fonts.d.ts +124 -0
- package/src/types/frame.d.ts +146 -0
- package/src/types/globalmenu.d.ts +42 -0
- package/src/types/nodes.d.ts +199 -0
- package/src/types/screencolor.d.ts +84 -0
- package/src/types/style.d.ts +434 -0
- package/src/types/system.d.ts +287 -0
- package/src/windowid.js +151 -0
- package/src/windowstate.js +393 -0
- package/src/xsettings.js +336 -0
- package/src/yoga.d.ts +55 -0
- package/src/yoga.js +135 -0
- package/src/components/Canvas3D.js +0 -28
- package/src/geometry3d.js +0 -223
- package/src/pointer3d.js +0 -158
- package/src/raycast3d.js +0 -146
- package/src/richnodes.js +0 -436
- package/src/scene3d.js +0 -683
package/src/editmenu.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The standard edit menu — its rows, their geometry and their painting.
|
|
3
|
+
*
|
|
4
|
+
* A browser gives `<input>` a context menu without being asked, and that is
|
|
5
|
+
* the bar here: a bare `<textinput>` gets Undo/Cut/Copy/Paste with no
|
|
6
|
+
* wiring at all. That rules out building it from the `Menu` components,
|
|
7
|
+
* which live a layer above the nodes and cannot be mounted from one — so
|
|
8
|
+
* the rows are measured and drawn here, and a node hangs them in a
|
|
9
|
+
* `<popup>` (`openEditMenu`, nodes.js, which is the public seam).
|
|
10
|
+
*
|
|
11
|
+
* Deliberately free of node imports: this takes plain data, a measuring
|
|
12
|
+
* function and a 2d context. It can be tested without a tree, and falls
|
|
13
|
+
* back to fixed metrics when there are no fonts, which is how the headless
|
|
14
|
+
* tests see it.
|
|
15
|
+
*
|
|
16
|
+
* The item vocabulary is the one `MenuBar` uses — `type: 'separator'`, a
|
|
17
|
+
* `shortcut` as dbusmenu's `aas` — even though these rows never reach a bus:
|
|
18
|
+
* two spellings for "separator" in one codebase is a trap for whoever writes
|
|
19
|
+
* the third menu. `editMenuGeometry` normalises both into the row it returns,
|
|
20
|
+
* so everything below it works on a boolean and a string.
|
|
21
|
+
*/
|
|
22
|
+
import { formatShortcut, isSeparator } from './menuitem.js';
|
|
23
|
+
|
|
24
|
+
const ROW_HEIGHT = 22;
|
|
25
|
+
const SEPARATOR_HEIGHT = 7;
|
|
26
|
+
const PAD_X = 10;
|
|
27
|
+
const PAD_Y = 4;
|
|
28
|
+
const SHORTCUT_GAP = 28;
|
|
29
|
+
const MIN_WIDTH = 150;
|
|
30
|
+
// headless (no fonts): enough to keep the rows a plausible size
|
|
31
|
+
const FALLBACK_CHAR_WIDTH = 7;
|
|
32
|
+
|
|
33
|
+
/** Matches the widget palette's defaults, so the built-in menu and a
|
|
34
|
+
* `ContextMenu` next to it do not look like different toolkits. */
|
|
35
|
+
export const EDIT_MENU_COLORS = {
|
|
36
|
+
background: 'white',
|
|
37
|
+
border: '#b2bec3',
|
|
38
|
+
text: '#2d3436',
|
|
39
|
+
dim: '#7f8c8d',
|
|
40
|
+
highlight: '#2980b9',
|
|
41
|
+
highlightText: 'white',
|
|
42
|
+
separator: '#dfe6e9',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Palette for a node: its theme where it has one, defaults otherwise. */
|
|
46
|
+
export function editMenuColors(theme) {
|
|
47
|
+
if (!theme) return EDIT_MENU_COLORS;
|
|
48
|
+
return {
|
|
49
|
+
background: theme.surface ?? EDIT_MENU_COLORS.background,
|
|
50
|
+
border: theme.border ?? EDIT_MENU_COLORS.border,
|
|
51
|
+
text: theme.text ?? EDIT_MENU_COLORS.text,
|
|
52
|
+
dim: theme.textMuted ?? EDIT_MENU_COLORS.dim,
|
|
53
|
+
highlight: theme.hoverBackground ?? EDIT_MENU_COLORS.highlight,
|
|
54
|
+
highlightText: theme.hoverText ?? EDIT_MENU_COLORS.highlightText,
|
|
55
|
+
separator: theme.track ?? EDIT_MENU_COLORS.separator,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The rows, in the standard order, each enabled exactly when it would do
|
|
61
|
+
* something — the whole of the menu's policy, as a pure function of the
|
|
62
|
+
* verbs a target offers (`openEditMenu`, nodes.js).
|
|
63
|
+
*
|
|
64
|
+
* **A verb that was not handed over is a row that is not there**, rather
|
|
65
|
+
* than a greyed one. It was motivated by `<textinput sensitive>`, where a
|
|
66
|
+
* disabled Copy over a password reads as a bug in the application rather
|
|
67
|
+
* than as a decision — but it is also exactly what a surface with nothing
|
|
68
|
+
* to edit needs: a read-only view that offers `copy` and `selectAll` gets a
|
|
69
|
+
* two-row menu, not six rows with four of them dead. So this is the rule,
|
|
70
|
+
* not an exception in the field: never render a verb the caller withheld.
|
|
71
|
+
*
|
|
72
|
+
* Enablement is the other half and is *not* the caller's to decide row by
|
|
73
|
+
* row: Cut and Copy follow the selection, Paste follows what the server
|
|
74
|
+
* says about the clipboard, and Undo/Redo/Select All follow the three
|
|
75
|
+
* `can*` answers only their owner can give. One implementation of that is
|
|
76
|
+
* the point of the seam.
|
|
77
|
+
*
|
|
78
|
+
* @param {object} actions the verb interface — see `openEditMenu`
|
|
79
|
+
* @param {{canPaste?: boolean}} [state] what only the connection knows
|
|
80
|
+
*/
|
|
81
|
+
export function editMenuItems(actions = {}, { canPaste = true } = {}) {
|
|
82
|
+
const has = (verb) => typeof actions[verb] === 'function';
|
|
83
|
+
const selected = Boolean(actions.hasSelection);
|
|
84
|
+
const groups = [
|
|
85
|
+
[
|
|
86
|
+
has('undo') && {
|
|
87
|
+
id: 'undo',
|
|
88
|
+
label: 'Undo',
|
|
89
|
+
shortcut: [['Control', 'Z']],
|
|
90
|
+
enabled: Boolean(actions.canUndo),
|
|
91
|
+
},
|
|
92
|
+
has('redo') && {
|
|
93
|
+
id: 'redo',
|
|
94
|
+
label: 'Redo',
|
|
95
|
+
shortcut: [['Control', 'Shift', 'Z']],
|
|
96
|
+
enabled: Boolean(actions.canRedo),
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
[
|
|
100
|
+
has('cut') && {
|
|
101
|
+
id: 'cut',
|
|
102
|
+
label: 'Cut',
|
|
103
|
+
shortcut: [['Control', 'X']],
|
|
104
|
+
enabled: selected,
|
|
105
|
+
},
|
|
106
|
+
has('copy') && {
|
|
107
|
+
id: 'copy',
|
|
108
|
+
label: 'Copy',
|
|
109
|
+
shortcut: [['Control', 'C']],
|
|
110
|
+
enabled: selected,
|
|
111
|
+
},
|
|
112
|
+
has('paste') && {
|
|
113
|
+
id: 'paste',
|
|
114
|
+
label: 'Paste',
|
|
115
|
+
shortcut: [['Control', 'V']],
|
|
116
|
+
enabled: canPaste,
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
[
|
|
120
|
+
has('selectAll') && {
|
|
121
|
+
id: 'selectAll',
|
|
122
|
+
label: 'Select All',
|
|
123
|
+
shortcut: [['Control', 'A']],
|
|
124
|
+
// the default is "there is something to select": only the target
|
|
125
|
+
// knows when everything already is
|
|
126
|
+
enabled: actions.canSelectAll !== false,
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
]
|
|
130
|
+
.map((group) => group.filter(Boolean))
|
|
131
|
+
.filter((group) => group.length > 0);
|
|
132
|
+
// a separator between the groups that survived, and never one at either
|
|
133
|
+
// end — a menu missing a whole group must not show the gap where it was
|
|
134
|
+
return groups.flatMap((group, i) =>
|
|
135
|
+
i === 0 ? group : [{ type: 'separator' }, ...group],
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Lay the rows out and size the popup that holds them.
|
|
141
|
+
*
|
|
142
|
+
* @param {Array<{id?, label?, shortcut?, type?, enabled?}>} items
|
|
143
|
+
* @param {(text: string) => number|null} [measure] text width, or null when
|
|
144
|
+
* nothing can be measured yet
|
|
145
|
+
*/
|
|
146
|
+
export function editMenuGeometry(items, measure) {
|
|
147
|
+
const widthOf = (text) => {
|
|
148
|
+
if (!text) return 0;
|
|
149
|
+
const w = measure?.(text);
|
|
150
|
+
return typeof w === 'number' && w > 0
|
|
151
|
+
? w
|
|
152
|
+
: text.length * FALLBACK_CHAR_WIDTH;
|
|
153
|
+
};
|
|
154
|
+
const rows = [];
|
|
155
|
+
let y = PAD_Y;
|
|
156
|
+
let widest = 0;
|
|
157
|
+
for (const item of items) {
|
|
158
|
+
const separator = isSeparator(item);
|
|
159
|
+
const shortcut = formatShortcut(item.shortcut);
|
|
160
|
+
const height = separator ? SEPARATOR_HEIGHT : ROW_HEIGHT;
|
|
161
|
+
rows.push({ ...item, separator, shortcut, y, height });
|
|
162
|
+
y += height;
|
|
163
|
+
if (separator) continue;
|
|
164
|
+
widest = Math.max(
|
|
165
|
+
widest,
|
|
166
|
+
widthOf(item.label) + (shortcut ? SHORTCUT_GAP + widthOf(shortcut) : 0),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
rows,
|
|
171
|
+
width: Math.max(MIN_WIDTH, Math.ceil(widest + PAD_X * 2)),
|
|
172
|
+
height: Math.ceil(y + PAD_Y),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Row under a y coordinate, or -1. Separators and disabled rows are not
|
|
177
|
+
* hoverable — moving onto one clears the highlight rather than leaving a
|
|
178
|
+
* stale row lit. */
|
|
179
|
+
export function editMenuIndexAt(geometry, y) {
|
|
180
|
+
for (let i = 0; i < geometry.rows.length; i++) {
|
|
181
|
+
const row = geometry.rows[i];
|
|
182
|
+
if (row.separator || row.enabled === false) continue;
|
|
183
|
+
if (y >= row.y && y < row.y + row.height) return i;
|
|
184
|
+
}
|
|
185
|
+
return -1;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** The next selectable row in `dir`, wrapping, or -1 if there are none. */
|
|
189
|
+
export function editMenuStep(geometry, from, dir) {
|
|
190
|
+
const rows = geometry.rows;
|
|
191
|
+
if (rows.length === 0) return -1;
|
|
192
|
+
let i = from < 0 ? (dir > 0 ? -1 : rows.length) : from;
|
|
193
|
+
for (let k = 0; k < rows.length; k++) {
|
|
194
|
+
i += dir;
|
|
195
|
+
if (i < 0) i = rows.length - 1;
|
|
196
|
+
if (i >= rows.length) i = 0;
|
|
197
|
+
const row = rows[i];
|
|
198
|
+
if (!row.separator && row.enabled !== false) return i;
|
|
199
|
+
}
|
|
200
|
+
return -1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function panelPath(ctx, width, height, radius) {
|
|
204
|
+
ctx.beginPath();
|
|
205
|
+
if (radius > 0 && typeof ctx.roundRect === 'function') {
|
|
206
|
+
ctx.roundRect(0.5, 0.5, width - 1, height - 1, radius);
|
|
207
|
+
} else {
|
|
208
|
+
ctx.rect(0.5, 0.5, width - 1, height - 1);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @param {object} ctx ntk 2d context, translated to the popup's origin
|
|
214
|
+
* @param {object} opts
|
|
215
|
+
* @param {(text: string, color: string) => object|null} opts.layoutOf shaped
|
|
216
|
+
* text, or null when there are no fonts — the rows still paint, unlabelled
|
|
217
|
+
*/
|
|
218
|
+
export function paintEditMenu(
|
|
219
|
+
ctx,
|
|
220
|
+
{ geometry, active = -1, colors = EDIT_MENU_COLORS, layoutOf, radius = 4 },
|
|
221
|
+
) {
|
|
222
|
+
const { width, height, rows } = geometry;
|
|
223
|
+
|
|
224
|
+
panelPath(ctx, width, height, radius);
|
|
225
|
+
ctx.fillStyle = colors.background;
|
|
226
|
+
ctx.fill();
|
|
227
|
+
ctx.strokeStyle = colors.border;
|
|
228
|
+
ctx.lineWidth = 1;
|
|
229
|
+
ctx.stroke();
|
|
230
|
+
|
|
231
|
+
const label = (text, color, x, row) => {
|
|
232
|
+
const layout = layoutOf?.(text, color);
|
|
233
|
+
if (!layout) return;
|
|
234
|
+
const line = layout.lines?.[0];
|
|
235
|
+
const ink = line ? line.ascent + line.descent : layout.height;
|
|
236
|
+
layout.draw(ctx, x, row.y + Math.max(0, (row.height - ink) / 2));
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
for (let i = 0; i < rows.length; i++) {
|
|
240
|
+
const row = rows[i];
|
|
241
|
+
if (row.separator) {
|
|
242
|
+
ctx.fillStyle = colors.separator;
|
|
243
|
+
ctx.fillRect(
|
|
244
|
+
PAD_X,
|
|
245
|
+
Math.round(row.y + row.height / 2) + 0.5,
|
|
246
|
+
width - PAD_X * 2,
|
|
247
|
+
1,
|
|
248
|
+
);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const disabled = row.enabled === false;
|
|
252
|
+
const on = i === active && !disabled;
|
|
253
|
+
if (on) {
|
|
254
|
+
ctx.fillStyle = colors.highlight;
|
|
255
|
+
ctx.fillRect(1, row.y, width - 2, row.height);
|
|
256
|
+
}
|
|
257
|
+
const color = disabled
|
|
258
|
+
? colors.dim
|
|
259
|
+
: on
|
|
260
|
+
? colors.highlightText
|
|
261
|
+
: colors.text;
|
|
262
|
+
label(row.label, color, PAD_X, row);
|
|
263
|
+
if (row.shortcut) {
|
|
264
|
+
// the shortcut column stays quieter than the label, except in the
|
|
265
|
+
// highlighted row where it has to stay legible on the accent
|
|
266
|
+
const shortcutColor = on ? colors.highlightText : colors.dim;
|
|
267
|
+
const layout = layoutOf?.(row.shortcut, shortcutColor);
|
|
268
|
+
const w = layout?.width ?? 0;
|
|
269
|
+
label(row.shortcut, shortcutColor, width - PAD_X - w, row);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Where a throw from user code goes when React is not on the stack.
|
|
2
|
+
//
|
|
3
|
+
// An event handler runs from an X event, not from a render, so no error
|
|
4
|
+
// boundary can catch it — a boundary only sees throws React itself invoked.
|
|
5
|
+
// Left bare, the throw unwinds out of the dispatcher into ntk's socket
|
|
6
|
+
// `data` handler and takes the process with it, or wedges the frame loop,
|
|
7
|
+
// which is worse: it looks like a hang rather than a crash.
|
|
8
|
+
|
|
9
|
+
// container -> the root's onUncaughtError, when it set one
|
|
10
|
+
const perContainer = new WeakMap();
|
|
11
|
+
|
|
12
|
+
export function setErrorHandler(container, fn) {
|
|
13
|
+
if (fn) perContainer.set(container, fn);
|
|
14
|
+
else perContainer.delete(container);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The component that rendered this node, which is the name worth printing
|
|
18
|
+
* — `<box>` alone never tells anyone whose box it was. */
|
|
19
|
+
export function ownerName(node) {
|
|
20
|
+
const type = node?._reactFiber?._debugOwner?.type;
|
|
21
|
+
if (!type) return null;
|
|
22
|
+
return type.displayName || type.name || '(anonymous)';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A GUI process whose tree threw must not exit 0 — that lies to CI and to
|
|
26
|
+
* a supervisor. Guarded because the playground bundle runs in a browser. */
|
|
27
|
+
function markFailed() {
|
|
28
|
+
if (typeof process !== 'undefined' && process) process.exitCode = 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Report a throw from a user callback, then carry on.
|
|
33
|
+
*
|
|
34
|
+
* Carrying on is the point: one bad tooltip handler must not stop the rest
|
|
35
|
+
* of the dispatch or the frame loop. The default still refuses to swallow —
|
|
36
|
+
* it logs what threw and where, and sets `process.exitCode`. A root that
|
|
37
|
+
* passed `onUncaughtError` decides for itself, because for a kiosk "crash
|
|
38
|
+
* loudly" is the right answer and for a text editor it is not.
|
|
39
|
+
*/
|
|
40
|
+
export function reportHandlerError(node, handler, error) {
|
|
41
|
+
const element = node?.kind ? `<${node.kind}>` : '(unknown)';
|
|
42
|
+
const owner = ownerName(node);
|
|
43
|
+
const custom = node?.app && perContainer.get(node.app);
|
|
44
|
+
if (custom) {
|
|
45
|
+
custom(error, {
|
|
46
|
+
componentStack: owner ? `\n in ${owner}` : undefined,
|
|
47
|
+
element,
|
|
48
|
+
handler,
|
|
49
|
+
node,
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
console.error(
|
|
54
|
+
`react-x11: ${handler} on ${element}${owner ? ` in ${owner}` : ''} threw. ` +
|
|
55
|
+
'It ran from an X event rather than a render, so no error boundary ' +
|
|
56
|
+
'could catch it; dispatch continues.',
|
|
57
|
+
error,
|
|
58
|
+
);
|
|
59
|
+
markFailed();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Wrap a call to user code so a throw is reported instead of escaping. */
|
|
63
|
+
export function callHandler(node, handler, fn, ev) {
|
|
64
|
+
try {
|
|
65
|
+
fn(ev);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
reportHandlerError(node, handler, error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The root error callbacks React itself invokes, as `(error, errorInfo)`.
|
|
73
|
+
* `errorInfo.componentStack` is the whole reason a boundary is debuggable,
|
|
74
|
+
* and printing it beside the error is most of what makes these useful.
|
|
75
|
+
*/
|
|
76
|
+
export const defaultRootHandlers = {
|
|
77
|
+
onUncaughtError(error, errorInfo) {
|
|
78
|
+
console.error(
|
|
79
|
+
'react-x11: uncaught error' + (errorInfo?.componentStack ?? ''),
|
|
80
|
+
error,
|
|
81
|
+
);
|
|
82
|
+
markFailed();
|
|
83
|
+
},
|
|
84
|
+
onCaughtError(error, errorInfo) {
|
|
85
|
+
// an error boundary handled this one, so the process is still healthy
|
|
86
|
+
console.error(
|
|
87
|
+
'react-x11: error caught by a boundary' +
|
|
88
|
+
(errorInfo?.componentStack ?? ''),
|
|
89
|
+
error,
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
onRecoverableError(error, errorInfo) {
|
|
93
|
+
console.error(
|
|
94
|
+
'react-x11: recoverable error' + (errorInfo?.componentStack ?? ''),
|
|
95
|
+
error,
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
};
|