react-x11 2.44.1 → 2.45.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/package.json +1 -1
- package/src/cocoa/app.js +6 -0
- package/src/cocoa/panewindow.js +16 -0
- package/src/cocoa/window.js +6 -0
- package/src/frame/index.js +16 -0
- package/src/node.d.ts +14 -0
- package/src/nodes/invalidate.js +30 -0
- package/src/nodes/scrollable.js +49 -0
- package/src/nodes/window/floors.js +20 -5
- package/src/nodes/window/size.js +32 -7
package/package.json
CHANGED
package/src/cocoa/app.js
CHANGED
|
@@ -1014,8 +1014,11 @@ export class CocoaApp {
|
|
|
1014
1014
|
wnd?._shown?.(msg.seq);
|
|
1015
1015
|
} else if (msg?.type === 'pane-rect') {
|
|
1016
1016
|
pane.setPaneSize(msg.width, msg.height, msg.scale, msg.screen);
|
|
1017
|
+
if (typeof msg.live === 'boolean') pane.setLive(msg.live);
|
|
1017
1018
|
this._flushPaneOutbox();
|
|
1018
1019
|
this._afterInput();
|
|
1020
|
+
} else if (msg?.type === 'pane-live') {
|
|
1021
|
+
pane.setLive(msg.live === true);
|
|
1019
1022
|
} else if (msg?.type === 'pane-menu-answer') {
|
|
1020
1023
|
const answer = this._paneMenus?.get(msg.menu);
|
|
1021
1024
|
this._paneMenus?.delete(msg.menu);
|
|
@@ -1687,6 +1690,9 @@ export class CocoaApp {
|
|
|
1687
1690
|
const wnd = this._window(ev);
|
|
1688
1691
|
if (!wnd || wnd.destroyed) return;
|
|
1689
1692
|
wnd.liveResizing = ev.phase === 'begin';
|
|
1693
|
+
// and what lives in the window without being it hears: a `<Frame>`'s
|
|
1694
|
+
// pane, which has no window of its own to be resized live
|
|
1695
|
+
wnd.emit('liveresize', { live: wnd.liveResizing });
|
|
1690
1696
|
}
|
|
1691
1697
|
|
|
1692
1698
|
/**
|
package/src/cocoa/panewindow.js
CHANGED
|
@@ -50,6 +50,8 @@ export class CocoaPaneWindow {
|
|
|
50
50
|
// polls it through the pane's ref, so both spellings answer
|
|
51
51
|
this.id = this.windowId;
|
|
52
52
|
this.windowNumber = this.windowId;
|
|
53
|
+
// the host's word, which the pane has no AppKit to hear (`setLive`)
|
|
54
|
+
this.liveResizing = false;
|
|
53
55
|
this.scale = app.scale ?? 2;
|
|
54
56
|
// Device pixels, as every window's attributes are (`windowAttributes`
|
|
55
57
|
// multiplies the props through once): the size the pane is born at,
|
|
@@ -112,6 +114,20 @@ export class CocoaPaneWindow {
|
|
|
112
114
|
* a host that does not say. A move alone is a `resize` that `moved`, the
|
|
113
115
|
* event a window's own move is, so what is anchored here follows it.
|
|
114
116
|
*/
|
|
117
|
+
/**
|
|
118
|
+
* Whether the window the pane is in is being resized live, as the host
|
|
119
|
+
* says (`pane-rect`, `pane-live`, src/frame/index.js): what a window's
|
|
120
|
+
* `liveResizing` is in a process of its own, where AppKit brackets the
|
|
121
|
+
* drag of its edge. Core defers a content floor's measurement while it
|
|
122
|
+
* is (src/nodes/window/size.js), and catches it up the frame after it
|
|
123
|
+
* ends; and it is announced as a window announces its own.
|
|
124
|
+
*/
|
|
125
|
+
setLive(live) {
|
|
126
|
+
if (this.destroyed || this.liveResizing === live) return;
|
|
127
|
+
this.liveResizing = live;
|
|
128
|
+
this.emit('liveresize', { live });
|
|
129
|
+
}
|
|
130
|
+
|
|
115
131
|
setPaneSize(width, height, scale, screen) {
|
|
116
132
|
if (this.destroyed) return;
|
|
117
133
|
if (scale) this.scale = scale;
|
package/src/cocoa/window.js
CHANGED
|
@@ -278,6 +278,12 @@ export class CocoaWindow {
|
|
|
278
278
|
let list = this._handlers.get(name);
|
|
279
279
|
if (!list) this._handlers.set(name, (list = []));
|
|
280
280
|
list.push(fn);
|
|
281
|
+
// and the way to stop listening, for a listener that goes before the
|
|
282
|
+
// window does (`<Frame>`'s, for `liveresize`)
|
|
283
|
+
return () => {
|
|
284
|
+
const at = list.indexOf(fn);
|
|
285
|
+
if (at >= 0) list.splice(at, 1);
|
|
286
|
+
};
|
|
281
287
|
}
|
|
282
288
|
|
|
283
289
|
emit(name, ev) {
|
package/src/frame/index.js
CHANGED
|
@@ -574,6 +574,20 @@ function PaneHostView({
|
|
|
574
574
|
let acks = false;
|
|
575
575
|
let inFlight = false;
|
|
576
576
|
let waiting = null;
|
|
577
|
+
// And whether the window is being resized live — AppKit's drag of its
|
|
578
|
+
// edge, from its begin to its end (`liveResizing`) — which the pane
|
|
579
|
+
// has no window to hear. Core defers measuring a content floor for a
|
|
580
|
+
// drag's length and catches it up after it (src/nodes/window/size.js),
|
|
581
|
+
// and an element puts off what it would rather do once the drag ends:
|
|
582
|
+
// a pane measured its floors at every step, a document a pixel wide
|
|
583
|
+
// among them, and laid a document out whole at every rest of the drag.
|
|
584
|
+
// With every rect, and on its own where only that changed — the end
|
|
585
|
+
// of a drag comes after its last size.
|
|
586
|
+
const hostWindow = node.root?.window;
|
|
587
|
+
const liveNow = () => hostWindow?.liveResizing === true;
|
|
588
|
+
const offLive = hostWindow?.on?.('liveresize', (ev) =>
|
|
589
|
+
s.trySend?.({ type: 'pane-live', live: ev?.live === true }),
|
|
590
|
+
);
|
|
577
591
|
const transmit = (rect) => {
|
|
578
592
|
sent = rect;
|
|
579
593
|
s.trySend?.({
|
|
@@ -581,6 +595,7 @@ function PaneHostView({
|
|
|
581
595
|
width: rect.width,
|
|
582
596
|
height: rect.height,
|
|
583
597
|
scale: rect.scale,
|
|
598
|
+
live: liveNow(),
|
|
584
599
|
...(rect.screen && { screen: rect.screen }),
|
|
585
600
|
});
|
|
586
601
|
if (!acks) return;
|
|
@@ -685,6 +700,7 @@ function PaneHostView({
|
|
|
685
700
|
waiting = null;
|
|
686
701
|
offAnchor?.();
|
|
687
702
|
offFocus?.();
|
|
703
|
+
if (typeof offLive === 'function') offLive();
|
|
688
704
|
node.defaultCursor = undefined;
|
|
689
705
|
node.root?.events?.refreshCursor();
|
|
690
706
|
node.absolutize = origAbsolutize;
|
package/src/node.d.ts
CHANGED
|
@@ -750,6 +750,20 @@ export declare class Node {
|
|
|
750
750
|
* each scroll the pane blits, after layout.
|
|
751
751
|
*/
|
|
752
752
|
viewportFixedRects(): Rect[] | null;
|
|
753
|
+
/**
|
|
754
|
+
* What this element draws under the viewport of the scroll pane it is in
|
|
755
|
+
* moved by (dx, dy) device pixels, and what the viewport shows is to stay
|
|
756
|
+
* where it is on screen: the pane's offsets move by as much, in the
|
|
757
|
+
* layout pass running now, after it measures what it holds and before it
|
|
758
|
+
* places it (CSS Scroll Anchoring 1) — a document laid out again at
|
|
759
|
+
* another width, whose text above the fold broke into more lines or
|
|
760
|
+
* fewer. Asked of the nearest scroll pane; shifts in one pass add up.
|
|
761
|
+
* Ask from the layout pass (`measureContent`); asked outside one, the
|
|
762
|
+
* shift waits for the next and asks for it. A `scrollTo` made since the
|
|
763
|
+
* last pass drops it, a `scrollIntoView` lands after it, and the result
|
|
764
|
+
* is clamped as any scroll. Returns whether a pane took it.
|
|
765
|
+
*/
|
|
766
|
+
anchorScrollBy(dx: number, dy: number): boolean;
|
|
753
767
|
/**
|
|
754
768
|
* How far this element's own drawing reaches past its box, in device
|
|
755
769
|
* pixels on every side — 0, the default, for an element that stays inside
|
package/src/nodes/invalidate.js
CHANGED
|
@@ -137,6 +137,36 @@ export class NodeInvalidate {
|
|
|
137
137
|
return null;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
/**
|
|
141
|
+
* What this element draws under the viewport of the scroll pane it is
|
|
142
|
+
* in moved by (dx, dy) device pixels, and what the viewport shows is to
|
|
143
|
+
* stay where it is on screen: the pane's offsets move by as much, in the
|
|
144
|
+
* layout pass running now, after it measures what it holds and before it
|
|
145
|
+
* places it (CSS Scroll Anchoring 1). A document laid out again at
|
|
146
|
+
* another width is the case: its text above the fold breaks into more
|
|
147
|
+
* lines or fewer, and the paragraph at the top of the viewport moves
|
|
148
|
+
* down or up the document by the difference — which, unanswered, scrolls
|
|
149
|
+
* the reader a page away from where they were.
|
|
150
|
+
*
|
|
151
|
+
* Asked of the nearest scroll pane, the one whose viewport the element
|
|
152
|
+
* is seen through; shifts asked of it in one pass add up. Ask from the
|
|
153
|
+
* layout pass — `measureContent`, or what it calls — where the pane
|
|
154
|
+
* places its content after; asked outside one, the shift waits for the
|
|
155
|
+
* next pass, and asks for it. A `scrollTo` the application made since
|
|
156
|
+
* the last pass drops it, since the shift was measured against the
|
|
157
|
+
* offsets before that scroll; a `scrollIntoView` lands after it; and the
|
|
158
|
+
* pane clamps the result to what it holds, as any scroll. Returns
|
|
159
|
+
* whether a pane took it: false where nothing scrolls the element.
|
|
160
|
+
*/
|
|
161
|
+
anchorScrollBy(dx, dy) {
|
|
162
|
+
for (let n = this.parent; n; n = n.parent) {
|
|
163
|
+
if (typeof n._anchorScrollBy === 'function' && n.isScroller()) {
|
|
164
|
+
return n._anchorScrollBy(dx, dy);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
140
170
|
/**
|
|
141
171
|
* How far this element's own drawing reaches past its box — device
|
|
142
172
|
* pixels, on every side — or 0, the default, for one that stays inside
|
package/src/nodes/scrollable.js
CHANGED
|
@@ -330,6 +330,10 @@ export const Scrollable = (Base) =>
|
|
|
330
330
|
// the viewport grew, under it. A browser fires `scroll` for each, so
|
|
331
331
|
// `onScroll` hears of them too, once the pass is over.
|
|
332
332
|
const from = { x: this.scrollX, y: this.scrollY };
|
|
333
|
+
// What the content asked to keep in place lands before them all
|
|
334
|
+
// (`anchorScrollBy`): it moved under the viewport in this pass, and
|
|
335
|
+
// a scroll anyone asks for is from where it shows what it did
|
|
336
|
+
this._applyAnchorShift();
|
|
333
337
|
// A scrollTo held for this pass lands first, so a node asked into view
|
|
334
338
|
// in the same frame is brought in from where that scroll put the pane:
|
|
335
339
|
// the order the two have on a laid-out pane, where scrollTo applies at
|
|
@@ -607,6 +611,10 @@ export const Scrollable = (Base) =>
|
|
|
607
611
|
}
|
|
608
612
|
this.scrollX = next.x;
|
|
609
613
|
this.scrollY = next.y;
|
|
614
|
+
// a scroll asked for since the last pass is where the pane goes: a
|
|
615
|
+
// shift the content asks for in the next was measured against the
|
|
616
|
+
// offsets before it (`_applyAnchorShift`)
|
|
617
|
+
this._scrolledSincePass = true;
|
|
610
618
|
if (moved) this.props.onScroll?.(this._scrollEvent());
|
|
611
619
|
// A scroll reflows this viewport's contents and nothing else, and the
|
|
612
620
|
// viewport clips them, so the damage is this node's own rect. It is a
|
|
@@ -707,6 +715,47 @@ export const Scrollable = (Base) =>
|
|
|
707
715
|
return true;
|
|
708
716
|
}
|
|
709
717
|
|
|
718
|
+
/**
|
|
719
|
+
* The pane's half of `Node.anchorScrollBy`: a shift of the offsets the
|
|
720
|
+
* content asked for, added up until the pass applies it
|
|
721
|
+
* (`_applyAnchorShift`). The pass measures the content again, since
|
|
722
|
+
* what moved under the viewport moved by being laid out; one asked
|
|
723
|
+
* outside a pass asks for one.
|
|
724
|
+
*/
|
|
725
|
+
_anchorScrollBy(dx, dy) {
|
|
726
|
+
if (!Number.isFinite(dx) || !Number.isFinite(dy)) return false;
|
|
727
|
+
if (dx === 0 && dy === 0) return true;
|
|
728
|
+
const shift = (this._anchorShift ??= { x: 0, y: 0 });
|
|
729
|
+
shift.x += dx;
|
|
730
|
+
shift.y += dy;
|
|
731
|
+
this._scrollMeasureDirty = true;
|
|
732
|
+
const root = this.root;
|
|
733
|
+
if (root && !root._inFlush) root.invalidate(true, this, 'scroll');
|
|
734
|
+
return true;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Apply the shift the content asked for (`_anchorScrollBy`), before
|
|
739
|
+
* anything else the pass moves the offsets by: on each axis it scrolls,
|
|
740
|
+
* the clamp after takes it into the extent just measured. Its
|
|
741
|
+
* `onScroll` and its claim of the viewport are the pass's, as for a
|
|
742
|
+
* held `scrollTo`. A scroll the application asked for since the last
|
|
743
|
+
* pass drops it: the shift was measured against the offsets before
|
|
744
|
+
* that scroll, which is where the application wants the pane now.
|
|
745
|
+
*/
|
|
746
|
+
_applyAnchorShift() {
|
|
747
|
+
const shift = this._anchorShift;
|
|
748
|
+
const scrolled = this._scrolledSincePass === true;
|
|
749
|
+
this._scrolledSincePass = false;
|
|
750
|
+
if (!shift) return;
|
|
751
|
+
this._anchorShift = null;
|
|
752
|
+
if (scrolled) return;
|
|
753
|
+
// `scrollX` is a distance from the start in either direction, and
|
|
754
|
+
// content that moved towards the end is followed the same way
|
|
755
|
+
this.scrollX += this.direction === 'rtl' ? -shift.x : shift.x;
|
|
756
|
+
this.scrollY += shift.y;
|
|
757
|
+
}
|
|
758
|
+
|
|
710
759
|
/**
|
|
711
760
|
* Answer a held request (`_holdScrollTo`) against the extent this pass
|
|
712
761
|
* has just measured: each held axis's base, clamped, then each step
|
|
@@ -533,10 +533,12 @@ export function setMeasuringShrink(node, axis, out) {
|
|
|
533
533
|
* It is still laid out, though, and a leaf's answer could reach something
|
|
534
534
|
* that is read through its size. So it is marked only where it cannot:
|
|
535
535
|
*
|
|
536
|
-
* - every box from
|
|
537
|
-
* its content — the
|
|
538
|
-
*
|
|
539
|
-
*
|
|
536
|
+
* - every box from `node` down to it is as wide as something other than
|
|
537
|
+
* its content — `node` the width the pass gives it (no room, for the
|
|
538
|
+
* window; its column's, for a spine root; its own, for a box that sizes
|
|
539
|
+
* itself), and below it a width it names or a column stretching it — so
|
|
540
|
+
* the width the leaf answers is one yoga replaces, and nothing above it
|
|
541
|
+
* is sized from it;
|
|
540
542
|
* - no box is handed a share of space the leaf's height changed, which is
|
|
541
543
|
* how a height could become somebody's width — an aspect ratio, a column
|
|
542
544
|
* that wraps, an image sized to the height it is given. Every column from
|
|
@@ -656,15 +658,28 @@ function stretchedAlong(node, axis) {
|
|
|
656
658
|
* names are numbers — a height, a basis (a height, down a column), a floor or
|
|
657
659
|
* a ceiling; and `'unknown'` for a share of something (a percentage), which
|
|
658
660
|
* a pass with no height on offer resolves as it likes.
|
|
661
|
+
*
|
|
662
|
+
* A floor of nothing bounds nothing: a box as tall as its content is at
|
|
663
|
+
* least that tall already, so its height is its content's. It is how a box
|
|
664
|
+
* says it may shrink, and a browser's page has one on every box from the
|
|
665
|
+
* window down to its scroller, so that a page taller than the window
|
|
666
|
+
* scrolls. Read as a height of the box's own, it stopped the walk at the
|
|
667
|
+
* first of them, where the scroller gives way: the whole document in the
|
|
668
|
+
* scroller was set a word to a line each time the tree was measured, 100
|
|
669
|
+
* to 250 ms of a frame for a long Wikipedia article, and nothing read any
|
|
670
|
+
* of it. And it took the box pinned to both edges of such a column for one
|
|
671
|
+
* whose height nothing in flow decides, which it is.
|
|
659
672
|
*/
|
|
660
673
|
function heightOf(yoga) {
|
|
674
|
+
const floor = yoga.getMinHeight();
|
|
661
675
|
let own = false;
|
|
662
676
|
for (const value of [
|
|
663
677
|
yoga.getHeight(),
|
|
664
678
|
yoga.getFlexBasis(),
|
|
665
|
-
|
|
679
|
+
floor.value === 0 ? null : floor,
|
|
666
680
|
yoga.getMaxHeight(),
|
|
667
681
|
]) {
|
|
682
|
+
if (value === null) continue;
|
|
668
683
|
if (value.unit === Yoga.UNIT_PERCENT) return 'unknown';
|
|
669
684
|
if (value.unit === Yoga.UNIT_POINT) own = true;
|
|
670
685
|
}
|
package/src/nodes/window/size.js
CHANGED
|
@@ -354,12 +354,28 @@ export class WindowSize {
|
|
|
354
354
|
if (axis === 'width') {
|
|
355
355
|
const shrunk = [];
|
|
356
356
|
setMeasuringShrink(root, axis, shrunk);
|
|
357
|
+
// Nothing reads a root's own width — only heights travel up a spine
|
|
358
|
+
// (spine.js) — so what it holds is read only where a floor is written
|
|
359
|
+
// from it, as down the window's columns, and a leaf that is the root
|
|
360
|
+
// is laid out at the width its column offers, which is no answer of
|
|
361
|
+
// its own: a document in a scroller is a root when it changes.
|
|
362
|
+
const read = NO_UNREAD_WIDTHS || this._readEveryWidth;
|
|
363
|
+
const unread = [];
|
|
364
|
+
if (!read && root._measureFn) {
|
|
365
|
+
root._widthUnread = true;
|
|
366
|
+
unread.push(root);
|
|
367
|
+
} else if (!read) {
|
|
368
|
+
markUnreadLeaves(root, unread);
|
|
369
|
+
}
|
|
357
370
|
this._scopedFloorPasses += 1;
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
371
|
+
try {
|
|
372
|
+
yoga.calculateLayout(widths.widthPass, undefined, dir);
|
|
373
|
+
} finally {
|
|
374
|
+
for (const leaf of unread) leaf._widthUnread = false;
|
|
375
|
+
}
|
|
376
|
+
const span = contentSpan(root, axis, null, this, read);
|
|
377
|
+
const own = declaresOwnMinimum(root, axis);
|
|
378
|
+
if (own || read) root._floorW = own ? yoga.getComputedWidth() : span;
|
|
363
379
|
restoreShrink(shrunk);
|
|
364
380
|
return;
|
|
365
381
|
}
|
|
@@ -443,9 +459,18 @@ export class WindowSize {
|
|
|
443
459
|
if (axis === 'width') {
|
|
444
460
|
const shrunk = [];
|
|
445
461
|
setMeasuringShrink(boundary, axis, shrunk);
|
|
462
|
+
// its own extent is its style's, so what it holds is read only where
|
|
463
|
+
// a floor is written from it, as down the window's columns
|
|
464
|
+
const read = NO_UNREAD_WIDTHS || this._readEveryWidth;
|
|
465
|
+
const unread = [];
|
|
466
|
+
if (!read) markUnreadLeaves(boundary, unread);
|
|
446
467
|
this._scopedFloorPasses += 1;
|
|
447
|
-
|
|
448
|
-
|
|
468
|
+
try {
|
|
469
|
+
yoga.calculateLayout(undefined, undefined, dir);
|
|
470
|
+
} finally {
|
|
471
|
+
for (const leaf of unread) leaf._widthUnread = false;
|
|
472
|
+
}
|
|
473
|
+
contentSpan(boundary, axis, null, this, read);
|
|
449
474
|
restoreShrink(shrunk);
|
|
450
475
|
return;
|
|
451
476
|
}
|