react-x11 2.49.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/package.json +3 -3
- package/src/ClickToComponent.js +11 -132
- package/src/cli/dev.js +94 -19
- package/src/cli/errorwindow.js +368 -0
- 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/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 +58 -8
- package/src/shorthands.js +1266 -0
- package/src/styles.js +370 -131
- package/src/types/style.d.ts +163 -39
package/src/nodes/node.js
CHANGED
|
@@ -227,6 +227,14 @@ export class Node {
|
|
|
227
227
|
// the layout that threw, with the style value that named it: flexbox
|
|
228
228
|
// until the style names a different one
|
|
229
229
|
this._layoutAbandoned = null;
|
|
230
|
+
// what the style's relative lengths read, and the context they read it
|
|
231
|
+
// through during a swap (`_noteUnits` in styling.js)
|
|
232
|
+
this._unitDeps = 0;
|
|
233
|
+
this._emBasis = undefined;
|
|
234
|
+
this._units = null;
|
|
235
|
+
this._unitsNow = null;
|
|
236
|
+
this._makeUnits = null;
|
|
237
|
+
this._sizeQueried = false;
|
|
230
238
|
this._syncStyle(props);
|
|
231
239
|
this.yoga = yoga ? createLayoutNode() : null;
|
|
232
240
|
if (this.yoga) {
|
package/src/nodes/paint.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { isPlaced } from '../layouts.js';
|
|
6
6
|
import { Surface } from '../offscreen.js';
|
|
7
|
+
import { radiusOf } from '../styles.js';
|
|
7
8
|
import { isPaintedColor } from './boxpaint.js';
|
|
8
9
|
import { DAMAGE_SLOP } from './damage.js';
|
|
9
10
|
import { DRAWN_KINDS } from './kinds.js';
|
|
@@ -357,7 +358,7 @@ export class NodePaint {
|
|
|
357
358
|
// a context with no `roundRect` has always clipped a rounded box to its
|
|
358
359
|
// rectangle, the way `_roundedPath` degrades
|
|
359
360
|
const radius =
|
|
360
|
-
typeof ctx.roundRect === 'function' ? (this.style.
|
|
361
|
+
typeof ctx.roundRect === 'function' ? radiusOf(this.style, this.abs) : 0;
|
|
361
362
|
if (radius > 0 && this._paintRoundedChildren(ctx, order, radius)) return;
|
|
362
363
|
const clip = this._childrenCanOverflow();
|
|
363
364
|
if (clip) {
|
|
@@ -661,7 +662,8 @@ export class WindowPaint {
|
|
|
661
662
|
* Shape extension's hard 1-bit edge.
|
|
662
663
|
*/
|
|
663
664
|
_paintWindowBackground(ctx, damage, width, height) {
|
|
664
|
-
const
|
|
665
|
+
const backgroundColor = this.resolveColor(this.style.backgroundColor);
|
|
666
|
+
const borderRadius = radiusOf(this.style, { width, height });
|
|
665
667
|
if (DEV && this.style.boxShadow) devWarnWindowShadow(this.kind);
|
|
666
668
|
// A `backgroundImage` works wherever a `backgroundColor` does, which is
|
|
667
669
|
// the rule worth having — over the whole window, in window coordinates,
|
package/src/nodes/queries.js
CHANGED
|
@@ -183,42 +183,54 @@ export class NodeQueries {
|
|
|
183
183
|
// what pushes that into the subtree; only the node-local text props are
|
|
184
184
|
// left to notice here
|
|
185
185
|
this._syncStyle(this.props);
|
|
186
|
+
this._restyled(before, 'applied when a query block started matching');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* What a style resolved again outside a commit owes, past the swap itself:
|
|
191
|
+
* the node-local text props, and the layout. Returns whether the layout
|
|
192
|
+
* moved. A resize, a container settling, a font size a relative length
|
|
193
|
+
* read — none of them is React re-rendering the node, so nothing else
|
|
194
|
+
* applies it.
|
|
195
|
+
*
|
|
196
|
+
* A block that moved a layout property changed the tree the content floors
|
|
197
|
+
* were measured from — the debt a style change from React leaves too
|
|
198
|
+
* (`invalidate`, reason 'props'). It has to be marked as a *content*
|
|
199
|
+
* change: the live-resize deferral takes a plain `_floorsDirty` for the
|
|
200
|
+
* drag itself and lays out against the floors in hand, which are the old
|
|
201
|
+
* arrangement's, and by the time the catch-up looks the dirty flags are
|
|
202
|
+
* spent and no leaf's height moved — so the floor of a card that turned
|
|
203
|
+
* from a row into a column would stay the row's, and yoga would squeeze the
|
|
204
|
+
* column down to it.
|
|
205
|
+
*
|
|
206
|
+
* This can run in a frame, so a value the layout refuses is dropped and
|
|
207
|
+
* reported rather than thrown: nothing would catch it. `how` is the clause
|
|
208
|
+
* the report says it was applied by.
|
|
209
|
+
*/
|
|
210
|
+
_restyled(before, how) {
|
|
186
211
|
if (localTextStyleChanged(this.style, before)) this._textContentChanged();
|
|
187
|
-
if (this.yoga
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
`(${error.message})`;
|
|
208
|
-
reportStyleProblem(
|
|
209
|
-
this,
|
|
210
|
-
`${said} in <${this.kind} style>, applied when a query block ` +
|
|
211
|
-
'started matching',
|
|
212
|
-
'The declaration is dropped, as CSS drops one it cannot parse',
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
if (moved && this.root) {
|
|
216
|
-
this.root._floorsDirty = true;
|
|
217
|
-
this.root._floorsContentDirty = true;
|
|
218
|
-
this.root._floorsSources.add(this);
|
|
219
|
-
this.root._floorsOtherSources.add(this);
|
|
220
|
-
}
|
|
212
|
+
if (!this.yoga || this.style === before) return false;
|
|
213
|
+
const refused = [];
|
|
214
|
+
const moved = applyLayoutStyle(this.yoga, this.style, before, refused);
|
|
215
|
+
for (const { key, value, error } of refused) {
|
|
216
|
+
// an enum's own message already says what it is and what it takes
|
|
217
|
+
const said = error.message.startsWith('react-x11:')
|
|
218
|
+
? error.message
|
|
219
|
+
: `react-x11: invalid ${key} ${JSON.stringify(value)} ` +
|
|
220
|
+
`(${error.message})`;
|
|
221
|
+
reportStyleProblem(
|
|
222
|
+
this,
|
|
223
|
+
`${said} in <${this.kind} style>, ${how}`,
|
|
224
|
+
'The declaration is dropped, as CSS drops one it cannot parse',
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
if (moved && this.root) {
|
|
228
|
+
this.root._floorsDirty = true;
|
|
229
|
+
this.root._floorsContentDirty = true;
|
|
230
|
+
this.root._floorsSources.add(this);
|
|
231
|
+
this.root._floorsOtherSources.add(this);
|
|
221
232
|
}
|
|
233
|
+
return moved;
|
|
222
234
|
}
|
|
223
235
|
}
|
|
224
236
|
|
package/src/nodes/scrollblit.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// applies the blits at flush. Every failure falls back to repainting the
|
|
8
8
|
// viewport, so the fast path can cost correctness nothing.
|
|
9
9
|
|
|
10
|
-
import { EMPTY_STYLE, resolveBorderWidths } from '../styles.js';
|
|
10
|
+
import { EMPTY_STYLE, radiusOf, resolveBorderWidths } from '../styles.js';
|
|
11
11
|
import {
|
|
12
12
|
DAMAGE_SLOP,
|
|
13
13
|
MAX_DAMAGE_RECTS,
|
|
@@ -648,7 +648,10 @@ export class NodeScrollBlit {
|
|
|
648
648
|
_paintsNothingOwn() {
|
|
649
649
|
if (this.kind !== 'box' || this.isScroller?.()) return false;
|
|
650
650
|
const style = this.style ?? EMPTY_STYLE;
|
|
651
|
-
if (
|
|
651
|
+
if (
|
|
652
|
+
isPaintedColor(this.resolveColor(style.backgroundColor)) ||
|
|
653
|
+
style.backgroundImage
|
|
654
|
+
) {
|
|
652
655
|
return false;
|
|
653
656
|
}
|
|
654
657
|
if (style.opacity !== undefined && style.opacity < 1) return false;
|
|
@@ -763,7 +766,7 @@ export class WindowScrollBlit {
|
|
|
763
766
|
blitBorder.right > 0 ||
|
|
764
767
|
blitBorder.bottom > 0 ||
|
|
765
768
|
blitBorder.left > 0 ||
|
|
766
|
-
node.style.
|
|
769
|
+
radiusOf(node.style, node.abs) > 0
|
|
767
770
|
) {
|
|
768
771
|
return;
|
|
769
772
|
}
|
|
@@ -1101,7 +1104,7 @@ export class WindowScrollBlit {
|
|
|
1101
1104
|
bw.right,
|
|
1102
1105
|
bw.bottom,
|
|
1103
1106
|
bw.left,
|
|
1104
|
-
node.style
|
|
1107
|
+
radiusOf(node.style, node.abs),
|
|
1105
1108
|
);
|
|
1106
1109
|
if (!rectContains(insetRect(node.abs, inset), vp)) return;
|
|
1107
1110
|
// A scroll container's children *are* the scrolled content, which is
|
|
@@ -1229,7 +1232,7 @@ export class WindowScrollBlit {
|
|
|
1229
1232
|
_cornerPins(node, vp, upTo = this) {
|
|
1230
1233
|
let out = null;
|
|
1231
1234
|
for (let n = node.parent; n && n !== upTo; n = n.parent) {
|
|
1232
|
-
const radius = n.style
|
|
1235
|
+
const radius = radiusOf(n.style, n.abs);
|
|
1233
1236
|
if (!(radius > 0) || !n.abs) continue;
|
|
1234
1237
|
for (const square of cornerSquares(n.abs, radius)) {
|
|
1235
1238
|
if (!rectsOverlap(square, vp)) continue;
|
|
@@ -1295,7 +1298,7 @@ export class WindowScrollBlit {
|
|
|
1295
1298
|
// squares is translation-safe, and so is an element blit that
|
|
1296
1299
|
// pins the squares it reaches into (`_cornerPins`) — repaired
|
|
1297
1300
|
// where they are and where the copy dragged them.
|
|
1298
|
-
const radius = child.style
|
|
1301
|
+
const radius = radiusOf(child.style, child.abs);
|
|
1299
1302
|
if (radius > 0 && !cornersPinned(child.abs, radius, vp, pinned)) {
|
|
1300
1303
|
return false;
|
|
1301
1304
|
}
|
|
@@ -1407,7 +1410,7 @@ export class WindowScrollBlit {
|
|
|
1407
1410
|
break;
|
|
1408
1411
|
}
|
|
1409
1412
|
if (n.clipsChildren()) {
|
|
1410
|
-
const radius = n.style
|
|
1413
|
+
const radius = radiusOf(n.style, n.abs);
|
|
1411
1414
|
cover = intersectRects(
|
|
1412
1415
|
cover,
|
|
1413
1416
|
radius > 0 ? insetRect(n.abs, radius) : n.abs,
|
package/src/nodes/styling.js
CHANGED
|
@@ -21,9 +21,85 @@ import {
|
|
|
21
21
|
containerAnswers,
|
|
22
22
|
resolveQueries,
|
|
23
23
|
} from '../styles.js';
|
|
24
|
+
import { readLength, UNIT_EM, UNIT_REM, UNIT_VIEWPORT } from '../shorthands.js';
|
|
25
|
+
import { DEFAULT_TEXT_STYLE } from '../styles.js';
|
|
24
26
|
import { CUSTOM_SEMANTIC_NAMES } from './kinds.js';
|
|
25
27
|
import { DEV, shallowEqual } from './util.js';
|
|
26
28
|
|
|
29
|
+
/**
|
|
30
|
+
* What a relative length on one node resolves against — `em`, `rem` and the
|
|
31
|
+
* viewport units (src/shorthands.js, `RELATIVE`) — in logical pixels, the
|
|
32
|
+
* unit everything an app writes is in. Lazy: a style written in pixels asks
|
|
33
|
+
* none of it, and pays one allocation. Each answer it gives is recorded in
|
|
34
|
+
* `deps`, which is how the node knows to resolve the style again when that
|
|
35
|
+
* answer moves (`_noteUnits`).
|
|
36
|
+
*
|
|
37
|
+
* `style` is the one being resolved, not the one in force: a `padding:
|
|
38
|
+
* '1em'` beside a `fontSize: '20px'` is 20, whichever the style had before.
|
|
39
|
+
*/
|
|
40
|
+
class Units {
|
|
41
|
+
constructor(node, style) {
|
|
42
|
+
this.node = node;
|
|
43
|
+
this.style = style;
|
|
44
|
+
this.deps = 0;
|
|
45
|
+
this.parentEmRead = undefined;
|
|
46
|
+
this.emRead = undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The font size this node inherits — what `em` is on `fontSize`
|
|
50
|
+
* itself, as in CSS. Device pixels down the cascade, at this node's
|
|
51
|
+
* scale, so one divide brings it back. */
|
|
52
|
+
get parentEm() {
|
|
53
|
+
this.deps |= UNIT_EM;
|
|
54
|
+
if (this.parentEmRead === undefined) {
|
|
55
|
+
const node = this.node;
|
|
56
|
+
this.parentEmRead = node.inheritedTextStyle.size / (node.scale || 1);
|
|
57
|
+
}
|
|
58
|
+
return this.parentEmRead;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** This node's own font size — its `fontSize`, read against what it
|
|
62
|
+
* inherits where that is relative, or the inherited one. Depends on the
|
|
63
|
+
* inheritance only when it reads it. */
|
|
64
|
+
get em() {
|
|
65
|
+
if (this.emRead === undefined) {
|
|
66
|
+
const size = this.style.fontSize;
|
|
67
|
+
let own;
|
|
68
|
+
if (size != null && size !== '') {
|
|
69
|
+
try {
|
|
70
|
+
own = readLength('fontSize', size, undefined, this);
|
|
71
|
+
} catch {
|
|
72
|
+
// the text inherits past a size it cannot read, and so does `em`
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
this.emRead = typeof own === 'number' && own >= 0 ? own : this.parentEm;
|
|
76
|
+
}
|
|
77
|
+
return this.emRead;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The palette's `fontSize`: the root font size here, the size every label
|
|
81
|
+
* that names none of its own is set in. */
|
|
82
|
+
get rem() {
|
|
83
|
+
this.deps |= UNIT_REM;
|
|
84
|
+
return this.node.theme?.fontSize ?? DEFAULT_TEXT_STYLE.size;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The window this node is in, as a size query sees it. Before the window
|
|
88
|
+
* has been laid out, the size it asked for, where it named one; the
|
|
89
|
+
* size-query registry resolves the style again when the real one lands. */
|
|
90
|
+
get viewport() {
|
|
91
|
+
this.deps |= UNIT_VIEWPORT;
|
|
92
|
+
const node = this.node;
|
|
93
|
+
const root = node.root ?? (node.isWindow ? node : null);
|
|
94
|
+
if (root?.querySize) return root.querySize;
|
|
95
|
+
const props = root?.props;
|
|
96
|
+
return {
|
|
97
|
+
width: typeof props?.width === 'number' ? props.width : 0,
|
|
98
|
+
height: typeof props?.height === 'number' ? props.height : 0,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
27
103
|
/**
|
|
28
104
|
* A style property passed flat used to be silently dropped — it was neither
|
|
29
105
|
* a layout prop, a paint prop nor an `on*` handler, so nothing looked at it
|
|
@@ -46,6 +122,9 @@ function assertNoFlatStyleProps(props, kind, semantic) {
|
|
|
46
122
|
// style, and there is no element where a name means both.
|
|
47
123
|
const NO_SEMANTIC_NAMES = new Set();
|
|
48
124
|
|
|
125
|
+
/** What a style that read no relative length read: nothing. */
|
|
126
|
+
const NO_UNITS = Object.freeze({ deps: 0, parentEmRead: undefined });
|
|
127
|
+
|
|
49
128
|
/** A node's own style, installed onto `Node.prototype` by node.js. */
|
|
50
129
|
export class NodeStyling {
|
|
51
130
|
/**
|
|
@@ -81,15 +160,9 @@ export class NodeStyling {
|
|
|
81
160
|
// window size queries fold into the base before state blocks, so a
|
|
82
161
|
// `:hover` inside the wide layout still wins over the wide layout
|
|
83
162
|
const kinds = queryKinds(this._baseStyle);
|
|
163
|
+
// whether a resize re-resolves this node is decided at the end, once the
|
|
164
|
+
// style has said whether it reads the window through a viewport unit
|
|
84
165
|
const queried = (kinds & QUERY_SIZE) !== 0;
|
|
85
|
-
if (queried !== this._queried) {
|
|
86
|
-
this._queried = queried;
|
|
87
|
-
const root = this.root;
|
|
88
|
-
if (root?._sizeQueryNodes) {
|
|
89
|
-
if (queried) root._sizeQueryNodes.add(this);
|
|
90
|
-
else root._sizeQueryNodes.delete(this);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
166
|
// `@supports` blocks keep their own registry: what re-resolves them is
|
|
94
167
|
// the server's answer changing, not a resize
|
|
95
168
|
// Registered unconditionally rather than on change: a `<window>`'s own
|
|
@@ -166,20 +239,90 @@ export class NodeStyling {
|
|
|
166
239
|
}
|
|
167
240
|
}
|
|
168
241
|
this._stateful = hasStateStyles(this._baseStyle);
|
|
242
|
+
const merged = this._stateful
|
|
243
|
+
? resolveStyleStates(this._baseStyle, this.states)
|
|
244
|
+
: this._baseStyle;
|
|
169
245
|
// The scale multiplies *after* every merge — state blocks, queries,
|
|
170
|
-
// the
|
|
171
|
-
// pixels the app wrote, and device pixels exist only
|
|
172
|
-
// this line (src/scale.js).
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
246
|
+
// the shorthands, the relative units — so each of those keeps thinking
|
|
247
|
+
// in the logical pixels the app wrote, and device pixels exist only
|
|
248
|
+
// downstream of this line (src/scale.js).
|
|
249
|
+
this._unitsNow = null;
|
|
250
|
+
const target = scaleResolvedStyle(
|
|
251
|
+
resolveComputedStyle(merged, this._unitsMaker()),
|
|
252
|
+
this.scale,
|
|
253
|
+
);
|
|
254
|
+
const units = this._unitsNow ?? NO_UNITS;
|
|
255
|
+
// what a loop's relative end reads (`_syncLoops`) is read through the
|
|
256
|
+
// same context, during the swap
|
|
257
|
+
// …and the basis is in place before the swap, which may walk the text
|
|
258
|
+
// cascade back through this node (`_emMoved`)
|
|
259
|
+
this._units = units;
|
|
260
|
+
this._noteUnits(units, queried, false);
|
|
261
|
+
const out = this._retarget(target);
|
|
262
|
+
this._units = null;
|
|
263
|
+
this._noteUnits(units, queried, true);
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Record what this node's style read to resolve its relative lengths, and
|
|
269
|
+
* join or leave what moves each: the size-query registry for the window's
|
|
270
|
+
* size (a viewport unit asks the same question an `'@width'` block does,
|
|
271
|
+
* and is answered by the same re-resolution on a resize); the text cascade
|
|
272
|
+
* for the inherited font size (`_retext` walks it, and stops at a node
|
|
273
|
+
* whose resolution was never cached — so a node that reads it caches one);
|
|
274
|
+
* and the theme walk for the palette's (`_themeChanged`).
|
|
275
|
+
*/
|
|
276
|
+
/** What makes this node's unit context, for `resolveComputedStyle` to
|
|
277
|
+
* call only when a style has something to read; the one it made is
|
|
278
|
+
* `_unitsNow`. Made once per node, rather than a context per style. */
|
|
279
|
+
_unitsMaker() {
|
|
280
|
+
return (this._makeUnits ??= (style) =>
|
|
281
|
+
(this._unitsNow = new Units(this, style)));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
_noteUnits(units, sizeQueried, settled) {
|
|
285
|
+
this._unitDeps = units.deps;
|
|
286
|
+
this._emBasis = units.parentEmRead;
|
|
287
|
+
this._sizeQueried = sizeQueried;
|
|
288
|
+
const asks = sizeQueried || (units.deps & UNIT_VIEWPORT) !== 0;
|
|
289
|
+
if (asks !== this._queried) {
|
|
290
|
+
this._queried = asks;
|
|
291
|
+
const root = this.root;
|
|
292
|
+
if (root?._sizeQueryNodes) {
|
|
293
|
+
if (asks) root._sizeQueryNodes.add(this);
|
|
294
|
+
else root._sizeQueryNodes.delete(this);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// asked of the style the swap put in force, never the one before it
|
|
298
|
+
if (settled && units.deps & UNIT_EM && this._resolvedText === undefined) {
|
|
299
|
+
this.resolvedTextStyle();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Resolve the style again because something a relative length read moved
|
|
305
|
+
* — the font size this node inherits — with no commit to do it. The rest
|
|
306
|
+
* is the tail a re-resolution outside a commit always has
|
|
307
|
+
* (`_restyled`), plus the claim a commit would have made.
|
|
308
|
+
*/
|
|
309
|
+
_unitsMoved() {
|
|
310
|
+
if (this.destroyed) return;
|
|
311
|
+
const before = this.style;
|
|
312
|
+
this._syncStyle(this.props);
|
|
313
|
+
const moved = this._restyled(
|
|
314
|
+
before,
|
|
315
|
+
'resolved against a font size that moved',
|
|
182
316
|
);
|
|
317
|
+
if (moved) this._invalidateLayout('units');
|
|
318
|
+
else if (this.style !== before) this.root?.invalidate(false, this, 'units');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Whether the font size this node inherits is not the one its relative
|
|
322
|
+
* lengths were resolved against. */
|
|
323
|
+
_emMoved() {
|
|
324
|
+
if (!(this._unitDeps & UNIT_EM)) return false;
|
|
325
|
+
return this.inheritedTextStyle.size / (this.scale || 1) !== this._emBasis;
|
|
183
326
|
}
|
|
184
327
|
|
|
185
328
|
/**
|
|
@@ -194,12 +337,24 @@ export class NodeStyling {
|
|
|
194
337
|
// the node has a state block of its own (`_outlineExtent`)
|
|
195
338
|
this._clearPaintBounds();
|
|
196
339
|
if (!this._stateful || this.destroyed) return;
|
|
340
|
+
const merged = resolveStyleStates(this._baseStyle, this.states);
|
|
341
|
+
this._unitsNow = null;
|
|
197
342
|
const next = scaleResolvedStyle(
|
|
198
|
-
resolveComputedStyle(
|
|
343
|
+
resolveComputedStyle(merged, this._unitsMaker()),
|
|
199
344
|
this.scale,
|
|
200
345
|
);
|
|
201
346
|
if (shallowEqual(next, this._targetStyle)) return;
|
|
347
|
+
const units = this._unitsNow;
|
|
348
|
+
this._units = units;
|
|
202
349
|
this._retarget(next);
|
|
350
|
+
this._units = null;
|
|
351
|
+
// a state block reads what the base did and may read more — a radius in
|
|
352
|
+
// `rem` on `:hover` — and a node keeps answering to all of it
|
|
353
|
+
if (units !== null) {
|
|
354
|
+
units.deps |= this._unitDeps;
|
|
355
|
+
units.parentEmRead ??= this._emBasis;
|
|
356
|
+
this._noteUnits(units, this._sizeQueried, true);
|
|
357
|
+
}
|
|
203
358
|
// a state block may only set paint properties, so the node's own region
|
|
204
359
|
// is the whole of what changed
|
|
205
360
|
this.root?.invalidate(false, this, 'style-state');
|
|
@@ -137,7 +137,7 @@ export class WindowCapabilities {
|
|
|
137
137
|
* whatever it skipped would show the last frame's pixels.
|
|
138
138
|
*/
|
|
139
139
|
_windowBackground() {
|
|
140
|
-
const own = this.style.backgroundColor;
|
|
140
|
+
const own = this.resolveColor(this.style.backgroundColor);
|
|
141
141
|
if (isColor(own)) return own;
|
|
142
142
|
const palette = this.theme.background;
|
|
143
143
|
return isColor(palette) ? palette : 'white';
|
|
@@ -233,24 +233,24 @@ export function collectFloorStale(node, stale, found, sweep) {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/**
|
|
236
|
-
* Whether `node` is a box its
|
|
237
|
-
* size
|
|
236
|
+
* Whether `node` is a box its surroundings cannot size and nothing around it
|
|
237
|
+
* reads the size of — an edge a floor measurement may stop at
|
|
238
238
|
* (`WindowNode._floorsScope`).
|
|
239
239
|
*
|
|
240
|
-
* Absolutely positioned,
|
|
241
|
-
*
|
|
242
|
-
* reaches anything above it (`contentSpan` skips it, and no floor is
|
|
243
|
-
* on it), and
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
* host, whose children are measured tree by tree.
|
|
240
|
+
* Absolutely positioned, and the same size in every pass wherever it sits
|
|
241
|
+
* (`sizedAlike`): it is out of its parent's flow, so nothing measured inside
|
|
242
|
+
* it reaches anything above it (`contentSpan` skips it, and no floor is
|
|
243
|
+
* written on it), and what is inside it is measured the same in a pass over
|
|
244
|
+
* the window and in a pass over it alone. Nothing in its own box resolves
|
|
245
|
+
* against its parent — a percentage of padding would — and neither it nor
|
|
246
|
+
* its parent is a layout host, whose children are measured tree by tree.
|
|
248
247
|
*
|
|
249
|
-
* A card on a graph, a popover laid out inside the window, a sized overlay
|
|
250
|
-
*
|
|
251
|
-
* size of its own would do as well
|
|
252
|
-
* axis a flex line can grow it past
|
|
253
|
-
* the author named, and that is a
|
|
248
|
+
* A card on a graph, a popover laid out inside the window, a sized overlay,
|
|
249
|
+
* a badge or a label hung off a point: the boxes a change inside is most
|
|
250
|
+
* often confined to. An in-flow box with a size of its own would do as well
|
|
251
|
+
* along its cross axis, but on its main axis a flex line can grow it past
|
|
252
|
+
* its width or shrink it under a minimum the author named, and that is a
|
|
253
|
+
* question about its siblings.
|
|
254
254
|
*/
|
|
255
255
|
export function isFloorBoundary(node) {
|
|
256
256
|
const style = node.style;
|
|
@@ -259,18 +259,63 @@ export function isFloorBoundary(node) {
|
|
|
259
259
|
!node.isWindow &&
|
|
260
260
|
style.position === 'absolute' &&
|
|
261
261
|
style.display !== 'none' &&
|
|
262
|
-
typeof style.width === 'number' &&
|
|
263
|
-
typeof style.height === 'number' &&
|
|
264
262
|
typeof style.minWidth !== 'string' &&
|
|
265
263
|
typeof style.minHeight !== 'string' &&
|
|
266
264
|
typeof style.maxWidth !== 'string' &&
|
|
267
265
|
typeof style.maxHeight !== 'string' &&
|
|
268
266
|
!PADDINGS.some((key) => typeof style[key] === 'string') &&
|
|
269
267
|
node._host === null &&
|
|
270
|
-
(node.parent?._host ?? null) === null
|
|
268
|
+
(node.parent?._host ?? null) === null &&
|
|
269
|
+
sizedAlike(node, 'width') &&
|
|
270
|
+
sizedAlike(node, 'height')
|
|
271
271
|
);
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Whether every pass lays this absolute box out at the same size along
|
|
276
|
+
* `axis`, whatever its containing block is offered: a number its style
|
|
277
|
+
* names, or the size of what is inside it.
|
|
278
|
+
*
|
|
279
|
+
* Yoga measures an absolute box that names no size at its content's
|
|
280
|
+
* max-content size, with no bound from the box it is placed in — down, and
|
|
281
|
+
* across under a row. Under a column it is offered the column's width to
|
|
282
|
+
* wrap in, and that is another number in each pass (none in the width
|
|
283
|
+
* pass, the real one in the heights'), so what is inside it would be
|
|
284
|
+
* measured at whatever width the pass gave it, and not at the one a pass
|
|
285
|
+
* over it alone does. Nor may anything else bound it on that axis: an edge
|
|
286
|
+
* pinned at both ends sizes it from its containing block, a ratio from the
|
|
287
|
+
* other axis, and a minimum or a maximum is applied after its content was
|
|
288
|
+
* measured — yoga lays out the height of a box held under a `maxWidth` at
|
|
289
|
+
* the width it had before it was held, and a box laid out alone at the
|
|
290
|
+
* width it was held to.
|
|
291
|
+
*/
|
|
292
|
+
function sizedAlike(node, axis) {
|
|
293
|
+
const style = node.style;
|
|
294
|
+
const across = axis === 'width';
|
|
295
|
+
const size = across ? style.width : style.height;
|
|
296
|
+
if (typeof size === 'number') return true;
|
|
297
|
+
if (size !== undefined && size !== 'auto') return false;
|
|
298
|
+
if (
|
|
299
|
+
(across ? style.minWidth : style.minHeight) !== undefined ||
|
|
300
|
+
(across ? style.maxWidth : style.maxHeight) !== undefined ||
|
|
301
|
+
style.aspectRatio !== undefined
|
|
302
|
+
) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
// two insets across are both ends in one direction or the other
|
|
306
|
+
let insets = 0;
|
|
307
|
+
for (const key of across ? INSETS_ACROSS : INSETS_DOWN) {
|
|
308
|
+
if (style[key] !== undefined) insets++;
|
|
309
|
+
}
|
|
310
|
+
if (insets > 1) return false;
|
|
311
|
+
return (
|
|
312
|
+
!across || (node.parent != null && mainAxisOf(node.parent) === 'width')
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const INSETS_ACROSS = ['left', 'right', 'start', 'end'];
|
|
317
|
+
const INSETS_DOWN = ['top', 'bottom'];
|
|
318
|
+
|
|
274
319
|
const PADDINGS = [
|
|
275
320
|
'padding',
|
|
276
321
|
'paddingTop',
|