react-x11 2.49.0 → 2.51.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 +5 -1
- package/package.json +4 -4
- package/src/ClickToComponent.js +11 -132
- package/src/DevToolsIntegration.js +126 -42
- package/src/Reconciler.js +26 -10
- package/src/cli/dev.js +162 -19
- package/src/cli/errorwindow.js +368 -0
- package/src/cli/index.js +4 -0
- package/src/cocoa/app.js +22 -0
- package/src/cocoa/presenter.js +3 -1
- package/src/cocoa/promotion.js +10 -11
- package/src/cocoa/relaunch.js +114 -15
- package/src/cocoa/window.js +98 -15
- 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/scrollable.js +4 -1
- 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/elements.d.ts +5 -2
- package/src/types/style.d.ts +163 -39
- package/src/types/system.d.ts +4 -1
package/src/gloverlay.js
CHANGED
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
import { cssColorStraight } from 'ntk/color';
|
|
87
87
|
|
|
88
88
|
import { FULL_DAMAGE, addDamageRect } from './nodes/damage.js';
|
|
89
|
+
import { radiusOf } from './styles.js';
|
|
89
90
|
import {
|
|
90
91
|
innerPixels,
|
|
91
92
|
intersectRects,
|
|
@@ -1200,7 +1201,7 @@ export class GlOverlay {
|
|
|
1200
1201
|
}
|
|
1201
1202
|
const area = this.area;
|
|
1202
1203
|
// a rounded surface clips its children to an arc, and the arc stays put
|
|
1203
|
-
if ((area.style
|
|
1204
|
+
if (radiusOf(area.style, area.abs) > 0) return false;
|
|
1204
1205
|
// the whole pixels of the surface's box: a fractional edge is an
|
|
1205
1206
|
// antialiased clip, and that does not move either
|
|
1206
1207
|
const box = innerPixels(area.abs);
|
package/src/node.d.ts
CHANGED
|
@@ -624,6 +624,11 @@ export declare class Node {
|
|
|
624
624
|
* with none of its own is set in. An element that draws text inherits
|
|
625
625
|
* exactly what `<text>` inherits by asking. */
|
|
626
626
|
resolvedTextStyle(): TextStyle;
|
|
627
|
+
/** A colour from this node's style as one a context can paint: the value
|
|
628
|
+
* itself, or — for `currentColor` — the node's ink, the colour
|
|
629
|
+
* `resolvedTextStyle()` resolves. Everything core paints from a style
|
|
630
|
+
* colour asks here; an element painting one of its own should too. */
|
|
631
|
+
resolveColor<T>(value: T): T | string;
|
|
627
632
|
/** The flattened `style` prop with the active state blocks overlaid.
|
|
628
633
|
* Everything that paints or lays out reads this, never `props`. */
|
|
629
634
|
readonly style: Style;
|
package/src/nodes/animation.js
CHANGED
|
@@ -11,9 +11,11 @@ import {
|
|
|
11
11
|
transitionFor,
|
|
12
12
|
interpolate,
|
|
13
13
|
ease,
|
|
14
|
+
isColor,
|
|
14
15
|
isLayoutProp,
|
|
15
16
|
TEXT_LAYOUT_PROPS,
|
|
16
17
|
} from '../styles.js';
|
|
18
|
+
import { isCurrentColor } from '../currentcolor.js';
|
|
17
19
|
import { GRID_CONTAINER_PROPS, GRID_ITEM_PROPS } from '../grid.js';
|
|
18
20
|
import { isPlaced } from '../layouts.js';
|
|
19
21
|
import { desktopSettings, watchDesktopSettings } from '../desktopsettings.js';
|
|
@@ -85,11 +87,16 @@ export class NodeAnimation {
|
|
|
85
87
|
// transitions start on later changes, which is CSS's rule too.
|
|
86
88
|
if (this._placed) {
|
|
87
89
|
for (const prop of Object.keys(target)) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
let to = target[prop];
|
|
91
|
+
let from = displayed[prop];
|
|
90
92
|
if (from === to || from === undefined) continue;
|
|
91
93
|
const duration = transitionFor(target, prop);
|
|
92
94
|
if (duration <= 0) continue;
|
|
95
|
+
if (isCurrentColor(from) || isCurrentColor(to)) {
|
|
96
|
+
from = this._swapInk(prop, from, displayed);
|
|
97
|
+
to = this._swapInk(prop, to, target);
|
|
98
|
+
if (from === to) continue;
|
|
99
|
+
}
|
|
93
100
|
if (interpolate(from, to, 0.5) === null) continue; // no midpoint: snap
|
|
94
101
|
const entry = {
|
|
95
102
|
from,
|
|
@@ -260,6 +267,27 @@ export class NodeAnimation {
|
|
|
260
267
|
return this.style;
|
|
261
268
|
}
|
|
262
269
|
|
|
270
|
+
/**
|
|
271
|
+
* A `currentColor` end of a transition as the colour it is at the swap:
|
|
272
|
+
* the ink `style` gives this node, or on `color` itself the ink it
|
|
273
|
+
* inherits. The keyword has no midpoint of its own, so the transition
|
|
274
|
+
* runs between the two colours as they are now — and lands on the style,
|
|
275
|
+
* where the keyword follows the ink again. An ink that moves mid-flight
|
|
276
|
+
* is caught up with at the end, not chased: a `color` that should carry
|
|
277
|
+
* a border with it is the property to transition.
|
|
278
|
+
*/
|
|
279
|
+
_swapInk(prop, value, style) {
|
|
280
|
+
if (!isCurrentColor(value)) return value;
|
|
281
|
+
const inherited = this.inheritedTextStyle.color;
|
|
282
|
+
if (prop === 'color') return inherited;
|
|
283
|
+
// the displayed style's ink is cached; the target's is not yet
|
|
284
|
+
return style === this.style
|
|
285
|
+
? this.resolvedTextStyle().color
|
|
286
|
+
: isColor(style.color)
|
|
287
|
+
? style.color
|
|
288
|
+
: inherited;
|
|
289
|
+
}
|
|
290
|
+
|
|
263
291
|
_animatedValues() {
|
|
264
292
|
const values = {};
|
|
265
293
|
for (const [prop, a] of this._anim) {
|
|
@@ -388,7 +416,7 @@ export class NodeAnimation {
|
|
|
388
416
|
const specs =
|
|
389
417
|
target.animation == null
|
|
390
418
|
? null
|
|
391
|
-
: animationsOf(target, 'a style', this.scale);
|
|
419
|
+
: animationsOf(target, 'a style', this.scale, this._units);
|
|
392
420
|
if (!specs && !this._loops) return false;
|
|
393
421
|
this._loops = specs;
|
|
394
422
|
if (!specs) this.root?._forgetLoopNode(this);
|
package/src/nodes/boxpaint.js
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_FOCUS_RING,
|
|
7
7
|
isColor,
|
|
8
|
+
radiusOf,
|
|
8
9
|
resolveBorderWidths,
|
|
9
10
|
resolveBorderColors,
|
|
10
11
|
} from '../styles.js';
|
|
12
|
+
import { isCurrentColor } from '../currentcolor.js';
|
|
11
13
|
import {
|
|
12
14
|
blurKernel,
|
|
13
15
|
gradientSpec,
|
|
@@ -61,6 +63,19 @@ let warnedSideRadius = false;
|
|
|
61
63
|
|
|
62
64
|
/** Box decorations, installed onto `Node.prototype` by node.js. */
|
|
63
65
|
export class NodeBoxPaint {
|
|
66
|
+
/**
|
|
67
|
+
* A colour from this node's style as a colour a context can paint: the
|
|
68
|
+
* value itself, or — for CSS's `currentColor` — this node's resolved ink,
|
|
69
|
+
* its own `color` over what it inherits. Everything that paints a style
|
|
70
|
+
* colour asks here, on every backend, so the keyword means one thing
|
|
71
|
+
* wherever it is written; a registered element painting a colour of its
|
|
72
|
+
* own style asks the same. A node whose style paints in the keyword
|
|
73
|
+
* repaints when its ink moves (`_inkMoved`).
|
|
74
|
+
*/
|
|
75
|
+
resolveColor(value) {
|
|
76
|
+
return isCurrentColor(value) ? this.resolvedTextStyle().color : value;
|
|
77
|
+
}
|
|
78
|
+
|
|
64
79
|
/**
|
|
65
80
|
* The focus ring this node would draw: its own `outline*` style, falling
|
|
66
81
|
* back to the theme's focus-ring tokens for anything it leaves out. Null
|
|
@@ -80,7 +95,9 @@ export class NodeBoxPaint {
|
|
|
80
95
|
if (!(resolved > 0)) return null;
|
|
81
96
|
return {
|
|
82
97
|
width: resolved,
|
|
83
|
-
color:
|
|
98
|
+
color: this.resolveColor(
|
|
99
|
+
style.outlineColor ?? theme?.focusRing ?? DEFAULT_FOCUS_RING.color,
|
|
100
|
+
),
|
|
84
101
|
offset:
|
|
85
102
|
style.outlineOffset ??
|
|
86
103
|
theme?.focusRingOffset ??
|
|
@@ -138,7 +155,7 @@ export class NodeBoxPaint {
|
|
|
138
155
|
// stroked centred on the path, like the border, so half the width sits
|
|
139
156
|
// inside the offset gap and half outside it
|
|
140
157
|
const grow = offset + width / 2;
|
|
141
|
-
const radius = this.style.
|
|
158
|
+
const radius = radiusOf(this.style, this.abs);
|
|
142
159
|
ctx.strokeStyle = outline.color;
|
|
143
160
|
ctx.lineWidth = width;
|
|
144
161
|
ctx.beginPath();
|
|
@@ -162,7 +179,8 @@ export class NodeBoxPaint {
|
|
|
162
179
|
// under an element that covers this pass with opaque pixels, this fill
|
|
163
180
|
// is never seen (`WindowNode._coverFor`)
|
|
164
181
|
if (this.root?._coverChain?.has(this)) return;
|
|
165
|
-
const
|
|
182
|
+
const backgroundColor = this.resolveColor(this.style.backgroundColor);
|
|
183
|
+
const borderRadius = radiusOf(this.style, this.abs);
|
|
166
184
|
const fill = (style) => {
|
|
167
185
|
ctx.fillStyle = style;
|
|
168
186
|
if (borderRadius > 0) {
|
|
@@ -202,7 +220,11 @@ export class NodeBoxPaint {
|
|
|
202
220
|
const spec = gradientSpec(value, this.scale);
|
|
203
221
|
if (!spec) return null;
|
|
204
222
|
const { x, y, width, height } = rect;
|
|
205
|
-
|
|
223
|
+
// a stop in `currentColor` is the ink, which can move under a value
|
|
224
|
+
// that did not, so the ink is part of what the gradient is keyed by
|
|
225
|
+
const inked = spec.stops.some((stop) => isCurrentColor(stop.color));
|
|
226
|
+
const ink = inked ? this.resolvedTextStyle().color : '';
|
|
227
|
+
const key = `${value}|${x},${y},${width},${height}|${ink}`;
|
|
206
228
|
if (this._gradient?.key === key) return this._gradient.value;
|
|
207
229
|
const line = linearGradientGeometry(spec, rect);
|
|
208
230
|
if (!line) return null;
|
|
@@ -213,7 +235,7 @@ export class NodeBoxPaint {
|
|
|
213
235
|
line.y1,
|
|
214
236
|
);
|
|
215
237
|
for (const [offset, color] of line.stops)
|
|
216
|
-
gradient.addColorStop(offset, color);
|
|
238
|
+
gradient.addColorStop(offset, inked ? this.resolveColor(color) : color);
|
|
217
239
|
this._gradient = { key, value: gradient };
|
|
218
240
|
return gradient;
|
|
219
241
|
}
|
|
@@ -235,7 +257,7 @@ export class NodeBoxPaint {
|
|
|
235
257
|
if (!value || value === 'none') return;
|
|
236
258
|
const shadows = shadowSpecs(value, this.scale);
|
|
237
259
|
if (!shadows?.length) return;
|
|
238
|
-
const radius = this.style.
|
|
260
|
+
const radius = radiusOf(this.style, this.abs);
|
|
239
261
|
for (let i = shadows.length - 1; i >= 0; i--) {
|
|
240
262
|
const shadow = shadows[i];
|
|
241
263
|
// CSS's `currentColor`: this node's *resolved* ink, its own `color`
|
|
@@ -404,11 +426,15 @@ export class NodeBoxPaint {
|
|
|
404
426
|
}
|
|
405
427
|
|
|
406
428
|
_paintBorder(ctx) {
|
|
407
|
-
const
|
|
429
|
+
const borderRadius = radiusOf(this.style, this.abs);
|
|
408
430
|
// Both through the node's resolved direction: a `borderStartWidth` lays
|
|
409
431
|
// out on one side and has to paint on the same one.
|
|
410
432
|
const w = resolveBorderWidths(this.style, this.direction);
|
|
411
433
|
const colors = resolveBorderColors(this.style, this.direction);
|
|
434
|
+
colors.top = this.resolveColor(colors.top);
|
|
435
|
+
colors.right = this.resolveColor(colors.right);
|
|
436
|
+
colors.bottom = this.resolveColor(colors.bottom);
|
|
437
|
+
colors.left = this.resolveColor(colors.left);
|
|
412
438
|
const uniform =
|
|
413
439
|
w.top === w.right &&
|
|
414
440
|
w.top === w.bottom &&
|
|
@@ -469,7 +495,7 @@ export class NodeBoxPaint {
|
|
|
469
495
|
* strokes halfway.
|
|
470
496
|
*/
|
|
471
497
|
_paintBorderSides(ctx, w, colors) {
|
|
472
|
-
if (DEV && (this.style.
|
|
498
|
+
if (DEV && radiusOf(this.style, this.abs) > 0 && !warnedSideRadius) {
|
|
473
499
|
warnedSideRadius = true;
|
|
474
500
|
console.warn(
|
|
475
501
|
'react-x11: borderRadius needs uniform borders — same width and ' +
|
package/src/nodes/cascade.js
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
import {
|
|
6
6
|
applyLayoutStyle,
|
|
7
7
|
isColor,
|
|
8
|
+
paintsCurrentColor,
|
|
8
9
|
textStyleFrom,
|
|
9
10
|
DEFAULT_TEXT_STYLE,
|
|
10
11
|
localTextStyleChanged,
|
|
11
12
|
resolvedTextDelta,
|
|
12
13
|
TEXT_REMEASURE,
|
|
13
14
|
} from '../styles.js';
|
|
15
|
+
import { UNIT_EM, UNIT_REM } from '../shorthands.js';
|
|
14
16
|
import { Yoga } from '../yoga.js';
|
|
15
17
|
import { scaleOf } from '../scale.js';
|
|
16
18
|
import { baseTheme } from '../palette.js';
|
|
@@ -372,8 +374,22 @@ export class NodeCascade {
|
|
|
372
374
|
const before = this._resolvedText;
|
|
373
375
|
if (before === undefined) return 0;
|
|
374
376
|
this._resolvedText = undefined;
|
|
375
|
-
|
|
376
|
-
|
|
377
|
+
// A length in `em` is a length in the font size this node inherits,
|
|
378
|
+
// which is what this walk exists because of — so a node that wrote one
|
|
379
|
+
// resolves its style again here, before its own text is, since a
|
|
380
|
+
// `fontSize: '1.5em'` is part of what that text is set in.
|
|
381
|
+
if (this._emMoved()) this._unitsMoved();
|
|
382
|
+
const after = this.resolvedTextStyle();
|
|
383
|
+
const cost = resolvedTextDelta(before, after);
|
|
384
|
+
if (cost !== 0) {
|
|
385
|
+
this._textStyleMoved(cost);
|
|
386
|
+
// A node painting in `currentColor` resolved its ink to do it, so the
|
|
387
|
+
// walk reaches it — and its own style says nothing about the change,
|
|
388
|
+
// which was written on an ancestor or in its own `color`.
|
|
389
|
+
if (before.color !== after.color && paintsCurrentColor(this.style)) {
|
|
390
|
+
this._inkMoved();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
377
393
|
return cost;
|
|
378
394
|
}
|
|
379
395
|
|
|
@@ -405,6 +421,19 @@ export class NodeCascade {
|
|
|
405
421
|
}
|
|
406
422
|
}
|
|
407
423
|
|
|
424
|
+
/**
|
|
425
|
+
* The ink moved under a node that paints something in it — a border, a
|
|
426
|
+
* fill, a gradient stop or a shadow in `currentColor`. A repaint of the
|
|
427
|
+
* node, bounded to it, and a box needs it although it draws no text
|
|
428
|
+
* (`BoxNode._textStyleMoved`): the ancestor whose `color` moved claimed
|
|
429
|
+
* its own box, which covers this one on a surface but names no node to a
|
|
430
|
+
* retained presenter re-rastering by node, and no other window to a
|
|
431
|
+
* `<popup>` written under it.
|
|
432
|
+
*/
|
|
433
|
+
_inkMoved() {
|
|
434
|
+
this.root?.invalidate(false, this, 'ink');
|
|
435
|
+
}
|
|
436
|
+
|
|
408
437
|
/**
|
|
409
438
|
* Whether this node's ancestry is complete, so a `$token` that does not
|
|
410
439
|
* resolve is a mistake rather than a node that has not been placed yet.
|
|
@@ -508,7 +537,9 @@ export class NodeCascade {
|
|
|
508
537
|
// and the server's copy of that colour has to follow with it — otherwise
|
|
509
538
|
// the next resize fills the new area in the old scheme.
|
|
510
539
|
if (this.isWindow) this._syncWindowBackground();
|
|
511
|
-
|
|
540
|
+
// A relative length reads the palette too: `rem` is its `fontSize`,
|
|
541
|
+
// and `em` is what that size became by the time it reached this node.
|
|
542
|
+
if (this._usesTokens || this._unitDeps & (UNIT_REM | UNIT_EM)) {
|
|
512
543
|
const before = this.style;
|
|
513
544
|
this._syncStyle(this.props, mounting);
|
|
514
545
|
// a token change reaches the node without React re-rendering it, so
|
|
@@ -516,6 +547,15 @@ export class NodeCascade {
|
|
|
516
547
|
if (localTextStyleChanged(this.style, before)) {
|
|
517
548
|
this._textContentChanged();
|
|
518
549
|
}
|
|
550
|
+
// …and so does the layout half. A token in a layout value —
|
|
551
|
+
// `padding: '$gutter'` — resolves right here, on attach or on a
|
|
552
|
+
// palette swap, and yoga was last handed the style the constructor
|
|
553
|
+
// had, with its tokens stripped. Same tail as `_sizeQueriesChanged`
|
|
554
|
+
// and `_rescaled`; without it a themed gutter reached `this.style`
|
|
555
|
+
// and the paint, and the layout kept the un-themed value.
|
|
556
|
+
if (this.yoga && this.style !== before) {
|
|
557
|
+
applyLayoutStyle(this.yoga, this.style, before);
|
|
558
|
+
}
|
|
519
559
|
if (!mounting) this.root?.invalidate(true, null, 'theme');
|
|
520
560
|
}
|
|
521
561
|
// The palette is the floor under the cascade, so a theme swap moves the
|
|
@@ -624,4 +664,11 @@ export class WindowCascade {
|
|
|
624
664
|
/** Like a `<box>`: a window fills and never letters, so it is the top of
|
|
625
665
|
* the cascade rather than a reader of it. */
|
|
626
666
|
_textStyleMoved() {}
|
|
667
|
+
|
|
668
|
+
/** …except a fill in `currentColor`, which the server keeps a copy of for
|
|
669
|
+
* what a resize exposes. */
|
|
670
|
+
_inkMoved() {
|
|
671
|
+
this._syncWindowBackground();
|
|
672
|
+
this.invalidate(false, null, 'ink');
|
|
673
|
+
}
|
|
627
674
|
}
|
package/src/nodes/damage.js
CHANGED
|
@@ -86,8 +86,10 @@ export const INVALIDATE_REASONS = new Set([
|
|
|
86
86
|
'shadow', // a boxShadow got smaller: where it *was* still owes a repaint
|
|
87
87
|
'outline', // …and the same for an outline a style swap took away
|
|
88
88
|
'theme', // a theme/token change restyled a subtree
|
|
89
|
+
'ink', // the ink under a currentColor moved: its `color` or an ancestor's
|
|
89
90
|
'direction', // the reading direction moved: sides, glyph order, bar edge
|
|
90
91
|
'scale', // a `scale` prop zoomed a subtree: every length in it moved
|
|
92
|
+
'units', // a font size an `em` length read moved, and the style with it
|
|
91
93
|
'animation', // a transition frame
|
|
92
94
|
'scroll', // scrollTo/scrollBy/scrollIntoView, textarea/textinput panning
|
|
93
95
|
'text', // text content, input value or caret editing
|
package/src/nodes/layout.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { Yoga } from '../yoga.js';
|
|
6
6
|
import { callHandler } from '../errors.js';
|
|
7
7
|
import { DAMAGE_SLOP, layoutDiff } from './damage.js';
|
|
8
|
-
import { resolveBorderWidths } from '../styles.js';
|
|
8
|
+
import { radiusOf, resolveBorderWidths } from '../styles.js';
|
|
9
9
|
import { insetRect, intersectRects, outerPixels, outside } from './rects.js';
|
|
10
10
|
import { DEV } from './util.js';
|
|
11
11
|
|
|
@@ -612,25 +612,17 @@ export class NodeLayout {
|
|
|
612
612
|
// (`roundedCorners`), so a resize that moves that moves all four
|
|
613
613
|
// corners — and their outline and shadow with them — and the ones at the
|
|
614
614
|
// edges that held are in no band along the edges that moved.
|
|
615
|
-
|
|
615
|
+
// A percentage radius moves with the box for the same reason: it is a
|
|
616
|
+
// share of the shorter side (`radiusOf`), read off each rect.
|
|
616
617
|
const now = this.abs;
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
) {
|
|
622
|
-
return null;
|
|
623
|
-
}
|
|
618
|
+
const corner = (rect) =>
|
|
619
|
+
Math.min(radiusOf(style, rect), rect.width / 2, rect.height / 2);
|
|
620
|
+
const radius = Math.max(radiusOf(style, old), radiusOf(style, now));
|
|
621
|
+
if (radius > 0 && corner(old) !== corner(now)) return null;
|
|
624
622
|
const bw = resolveBorderWidths(style, this.direction);
|
|
625
623
|
const reach =
|
|
626
|
-
Math.max(
|
|
627
|
-
|
|
628
|
-
bw.top,
|
|
629
|
-
bw.right,
|
|
630
|
-
bw.bottom,
|
|
631
|
-
bw.left,
|
|
632
|
-
grow,
|
|
633
|
-
) + DAMAGE_SLOP;
|
|
624
|
+
Math.max(radius, bw.top, bw.right, bw.bottom, bw.left, grow) +
|
|
625
|
+
DAMAGE_SLOP;
|
|
634
626
|
const bands = [];
|
|
635
627
|
const oldBottom = old.y + old.height;
|
|
636
628
|
const newBottom = now.y + now.height;
|
package/src/nodes/moveblit.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// mistake it can make is not firing.
|
|
17
17
|
|
|
18
18
|
import { cssColorStraight } from 'ntk/color';
|
|
19
|
-
import { EMPTY_STYLE, resolveBorderWidths } from '../styles.js';
|
|
19
|
+
import { EMPTY_STYLE, radiusOf, resolveBorderWidths } from '../styles.js';
|
|
20
20
|
import { isPaintedColor } from './boxpaint.js';
|
|
21
21
|
import { DAMAGE_SLOP, FULL_DAMAGE, addDamageRect } from './damage.js';
|
|
22
22
|
import { NodePaint } from './paint.js';
|
|
@@ -100,7 +100,7 @@ function cornerSquares(box, radius) {
|
|
|
100
100
|
* Null when nothing is left.
|
|
101
101
|
*/
|
|
102
102
|
function clipBoxOf(node) {
|
|
103
|
-
const radius = node.style
|
|
103
|
+
const radius = radiusOf(node.style, node.abs);
|
|
104
104
|
return innerPixels(radius > 0 ? insetRect(node.abs, radius) : node.abs);
|
|
105
105
|
}
|
|
106
106
|
|
|
@@ -166,9 +166,9 @@ export class NodeMoveBlit {
|
|
|
166
166
|
const style = node.style ?? EMPTY_STYLE;
|
|
167
167
|
if (style.display === 'none') return null;
|
|
168
168
|
if (style.opacity !== undefined && !(style.opacity >= 1)) return null;
|
|
169
|
-
const radius = style.
|
|
169
|
+
const radius = radiusOf(style, node.abs);
|
|
170
170
|
let own = null;
|
|
171
|
-
if (isOpaqueColor(style.backgroundColor)) {
|
|
171
|
+
if (isOpaqueColor(node.resolveColor(style.backgroundColor))) {
|
|
172
172
|
own = node.abs;
|
|
173
173
|
holes.push(...cornerSquares(node.abs, radius));
|
|
174
174
|
} else {
|
|
@@ -581,7 +581,7 @@ export class WindowMoveBlit {
|
|
|
581
581
|
if (ring > 0) {
|
|
582
582
|
const inner = insetRect(
|
|
583
583
|
n.abs,
|
|
584
|
-
Math.max(ring, style.
|
|
584
|
+
Math.max(ring, radiusOf(style, n.abs)) + DAMAGE_SLOP,
|
|
585
585
|
);
|
|
586
586
|
const outer = insetRect(n.abs, -DAMAGE_SLOP);
|
|
587
587
|
for (const piece of outside(outer, inner)) over(piece);
|
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/scrollable.js
CHANGED
|
@@ -247,7 +247,10 @@ export const Scrollable = (Base) =>
|
|
|
247
247
|
// NaN, which a scroll pane in there measured its content from and
|
|
248
248
|
// threw on, out of the frame. The box itself has its empty rect;
|
|
249
249
|
// what is inside is placed when it is shown and laid out again.
|
|
250
|
-
|
|
250
|
+
// Both routes to it: the style, and the `hidden` React sets for
|
|
251
|
+
// `<Activity>` and `<Suspense>` (`setHidden`), which reaches yoga
|
|
252
|
+
// without touching the style.
|
|
253
|
+
if (this.style.display !== 'none' && !this.hidden) {
|
|
251
254
|
this._absolutizeChildren(
|
|
252
255
|
this.abs.x,
|
|
253
256
|
this.abs.y,
|
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,
|