react-x11 2.48.0 → 2.50.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 +9 -8
- package/package.json +7 -4
- package/src/ClickToComponent.js +11 -132
- package/src/Reconciler.js +4 -1
- package/src/cli/dev.js +578 -0
- package/src/cli/errorwindow.js +368 -0
- package/src/cli/index.js +81 -0
- package/src/cocoa/app.js +12 -3
- package/src/cocoa/presenter.js +3 -1
- package/src/cocoa/promotion.js +10 -11
- package/src/cocoa/relaunch.js +110 -13
- package/src/currentcolor.js +28 -0
- package/src/decorations.js +21 -9
- package/src/editor.js +151 -0
- package/src/errors.js +8 -0
- package/src/foreignnodes.js +8 -1
- package/src/gloverlay.js +2 -1
- package/src/node.d.ts +5 -0
- package/src/nodes/animation.js +31 -3
- package/src/nodes/boxpaint.js +34 -8
- package/src/nodes/cascade.js +50 -3
- package/src/nodes/damage.js +2 -0
- package/src/nodes/layout.js +9 -17
- package/src/nodes/moveblit.js +5 -5
- package/src/nodes/node.js +8 -0
- package/src/nodes/paint.js +4 -2
- package/src/nodes/queries.js +46 -34
- package/src/nodes/scrollblit.js +10 -7
- package/src/nodes/styling.js +176 -21
- package/src/nodes/window/capabilities.js +1 -1
- package/src/nodes/window/floors.js +63 -18
- package/src/nodes/window/size.js +94 -19
- package/src/refresh/loader.d.ts +16 -0
- package/src/refresh/loader.js +84 -14
- package/src/refresh/register.js +8 -5
- package/src/shorthands.js +1266 -0
- package/src/styles.js +370 -131
- package/src/types/style.d.ts +163 -39
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
// The window `react-x11 dev` shows an error in: what was thrown, the source
|
|
2
|
+
// around where, and the frames of the stack, each of which opens its line in
|
|
3
|
+
// the editor (src/editor.js, the one click-to-component uses).
|
|
4
|
+
//
|
|
5
|
+
// Two kinds of error reach it. A **render** error takes the app's tree down —
|
|
6
|
+
// React unmounts a root whose render threw and nothing caught — so this
|
|
7
|
+
// window stands in for the app until the next save mounts it again. A
|
|
8
|
+
// **compile** error is a save that did not compile: the app keeps running the
|
|
9
|
+
// module's last version, and this window opens beside it until a save
|
|
10
|
+
// compiles.
|
|
11
|
+
//
|
|
12
|
+
// Everything the window says comes out of `describeError`, which is pure, so
|
|
13
|
+
// the tests can read it without rendering anything.
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
import { relative } from 'node:path';
|
|
16
|
+
import React from 'react';
|
|
17
|
+
import { editorName, openInEditor, stackFrames } from '../editor.js';
|
|
18
|
+
|
|
19
|
+
const h = React.createElement;
|
|
20
|
+
|
|
21
|
+
/** Lines of source shown above and below the one that threw. */
|
|
22
|
+
const CONTEXT_ABOVE = 3;
|
|
23
|
+
const CONTEXT_BELOW = 2;
|
|
24
|
+
|
|
25
|
+
function firstLine(text) {
|
|
26
|
+
const at = text.indexOf('\n');
|
|
27
|
+
return at === -1 ? text : text.slice(0, at);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The source lines around `line` (1-based) of `file`: `{ number, text, at }`
|
|
32
|
+
* each, `at` marking the one that threw. Null when the file cannot be read.
|
|
33
|
+
*/
|
|
34
|
+
function excerptOf(file, line) {
|
|
35
|
+
let source;
|
|
36
|
+
try {
|
|
37
|
+
source = readFileSync(file, 'utf8');
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const lines = source.split('\n');
|
|
42
|
+
if (line < 1 || line > lines.length) return null;
|
|
43
|
+
const from = Math.max(1, line - CONTEXT_ABOVE);
|
|
44
|
+
const to = Math.min(lines.length, line + CONTEXT_BELOW);
|
|
45
|
+
const excerpt = [];
|
|
46
|
+
for (let number = from; number <= to; number++) {
|
|
47
|
+
excerpt.push({
|
|
48
|
+
number,
|
|
49
|
+
// a tab is as wide as its font says, and two spaces is what an editor
|
|
50
|
+
// with a two-space indent shows
|
|
51
|
+
text: lines[number - 1].replace(/\t/g, ' ').replace(/\r$/, ''),
|
|
52
|
+
at: number === line,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return excerpt;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Everything the window says about one failure, as data.
|
|
60
|
+
*
|
|
61
|
+
* `failure` is `{ kind, error, filename?, componentStack? }`: `kind` is
|
|
62
|
+
* `'render'` or `'compile'`, `filename` the module that did not compile, and
|
|
63
|
+
* `componentStack` what React handed `onUncaughtError`.
|
|
64
|
+
*/
|
|
65
|
+
export function describeError(failure, cwd = process.cwd()) {
|
|
66
|
+
const { kind, error, filename = null, componentStack = '' } = failure;
|
|
67
|
+
const rel = (file) => relative(cwd, file) || file;
|
|
68
|
+
const message = String(error?.message ?? error);
|
|
69
|
+
const name = error?.name && error.name !== 'Error' ? error.name : 'Error';
|
|
70
|
+
|
|
71
|
+
let heading;
|
|
72
|
+
let detail = '';
|
|
73
|
+
let location = null;
|
|
74
|
+
if (kind === 'compile') {
|
|
75
|
+
// babel says `<file>: <what> (<line>:<column>)` and then draws a frame;
|
|
76
|
+
// the window draws its own from `loc`, when there is one
|
|
77
|
+
let what = firstLine(message);
|
|
78
|
+
if (filename && what.startsWith(`${filename}: `)) {
|
|
79
|
+
what = what.slice(filename.length + 2);
|
|
80
|
+
}
|
|
81
|
+
what = what.replace(/ \(\d+:\d+\)$/, '');
|
|
82
|
+
heading = `${name}: ${what}`;
|
|
83
|
+
if (filename && error?.loc?.line) {
|
|
84
|
+
location = {
|
|
85
|
+
file: filename,
|
|
86
|
+
line: error.loc.line,
|
|
87
|
+
column: (error.loc.column ?? 0) + 1,
|
|
88
|
+
};
|
|
89
|
+
} else {
|
|
90
|
+
detail = message.slice(firstLine(message).length).replace(/^\n+/, '');
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
heading = `${name}: ${firstLine(message)}`;
|
|
94
|
+
detail = message.slice(firstLine(message).length).replace(/^\n+/, '');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A compile error's stack is the compiler's: where in babel the parse
|
|
98
|
+
// gave up, which says nothing about the app.
|
|
99
|
+
const all = kind === 'compile' ? [] : stackFrames(error?.stack);
|
|
100
|
+
const shown = all.filter((frame) => !frame.internal);
|
|
101
|
+
if (!location) {
|
|
102
|
+
const own = shown.find((frame) => !frame.installed) ?? shown[0];
|
|
103
|
+
if (own) {
|
|
104
|
+
location = { file: own.file, line: own.line, column: own.column };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// React's component stack, innermost first: `at Broken (…)`. Host
|
|
109
|
+
// elements are in it too; the breadcrumb is the components.
|
|
110
|
+
const components = [];
|
|
111
|
+
for (const text of String(componentStack).split('\n')) {
|
|
112
|
+
const match = text.match(/^\s*at ([A-Z][\w$.]*)/);
|
|
113
|
+
if (match) components.push(match[1]);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
kind,
|
|
118
|
+
heading,
|
|
119
|
+
detail,
|
|
120
|
+
location: location && {
|
|
121
|
+
...location,
|
|
122
|
+
where: `${rel(location.file)}:${location.line}:${location.column}`,
|
|
123
|
+
},
|
|
124
|
+
excerpt: location ? excerptOf(location.file, location.line) : null,
|
|
125
|
+
frames: shown.map((frame) => ({
|
|
126
|
+
label: frame.functionName ?? '(anonymous)',
|
|
127
|
+
where: `${rel(frame.file)}:${frame.line}:${frame.column}`,
|
|
128
|
+
file: frame.file,
|
|
129
|
+
line: frame.line,
|
|
130
|
+
column: frame.column,
|
|
131
|
+
own: !frame.installed,
|
|
132
|
+
})),
|
|
133
|
+
hidden: all.length - shown.length,
|
|
134
|
+
components: components.reverse(),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const mono = { fontFamily: '$monoFamily', fontSize: 12 };
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* A row that opens a source line: drawn pressed on the press and hovered
|
|
142
|
+
* under the pointer, because a click that opens another application takes a
|
|
143
|
+
* moment to show anything of its own.
|
|
144
|
+
*/
|
|
145
|
+
function SourceLink({ target, children }) {
|
|
146
|
+
const open = () => openInEditor(target);
|
|
147
|
+
return h(
|
|
148
|
+
'box',
|
|
149
|
+
{
|
|
150
|
+
focusable: true,
|
|
151
|
+
role: 'link',
|
|
152
|
+
onClick: open,
|
|
153
|
+
onKeyDown: (ev) => {
|
|
154
|
+
if (ev.key === 'Enter') open();
|
|
155
|
+
},
|
|
156
|
+
style: {
|
|
157
|
+
flexDirection: 'row',
|
|
158
|
+
gap: 10,
|
|
159
|
+
paddingTop: 3,
|
|
160
|
+
paddingBottom: 3,
|
|
161
|
+
paddingStart: 6,
|
|
162
|
+
paddingEnd: 6,
|
|
163
|
+
borderRadius: '$radiusSmall',
|
|
164
|
+
cursor: 'pointer',
|
|
165
|
+
':hover': { backgroundColor: '$surfaceHover' },
|
|
166
|
+
':active': { backgroundColor: '$surfaceActive' },
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
children,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function Excerpt({ excerpt, column }) {
|
|
174
|
+
const width = String(excerpt[excerpt.length - 1].number).length;
|
|
175
|
+
const rows = [];
|
|
176
|
+
for (const { number, text, at } of excerpt) {
|
|
177
|
+
rows.push(
|
|
178
|
+
h(
|
|
179
|
+
'box',
|
|
180
|
+
{ key: number, style: { flexDirection: 'row' } },
|
|
181
|
+
h(
|
|
182
|
+
'text',
|
|
183
|
+
{
|
|
184
|
+
style: {
|
|
185
|
+
...mono,
|
|
186
|
+
color: at ? '$danger' : '$textMuted',
|
|
187
|
+
textWrap: 'nowrap',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
`${at ? '>' : ' '} ${String(number).padStart(width)} │ `,
|
|
191
|
+
),
|
|
192
|
+
h(
|
|
193
|
+
'text',
|
|
194
|
+
{
|
|
195
|
+
style: {
|
|
196
|
+
...mono,
|
|
197
|
+
color: at ? '$text' : '$textMuted',
|
|
198
|
+
textWrap: 'nowrap',
|
|
199
|
+
flexShrink: 1,
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
text || ' ',
|
|
203
|
+
),
|
|
204
|
+
),
|
|
205
|
+
);
|
|
206
|
+
if (at && column > 0) {
|
|
207
|
+
rows.push(
|
|
208
|
+
h(
|
|
209
|
+
'text',
|
|
210
|
+
{
|
|
211
|
+
key: 'caret',
|
|
212
|
+
style: { ...mono, color: '$danger', textWrap: 'nowrap' },
|
|
213
|
+
},
|
|
214
|
+
`${' '.repeat(width + 5 + column - 1)}^`,
|
|
215
|
+
),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return h(
|
|
220
|
+
'box',
|
|
221
|
+
{
|
|
222
|
+
style: {
|
|
223
|
+
backgroundColor: '$surfaceHover',
|
|
224
|
+
borderRadius: '$radius',
|
|
225
|
+
paddingTop: 10,
|
|
226
|
+
paddingBottom: 10,
|
|
227
|
+
paddingStart: 12,
|
|
228
|
+
paddingEnd: 12,
|
|
229
|
+
overflow: 'hidden',
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
rows,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The window itself. `onCloseRequest` is the host's to give: without one,
|
|
238
|
+
* closing the only window there is ends the app, as closing an app's primary
|
|
239
|
+
* window does.
|
|
240
|
+
*/
|
|
241
|
+
export function ErrorWindow({ failure, title, onCloseRequest }) {
|
|
242
|
+
const info = describeError(failure);
|
|
243
|
+
const muted = { color: '$textMuted', fontSize: 12 };
|
|
244
|
+
const editor = editorName();
|
|
245
|
+
const children = [
|
|
246
|
+
h(
|
|
247
|
+
'text',
|
|
248
|
+
{ key: 'kind', style: { ...muted, fontWeight: 600 } },
|
|
249
|
+
info.kind === 'compile'
|
|
250
|
+
? 'This save did not compile. The app keeps running the last version that did.'
|
|
251
|
+
: 'The app threw while rendering, and React took its tree down.',
|
|
252
|
+
),
|
|
253
|
+
h(
|
|
254
|
+
'text',
|
|
255
|
+
{
|
|
256
|
+
key: 'heading',
|
|
257
|
+
style: { color: '$danger', fontSize: 16, fontWeight: 600 },
|
|
258
|
+
},
|
|
259
|
+
info.heading,
|
|
260
|
+
),
|
|
261
|
+
];
|
|
262
|
+
if (info.components.length > 0) {
|
|
263
|
+
children.push(
|
|
264
|
+
h(
|
|
265
|
+
'text',
|
|
266
|
+
{ key: 'components', style: muted },
|
|
267
|
+
info.components.join(' › '),
|
|
268
|
+
),
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if (info.detail) {
|
|
272
|
+
children.push(
|
|
273
|
+
h(
|
|
274
|
+
'text',
|
|
275
|
+
{ key: 'detail', style: { ...mono, color: '$text' } },
|
|
276
|
+
info.detail,
|
|
277
|
+
),
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
if (info.location) {
|
|
281
|
+
children.push(
|
|
282
|
+
h(
|
|
283
|
+
'box',
|
|
284
|
+
{ key: 'source', style: { gap: 6 } },
|
|
285
|
+
h(
|
|
286
|
+
SourceLink,
|
|
287
|
+
{ target: info.location },
|
|
288
|
+
h(
|
|
289
|
+
'text',
|
|
290
|
+
{ style: { ...mono, color: '$link' } },
|
|
291
|
+
info.location.where,
|
|
292
|
+
),
|
|
293
|
+
),
|
|
294
|
+
info.excerpt
|
|
295
|
+
? h(Excerpt, { excerpt: info.excerpt, column: info.location.column })
|
|
296
|
+
: null,
|
|
297
|
+
),
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
if (info.frames.length > 0) {
|
|
301
|
+
children.push(
|
|
302
|
+
h(
|
|
303
|
+
'box',
|
|
304
|
+
{ key: 'frames', style: { gap: 1 } },
|
|
305
|
+
h(
|
|
306
|
+
'text',
|
|
307
|
+
{ style: { ...muted, fontWeight: 600, marginBottom: 4 } },
|
|
308
|
+
'Stack',
|
|
309
|
+
),
|
|
310
|
+
info.frames.map((frame, i) =>
|
|
311
|
+
h(
|
|
312
|
+
SourceLink,
|
|
313
|
+
{ key: i, target: frame },
|
|
314
|
+
h(
|
|
315
|
+
'text',
|
|
316
|
+
{
|
|
317
|
+
style: {
|
|
318
|
+
...mono,
|
|
319
|
+
color: frame.own ? '$text' : '$textMuted',
|
|
320
|
+
fontWeight: frame.own ? 600 : 400,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
frame.label,
|
|
324
|
+
),
|
|
325
|
+
h(
|
|
326
|
+
'text',
|
|
327
|
+
{ style: { ...mono, color: '$textMuted', flexShrink: 1 } },
|
|
328
|
+
frame.where,
|
|
329
|
+
),
|
|
330
|
+
),
|
|
331
|
+
),
|
|
332
|
+
info.hidden > 0
|
|
333
|
+
? h(
|
|
334
|
+
'text',
|
|
335
|
+
{ style: { ...muted, marginTop: 4, marginLeft: 6 } },
|
|
336
|
+
`${info.hidden} more ${info.hidden === 1 ? 'frame' : 'frames'} in React, Node and react-x11, not shown.`,
|
|
337
|
+
)
|
|
338
|
+
: null,
|
|
339
|
+
),
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
children.push(
|
|
343
|
+
h(
|
|
344
|
+
'text',
|
|
345
|
+
{ key: 'footer', style: { ...muted, marginTop: 4 } },
|
|
346
|
+
(info.kind === 'compile'
|
|
347
|
+
? 'Fix it and save: this window goes once the file compiles. '
|
|
348
|
+
: 'Fix it and save: the app is mounted again on the next reload. ') +
|
|
349
|
+
`Click a line to open it in ${editor}` +
|
|
350
|
+
(process.env.REACT_X11_EDITOR
|
|
351
|
+
? '.'
|
|
352
|
+
: '; REACT_X11_EDITOR names another editor.'),
|
|
353
|
+
),
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
return h(
|
|
357
|
+
'window',
|
|
358
|
+
{ title, width: 760, minHeight: 240, onCloseRequest },
|
|
359
|
+
h(
|
|
360
|
+
'box',
|
|
361
|
+
{
|
|
362
|
+
selectable: true,
|
|
363
|
+
style: { flexGrow: 1, padding: 20, gap: 14, overflow: 'scroll' },
|
|
364
|
+
},
|
|
365
|
+
children,
|
|
366
|
+
),
|
|
367
|
+
);
|
|
368
|
+
}
|
package/src/cli/index.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// react-x11's command line, the `bin` of the package. One command so far:
|
|
3
|
+
//
|
|
4
|
+
// react-x11 dev <file> [args…] hot reload with nothing to set up (./dev.js)
|
|
5
|
+
//
|
|
6
|
+
// Arguments before the file belong to the command, and everything after it
|
|
7
|
+
// to the app, which sees them in process.argv as `node <file> …args` would
|
|
8
|
+
// have handed them over.
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { DevError, runDev } from './dev.js';
|
|
11
|
+
|
|
12
|
+
const USAGE = `Usage: react-x11 dev <file> [args…]
|
|
13
|
+
|
|
14
|
+
Commands:
|
|
15
|
+
dev <file> Run <file> with hot reload: saving it, or a .jsx module it
|
|
16
|
+
imports, updates the running app in place. Its default
|
|
17
|
+
export is mounted, inside a window of its own when it
|
|
18
|
+
renders none; a file that mounts its own root runs as it is.
|
|
19
|
+
Arguments after the file are the app's.
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
-h, --help Show this help.
|
|
23
|
+
-v, --version Print react-x11's version.
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
function version() {
|
|
27
|
+
const manifest = new URL('../../package.json', import.meta.url);
|
|
28
|
+
return JSON.parse(readFileSync(manifest, 'utf8')).version;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** An exit code to finish with, or undefined while the app runs. */
|
|
32
|
+
async function main([command, ...rest]) {
|
|
33
|
+
if (command === '-h' || command === '--help' || command === 'help') {
|
|
34
|
+
process.stdout.write(USAGE);
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
if (command === '-v' || command === '--version') {
|
|
38
|
+
process.stdout.write(`${version()}\n`);
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
if (command === undefined) {
|
|
42
|
+
process.stderr.write(USAGE);
|
|
43
|
+
return 2;
|
|
44
|
+
}
|
|
45
|
+
if (command !== 'dev') {
|
|
46
|
+
process.stderr.write(`react-x11: no command "${command}".\n\n${USAGE}`);
|
|
47
|
+
return 2;
|
|
48
|
+
}
|
|
49
|
+
const at = rest.findIndex((arg) => !arg.startsWith('-'));
|
|
50
|
+
const own = at === -1 ? rest : rest.slice(0, at);
|
|
51
|
+
if (own.includes('-h') || own.includes('--help')) {
|
|
52
|
+
process.stdout.write(USAGE);
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
if (own.length > 0) {
|
|
56
|
+
process.stderr.write(
|
|
57
|
+
`react-x11 dev: no option ${own[0]} — options after the file are ` +
|
|
58
|
+
`the app's.\n\n${USAGE}`,
|
|
59
|
+
);
|
|
60
|
+
return 2;
|
|
61
|
+
}
|
|
62
|
+
if (at === -1) {
|
|
63
|
+
process.stderr.write(`react-x11 dev: which file?\n\n${USAGE}`);
|
|
64
|
+
return 2;
|
|
65
|
+
}
|
|
66
|
+
await runDev(rest[at], { args: rest.slice(at + 1) });
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const code = await main(process.argv.slice(2));
|
|
72
|
+
if (code !== undefined) process.exitCode = code;
|
|
73
|
+
} catch (err) {
|
|
74
|
+
// A DevError is a sentence about the setup, and its stack is ours; any
|
|
75
|
+
// other throw came out of the app's own module, and its stack is the
|
|
76
|
+
// useful part.
|
|
77
|
+
console.error(
|
|
78
|
+
err instanceof DevError ? `react-x11 dev: ${err.message}` : err,
|
|
79
|
+
);
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
}
|
package/src/cocoa/app.js
CHANGED
|
@@ -178,7 +178,17 @@ export class CocoaApp {
|
|
|
178
178
|
// policy goes first, through `initApp`'s option (bridge >= 0.5), and
|
|
179
179
|
// only when the root asked for one: a bridge without the option, or a
|
|
180
180
|
// fake, is left alone.
|
|
181
|
-
|
|
181
|
+
//
|
|
182
|
+
// A `<Frame>` pane asks for one without being told: 'prohibited'. Its
|
|
183
|
+
// pixels are the host's to show, it makes no NSWindow (`createWindow`)
|
|
184
|
+
// and it runs no NSApplication, so nothing of it may be an app the user
|
|
185
|
+
// sees — and launched Regular, the bridge's default, every pane was a
|
|
186
|
+
// `node` tile of its own in the Dock and in ⌘-Tab, one per tab of a
|
|
187
|
+
// browser. Prohibited rather than accessory because a pane must not be
|
|
188
|
+
// activated either: the host window has the focus and forwards it.
|
|
189
|
+
const pane = options.pane ?? process.env.REACT_X11_FRAME === '1';
|
|
190
|
+
const policy =
|
|
191
|
+
options.cocoa?.activationPolicy ?? (pane ? 'prohibited' : undefined);
|
|
182
192
|
// Under the launcher (src/cocoa/main.js) the app launched before this
|
|
183
193
|
// code ran, so the policy is a switch after the fact, and a Regular
|
|
184
194
|
// launch has already registered its Dock tile. The bridge publishes
|
|
@@ -257,7 +267,6 @@ export class CocoaApp {
|
|
|
257
267
|
// here then (src/cocoa/paneplayer.js); one that does not leaves a pane
|
|
258
268
|
// refusing `src`, its poster shown and `onError` told.
|
|
259
269
|
this._players = new Map();
|
|
260
|
-
const pane = options.pane ?? process.env.REACT_X11_FRAME === '1';
|
|
261
270
|
if (canPlay(native) && !pane) {
|
|
262
271
|
this.createPlayer = (src, options) => new CocoaPlayer(this, src, options);
|
|
263
272
|
}
|
|
@@ -388,7 +397,7 @@ export class CocoaApp {
|
|
|
388
397
|
|
|
389
398
|
// Frame-pane mode: this process renders a pane whose pixels a host
|
|
390
399
|
// composites (REACT_X11_FRAME is what the Frame host sets on the fork).
|
|
391
|
-
this._paneMode =
|
|
400
|
+
this._paneMode = pane;
|
|
392
401
|
this._paneSend = null;
|
|
393
402
|
// the pane's own window, the one the host lays out and forwards to
|
|
394
403
|
this._paneWindow = null;
|
package/src/cocoa/presenter.js
CHANGED
|
@@ -502,7 +502,9 @@ export function propBoxProps(node, app, scale, parentOrigin, order) {
|
|
|
502
502
|
const style = node.style ?? {};
|
|
503
503
|
const border = typeof style.borderWidth === 'number' ? style.borderWidth : 0;
|
|
504
504
|
const colour = (value) =>
|
|
505
|
-
value
|
|
505
|
+
value
|
|
506
|
+
? (app._parseColor(String(node.resolveColor(value))) ?? [0, 0, 0, 0])
|
|
507
|
+
: [0, 0, 0, 0];
|
|
506
508
|
return {
|
|
507
509
|
frame: [
|
|
508
510
|
(abs.x - parentOrigin.x) / scale,
|
package/src/cocoa/promotion.js
CHANGED
|
@@ -42,7 +42,7 @@ import { now } from '../nodes/animation.js';
|
|
|
42
42
|
import { BoxNode } from '../nodes/box.js';
|
|
43
43
|
import { addDamageRect, damageToPaint } from '../nodes/damage.js';
|
|
44
44
|
import { intersectRects } from '../nodes/rects.js';
|
|
45
|
-
import { resolveBorderWidths } from '../styles.js';
|
|
45
|
+
import { radiusOf, resolveBorderWidths } from '../styles.js';
|
|
46
46
|
import {
|
|
47
47
|
LayerAnimations,
|
|
48
48
|
RASTER_PAD,
|
|
@@ -841,7 +841,7 @@ export class CocoaPromotion {
|
|
|
841
841
|
cut &&
|
|
842
842
|
!parent.isWindow &&
|
|
843
843
|
parent.clipsChildren?.() &&
|
|
844
|
-
!(parent.style
|
|
844
|
+
!(radiusOf(parent.style, parent.abs) > 0)
|
|
845
845
|
) {
|
|
846
846
|
const inner = insetRect(parent.abs, borderReach(parent));
|
|
847
847
|
// a clip with round corners cut again is no one box's shape: the
|
|
@@ -872,13 +872,15 @@ export class CocoaPromotion {
|
|
|
872
872
|
!cut.clip &&
|
|
873
873
|
!parent.isWindow &&
|
|
874
874
|
parent.clipsChildren?.() &&
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
875
|
+
radiusOf(parent.style, parent.abs) > 0 &&
|
|
876
|
+
!containsRect(
|
|
877
|
+
insetRect(parent.abs, radiusOf(parent.style, parent.abs)),
|
|
878
|
+
bounds,
|
|
879
|
+
)
|
|
878
880
|
) {
|
|
879
881
|
const border = borderReach(parent);
|
|
880
882
|
cut.clip = insetRect(parent.abs, border);
|
|
881
|
-
cut.radius = Math.max(0, parent.style.
|
|
883
|
+
cut.radius = Math.max(0, radiusOf(parent.style, parent.abs) - border);
|
|
882
884
|
roundCut = true;
|
|
883
885
|
bounds = intersectRects(bounds, cut.clip);
|
|
884
886
|
if (!bounds) {
|
|
@@ -906,11 +908,8 @@ export class CocoaPromotion {
|
|
|
906
908
|
return false;
|
|
907
909
|
}
|
|
908
910
|
if (parent.clipsChildren?.() && !roundCut) {
|
|
909
|
-
const radius = parent.style
|
|
910
|
-
const clip =
|
|
911
|
-
typeof radius === 'number' && radius > 0
|
|
912
|
-
? insetRect(parent.abs, radius)
|
|
913
|
-
: parent.abs;
|
|
911
|
+
const radius = radiusOf(parent.style, parent.abs);
|
|
912
|
+
const clip = radius > 0 ? insetRect(parent.abs, radius) : parent.abs;
|
|
914
913
|
if (!containsRect(clip, bounds)) return false;
|
|
915
914
|
}
|
|
916
915
|
}
|