react-x11 2.47.3 → 2.47.5
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/package.json +2 -2
- package/src/anchor.js +48 -22
- package/src/cocoa/app.js +13 -105
- package/src/cocoa/clipboard.js +485 -0
- package/src/cocoa/idle.js +92 -0
- package/src/cocoa/statusitem.js +30 -10
- package/src/components/Dialog.js +69 -28
- package/src/components/Menu.js +64 -23
- package/src/events.js +22 -4
- package/src/frame/index.js +4 -1
- package/src/idle.js +50 -20
- package/src/idlehooks.js +5 -2
- package/src/index.d.ts +14 -3
- package/src/nodes/node.js +6 -0
- package/src/nodes/textarea.js +10 -1
- package/src/nodes/textinput.js +8 -0
- package/src/nodes/window/anchoring.js +87 -2
- package/src/nodes/window/floors.js +26 -1
- package/src/nodes/window/spine.js +21 -3
- package/src/nodes/window/window.js +9 -0
- package/src/types/components.d.ts +35 -8
- package/src/types/elements.d.ts +5 -1
- package/src/types/system.d.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-x11",
|
|
3
|
-
"version": "2.47.
|
|
3
|
+
"version": "2.47.5",
|
|
4
4
|
"description": "react renderer with X11 as a target",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
"eslint-plugin-react": "^7.37.5",
|
|
152
152
|
"globals": "^17.8.0",
|
|
153
153
|
"hot-module-replacement": "^4.0.1",
|
|
154
|
-
"katex": "^0.
|
|
154
|
+
"katex": "^0.19.0",
|
|
155
155
|
"prettier": "^3.6.0",
|
|
156
156
|
"react": "^19.3.0",
|
|
157
157
|
"react-devtools-core": "^8.0.0",
|
package/src/anchor.js
CHANGED
|
@@ -55,6 +55,26 @@ export function windowOrigin(node) {
|
|
|
55
55
|
return win?._screenOrigin ?? { x: win?.x ?? 0, y: win?.y ?? 0 };
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* The rect of the window that owns `node`, on the screen, in logical
|
|
60
|
+
* pixels: what a dialog is centred over. From the window rather than from
|
|
61
|
+
* the root's laid-out box, because a dialog that opens with its owner is
|
|
62
|
+
* asked for it before that box has been laid out — the window already has
|
|
63
|
+
* its size. Null with no window yet.
|
|
64
|
+
*/
|
|
65
|
+
export function windowRect(node) {
|
|
66
|
+
const win = node?.root?.window;
|
|
67
|
+
if (!win) return null;
|
|
68
|
+
const origin = windowOrigin(node);
|
|
69
|
+
const s = node.scale ?? 1;
|
|
70
|
+
return {
|
|
71
|
+
x: origin.x / s,
|
|
72
|
+
y: origin.y / s,
|
|
73
|
+
width: win.width / s,
|
|
74
|
+
height: win.height / s,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
58
78
|
/**
|
|
59
79
|
* The rect within a node that a popup is anchored to, in the node's own
|
|
60
80
|
* coordinates — a caret, a table cell, a chart datapoint, the span under a
|
|
@@ -198,6 +218,11 @@ export function deviceAnchorArea(node) {
|
|
|
198
218
|
*
|
|
199
219
|
* ## Which side is which
|
|
200
220
|
*
|
|
221
|
+
* `placement: 'center'` is no side at all: the popup goes **over** the
|
|
222
|
+
* anchor, the middle of one on the middle of the other, and is only clamped
|
|
223
|
+
* — a dialog over its owner window (`centerRect`, and `Dialog` through a
|
|
224
|
+
* `<popup anchor>`).
|
|
225
|
+
*
|
|
201
226
|
* `placement: 'start'` and `'end'` are the **logical** sides, and they are
|
|
202
227
|
* what a submenu wants: a menu opens away from the edge its rows begin at, so
|
|
203
228
|
* it goes out to the right in an LTR menu and out to the left in an RTL one.
|
|
@@ -340,7 +365,14 @@ function placeAgainst(anchor, cross, area, s, options) {
|
|
|
340
365
|
let x;
|
|
341
366
|
let y;
|
|
342
367
|
|
|
343
|
-
if (side === '
|
|
368
|
+
if (side === 'center') {
|
|
369
|
+
// Over the anchor rather than beside it: the middle of the one on the
|
|
370
|
+
// middle of the other, on both axes. Nothing to flip to, and nothing
|
|
371
|
+
// for `align` or an `offset` to mean — the clamp below is the whole of
|
|
372
|
+
// what keeps it on the screen.
|
|
373
|
+
x = ax + (aw - width) / 2;
|
|
374
|
+
y = ay + (ah - height) / 2;
|
|
375
|
+
} else if (side === 'bottom' || side === 'top') {
|
|
344
376
|
const below = ay + ah + offset;
|
|
345
377
|
const above = ay - height - offset;
|
|
346
378
|
if (
|
|
@@ -399,19 +431,15 @@ function placeAgainst(anchor, cross, area, s, options) {
|
|
|
399
431
|
/**
|
|
400
432
|
* Where to put a `<popup>` of this size **centred over the owner window**,
|
|
401
433
|
* in screen coordinates, clamped into the screen. A dialog is anchored to
|
|
402
|
-
* the window rather than to a widget
|
|
403
|
-
*
|
|
434
|
+
* the window rather than to a widget — `placement: 'center'` against the
|
|
435
|
+
* window's rect, which is what this is.
|
|
404
436
|
*/
|
|
405
|
-
export function centerRect(node, { width
|
|
437
|
+
export function centerRect(node, { width, height }) {
|
|
406
438
|
if (!node) return null;
|
|
407
439
|
// Logical in, logical out, device in between — anchorRect's contract,
|
|
408
440
|
// for the same caller: the result becomes a popup's `x`/`y` props.
|
|
409
441
|
const s = node.scale ?? 1;
|
|
410
|
-
const width = logicalW * s;
|
|
411
|
-
const height = logicalH * s;
|
|
412
442
|
const win = node.root?.window;
|
|
413
|
-
const ww = win?.width ?? width;
|
|
414
|
-
const wh = win?.height ?? height;
|
|
415
443
|
// `_screenOrigin` for the same reason `anchorRect` uses it: `x`/`y` come
|
|
416
444
|
// from ConfigureNotify, and under a reparenting window manager those can be
|
|
417
445
|
// relative to the *frame* rather than the root — which would centre the
|
|
@@ -420,19 +448,17 @@ export function centerRect(node, { width: logicalW, height: logicalH }) {
|
|
|
420
448
|
// coordinates, so the two agree there; that is not something to rely on.)
|
|
421
449
|
// The raw coordinates stay as the fallback, since the headless mock has no
|
|
422
450
|
// server to ask.
|
|
423
|
-
const origin =
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
x = Math.max(area.x, Math.min(x, area.x + area.width - width));
|
|
430
|
-
y = Math.max(area.y, Math.min(y, area.y + area.height - height));
|
|
431
|
-
}
|
|
432
|
-
return {
|
|
433
|
-
x: Math.round(x) / s,
|
|
434
|
-
y: Math.round(y) / s,
|
|
435
|
-
width: logicalW,
|
|
436
|
-
height: logicalH,
|
|
451
|
+
const origin = windowOrigin(node);
|
|
452
|
+
const over = {
|
|
453
|
+
x: origin.x,
|
|
454
|
+
y: origin.y,
|
|
455
|
+
width: win?.width ?? width * s,
|
|
456
|
+
height: win?.height ?? height * s,
|
|
437
457
|
};
|
|
458
|
+
const at = placeAgainst(over, over, deviceAnchorArea(node), s, {
|
|
459
|
+
placement: 'center',
|
|
460
|
+
width,
|
|
461
|
+
height,
|
|
462
|
+
});
|
|
463
|
+
return { x: at.x, y: at.y, width: at.width, height: at.height };
|
|
438
464
|
}
|
package/src/cocoa/app.js
CHANGED
|
@@ -31,6 +31,7 @@ import { CocoaDockMenu } from './dock.js';
|
|
|
31
31
|
import { CocoaGlobalMenuExport } from './globalmenu.js';
|
|
32
32
|
import { CocoaStatusItem } from './statusitem.js';
|
|
33
33
|
import { CocoaCalendars } from './calendar.js';
|
|
34
|
+
import { CocoaClipboard } from './clipboard.js';
|
|
34
35
|
import { CocoaNotifications } from './notifications.js';
|
|
35
36
|
import { CocoaPaneHost } from './panehost.js';
|
|
36
37
|
import { CocoaPermissions } from './permissions.js';
|
|
@@ -38,13 +39,13 @@ import { CocoaPaneSubwindow, CocoaPaneWindow } from './panewindow.js';
|
|
|
38
39
|
import { CocoaColorSampler } from './screencolor.js';
|
|
39
40
|
import { CocoaFilePanels } from './filepanels.js';
|
|
40
41
|
import { CocoaFontManager } from './fonts.js';
|
|
42
|
+
import { lastInputReader } from './idle.js';
|
|
41
43
|
import { releaseImageUpload } from '../backend/context2d.js';
|
|
42
44
|
import { PanePlayer, canShowPanePlayers } from './paneplayer.js';
|
|
43
45
|
import { CocoaPlayer, canPlay } from './player.js';
|
|
44
46
|
import { CocoaSurface } from './surface.js';
|
|
45
47
|
import { CocoaSymbols } from './symbols.js';
|
|
46
48
|
import { CocoaWindow, FRAME_SLACK_MS } from './window.js';
|
|
47
|
-
import { utiFromMime, wire } from './dnd.js';
|
|
48
49
|
import { decodeKey, modifierMask } from './keymap.js';
|
|
49
50
|
import { loadNative } from './native.js';
|
|
50
51
|
import { requestAppKit, threadedChannel } from './threaded.js';
|
|
@@ -345,6 +346,12 @@ export class CocoaApp {
|
|
|
345
346
|
? new CocoaColorSampler(native)
|
|
346
347
|
: null;
|
|
347
348
|
|
|
349
|
+
// How long since anybody touched the Mac, which is what `useIdle()`
|
|
350
|
+
// asks of an app with no X server behind it (src/idle.js): the bridge's
|
|
351
|
+
// `lastInputMs` where it has one, `ioreg` where it does not
|
|
352
|
+
// (src/cocoa/idle.js, #938).
|
|
353
|
+
this.lastInputMs = lastInputReader(native);
|
|
354
|
+
|
|
348
355
|
// The GL policy, glbackend.js's shape. No GLX exists here, so the
|
|
349
356
|
// default is 'auto' (the direct backend where the runtime loads);
|
|
350
357
|
// useSupports('shaders') stays false until the first <glarea> resolves
|
|
@@ -391,110 +398,10 @@ export class CocoaApp {
|
|
|
391
398
|
};
|
|
392
399
|
this._atoms = new Map();
|
|
393
400
|
|
|
394
|
-
// ntk's clipboard shape over `NSPasteboard.general
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
// `own()`, the text controls' `_ownSelection`). Sent to the pasteboard,
|
|
399
|
-
// each of those gestures was a Copy: a drag across a paragraph replaced
|
|
400
|
-
// the link the user had just copied in another app.
|
|
401
|
-
//
|
|
402
|
-
// So every other name — PRIMARY, SECONDARY, one of the app's own — is a
|
|
403
|
-
// selection nobody on this desktop can paste from: a write resolves and
|
|
404
|
-
// changes nothing, a read finds no owner, `targets()` is empty, `clear()`
|
|
405
|
-
// leaves the pasteboard alone. Deliberately not a PRIMARY kept in this
|
|
406
|
-
// process: its point is the paste in *another* application, and inside
|
|
407
|
-
// one all it would keep is a middle click that types into a text field,
|
|
408
|
-
// which no Mac text field does. An app that wants select-to-copy anyway
|
|
409
|
-
// — a terminal — writes CLIPBOARD from `onSelectionChange`
|
|
410
|
-
// (docs/clipboard.md, "Two clipboards").
|
|
411
|
-
const isClipboard = (selection = 'CLIPBOARD') => selection === 'CLIPBOARD';
|
|
412
|
-
this.clipboard = {
|
|
413
|
-
// A write is one pasteboard item with every flavour under its UTI —
|
|
414
|
-
// dnd.js's table, then the system's — the way a drag offers its
|
|
415
|
-
// payload. Text alone keeps pasteboardWriteText, the verb every
|
|
416
|
-
// bridge has; anything else needs pasteboardWrite (windowkit/appkit
|
|
417
|
-
// #119), and a bridge without it keeps the text and says, once, what
|
|
418
|
-
// it dropped — an image that silently never reaches the pasteboard
|
|
419
|
-
// looks like a Copy that did nothing.
|
|
420
|
-
write: (data, { selection } = {}) => {
|
|
421
|
-
if (!isClipboard(selection)) return Promise.resolve();
|
|
422
|
-
if (typeof data === 'string') {
|
|
423
|
-
native.pasteboardWriteText(data);
|
|
424
|
-
return Promise.resolve();
|
|
425
|
-
}
|
|
426
|
-
const item = {};
|
|
427
|
-
for (const [type, value] of Object.entries(data ?? {})) {
|
|
428
|
-
const uti = utiFromMime(type, native);
|
|
429
|
-
if (uti && value != null && !(uti in item)) item[uti] = wire(value);
|
|
430
|
-
}
|
|
431
|
-
const TEXT = 'public.utf8-plain-text';
|
|
432
|
-
const raw = item[TEXT];
|
|
433
|
-
const text =
|
|
434
|
-
raw == null
|
|
435
|
-
? ''
|
|
436
|
-
: typeof raw === 'string'
|
|
437
|
-
? raw
|
|
438
|
-
: Buffer.from(raw).toString('utf8');
|
|
439
|
-
const others = Object.keys(item).filter((uti) => uti !== TEXT);
|
|
440
|
-
if (others.length === 0) {
|
|
441
|
-
native.pasteboardWriteText(text);
|
|
442
|
-
} else if (typeof native.pasteboardWrite === 'function') {
|
|
443
|
-
native.pasteboardWrite(item);
|
|
444
|
-
} else {
|
|
445
|
-
if (
|
|
446
|
-
!this._warnedPasteboardFlavours &&
|
|
447
|
-
process.env.NODE_ENV !== 'production'
|
|
448
|
-
) {
|
|
449
|
-
this._warnedPasteboardFlavours = true;
|
|
450
|
-
console.warn(
|
|
451
|
-
`react-x11: clipboard.write() kept only the text — this ` +
|
|
452
|
-
`@windowkit/appkit has no pasteboardWrite, so ` +
|
|
453
|
-
`${others.join(', ')} did not reach the pasteboard. ` +
|
|
454
|
-
'Update @windowkit/appkit to a release with pasteboardWrite ' +
|
|
455
|
-
'(windowkit/appkit#119).',
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
native.pasteboardWriteText(text);
|
|
459
|
-
}
|
|
460
|
-
return Promise.resolve();
|
|
461
|
-
},
|
|
462
|
-
clear: (selection) => {
|
|
463
|
-
if (isClipboard(selection)) native.pasteboardClear();
|
|
464
|
-
return Promise.resolve();
|
|
465
|
-
},
|
|
466
|
-
targets: ({ selection } = {}) => {
|
|
467
|
-
if (!isClipboard(selection)) return Promise.resolve([]);
|
|
468
|
-
return this._ask('pasteboardReadText').then((text) =>
|
|
469
|
-
text == null ? [] : ['UTF8_STRING', 'STRING'],
|
|
470
|
-
);
|
|
471
|
-
},
|
|
472
|
-
read: ({ selection, target } = {}) => {
|
|
473
|
-
if (!isClipboard(selection)) {
|
|
474
|
-
return Promise.reject(
|
|
475
|
-
new Error(
|
|
476
|
-
`clipboard: nothing to paste — ${selection} is an X11 ` +
|
|
477
|
-
'selection, and on macOS only CLIPBOARD is the pasteboard',
|
|
478
|
-
),
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
|
-
return this._ask('pasteboardReadText').then((text) => {
|
|
482
|
-
if (text == null) {
|
|
483
|
-
throw new Error(
|
|
484
|
-
'clipboard: nothing to paste — the pasteboard is empty',
|
|
485
|
-
);
|
|
486
|
-
}
|
|
487
|
-
if (target === undefined) return text;
|
|
488
|
-
if (target === 'UTF8_STRING' || target === 'STRING') {
|
|
489
|
-
return Buffer.from(text, 'utf8');
|
|
490
|
-
}
|
|
491
|
-
throw new Error(
|
|
492
|
-
`clipboard: cannot convert the pasteboard to ${target}`,
|
|
493
|
-
);
|
|
494
|
-
});
|
|
495
|
-
},
|
|
496
|
-
watch: () => Promise.resolve(() => {}),
|
|
497
|
-
};
|
|
401
|
+
// ntk's clipboard shape over `NSPasteboard.general`, the one pasteboard
|
|
402
|
+
// and CLIPBOARD's — every other selection is one nobody here can paste
|
|
403
|
+
// from (src/cocoa/clipboard.js says why).
|
|
404
|
+
this.clipboard = new CocoaClipboard(this, native);
|
|
498
405
|
}
|
|
499
406
|
|
|
500
407
|
_parseColor(value) {
|
|
@@ -1912,6 +1819,7 @@ export class CocoaApp {
|
|
|
1912
1819
|
if (this._frameTimer) clearTimeout(this._frameTimer);
|
|
1913
1820
|
this._frameTimer = null;
|
|
1914
1821
|
this._pollScreens(false);
|
|
1822
|
+
this.clipboard.close();
|
|
1915
1823
|
this._cocoaGL?.destroy();
|
|
1916
1824
|
this._cocoaGL = null;
|
|
1917
1825
|
this._unsubscribe?.();
|