@react-x11/components 0.16.1 → 0.17.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/dist/html/css/animation.d.ts +91 -0
- package/dist/html/css/animation.d.ts.map +1 -0
- package/dist/html/css/animation.js +430 -0
- package/dist/html/css/animation.js.map +1 -0
- package/dist/html/css/cascade.d.ts +27 -4
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +94 -6
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/parse.d.ts +35 -0
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +115 -2
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +5 -0
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +57 -7
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/layout/flex.d.ts.map +1 -1
- package/dist/html/layout/flex.js +139 -80
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +8 -0
- package/dist/html/node.js.map +1 -1
- package/package.json +1 -1
- package/src/html/css/animation.ts +466 -0
- package/src/html/css/cascade.ts +130 -7
- package/src/html/css/parse.ts +151 -2
- package/src/html/css/style.ts +69 -7
- package/src/html/layout/flex.ts +146 -83
- package/src/html/node.ts +8 -0
package/src/html/css/parse.ts
CHANGED
|
@@ -77,6 +77,43 @@ export interface Stylesheet {
|
|
|
77
77
|
/** `@counter-style` rules, in order: a name and its descriptors, which
|
|
78
78
|
* the cascade reads into counter styles (CSS Counter Styles 3, 3). */
|
|
79
79
|
counterStyles?: { prelude: string; declarations: Declaration[] }[];
|
|
80
|
+
/** `@keyframes` rules, in order (`KeyframesRule`). */
|
|
81
|
+
keyframes?: KeyframesRule[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* One `@keyframes` rule (CSS Animations 1, 3): the frames of the animation
|
|
86
|
+
* an `animation-name` names. Which rule a name finds is the cascade's to
|
|
87
|
+
* say (`Cascade.keyframes`): the last of its name whose media hold, in the
|
|
88
|
+
* latest layer, and never a prefixed one over one that is not.
|
|
89
|
+
*/
|
|
90
|
+
export interface KeyframesRule {
|
|
91
|
+
/** As written: a name is case-sensitive, as a custom ident is. */
|
|
92
|
+
name: string;
|
|
93
|
+
/** `@-webkit-keyframes`, which Chrome reads and which never takes the
|
|
94
|
+
* place of an `@keyframes` of the same name, wherever either comes. */
|
|
95
|
+
prefixed: boolean;
|
|
96
|
+
/** The frames in the order written, which is the order two at one
|
|
97
|
+
* offset cascade in. */
|
|
98
|
+
frames: Keyframe[];
|
|
99
|
+
/** The `@media` blocks the rule sits under, as a style rule's. */
|
|
100
|
+
media: MediaCondition[][] | null;
|
|
101
|
+
/** The cascade layer the rule is in, as a style rule's. */
|
|
102
|
+
layer: readonly number[] | null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** One frame of a `@keyframes`: a block of declarations at the offsets its
|
|
106
|
+
* selector names. */
|
|
107
|
+
export interface Keyframe {
|
|
108
|
+
/** Where in an iteration the frame is, 0 to 1: one per selector in its
|
|
109
|
+
* list, `from` 0 and `to` 1. */
|
|
110
|
+
offsets: number[];
|
|
111
|
+
/** What the frame sets: no `!important` one, which a frame ignores, and
|
|
112
|
+
* none of the animation's or a transition's own properties. */
|
|
113
|
+
declarations: Declaration[];
|
|
114
|
+
/** The `animation-timing-function` the frame eases to the next one by,
|
|
115
|
+
* as written; null where it gives none and the animation's is used. */
|
|
116
|
+
easing: string | null;
|
|
80
117
|
}
|
|
81
118
|
|
|
82
119
|
/**
|
|
@@ -322,9 +359,22 @@ export function parseStylesheet(
|
|
|
322
359
|
prelude: at.prelude,
|
|
323
360
|
declarations: parseDeclarations(at.block),
|
|
324
361
|
});
|
|
362
|
+
} else if (
|
|
363
|
+
(name === 'keyframes' || name === '-webkit-keyframes') &&
|
|
364
|
+
at.block !== null
|
|
365
|
+
) {
|
|
366
|
+
const keyframes = parseKeyframes(at.prelude, at.block);
|
|
367
|
+
if (keyframes) {
|
|
368
|
+
(sheet.keyframes ??= []).push({
|
|
369
|
+
...keyframes,
|
|
370
|
+
prefixed: name !== 'keyframes',
|
|
371
|
+
media,
|
|
372
|
+
layer,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
325
375
|
}
|
|
326
|
-
// @
|
|
327
|
-
// consumed.
|
|
376
|
+
// @page, and an at-rule nobody knows: nothing to do, and the block
|
|
377
|
+
// was already consumed.
|
|
328
378
|
continue;
|
|
329
379
|
}
|
|
330
380
|
|
|
@@ -1764,6 +1814,105 @@ const GENERIC_FAMILIES = new Set([
|
|
|
1764
1814
|
'default',
|
|
1765
1815
|
]);
|
|
1766
1816
|
|
|
1817
|
+
/** The properties a keyframe does not set: the animation's own, and a
|
|
1818
|
+
* transition's (CSS Animations 1, 3). */
|
|
1819
|
+
const NOT_IN_KEYFRAMES = /^(?:-webkit-)?(?:animation|transition)(?:-|$)/;
|
|
1820
|
+
|
|
1821
|
+
/** A `<number-token>` percentage, `+12.5%` or `1e2%`. */
|
|
1822
|
+
const KEYFRAME_PERCENT = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?%$/i;
|
|
1823
|
+
|
|
1824
|
+
/**
|
|
1825
|
+
* A `@keyframes` block's name and frames (CSS Animations 1, 3), or null
|
|
1826
|
+
* where its prelude is no name. A frame whose selector is not a list of
|
|
1827
|
+
* `from`, `to` and percentages from 0 to 100 is dropped whole, as a style
|
|
1828
|
+
* rule with a bad selector is, and the frames around it stand.
|
|
1829
|
+
*/
|
|
1830
|
+
function parseKeyframes(
|
|
1831
|
+
prelude: string,
|
|
1832
|
+
block: string,
|
|
1833
|
+
): { name: string; frames: Keyframe[] } | null {
|
|
1834
|
+
const name = keyframesName(prelude);
|
|
1835
|
+
if (name === null) return null;
|
|
1836
|
+
const frames: Keyframe[] = [];
|
|
1837
|
+
let i = 0;
|
|
1838
|
+
const n = block.length;
|
|
1839
|
+
while (i < n) {
|
|
1840
|
+
i = skipTrivia(block, i);
|
|
1841
|
+
if (i >= n) break;
|
|
1842
|
+
// a frame holds declarations and nothing else
|
|
1843
|
+
if (block[i] === '@' && startsIdent(block, i + 1)) {
|
|
1844
|
+
i = readAtRule(block, i).end;
|
|
1845
|
+
continue;
|
|
1846
|
+
}
|
|
1847
|
+
const braceAt = scanTo(block, i, '{');
|
|
1848
|
+
if (braceAt >= n) break;
|
|
1849
|
+
const selector = block.slice(i, braceAt);
|
|
1850
|
+
const body = readBlock(block, braceAt);
|
|
1851
|
+
i = body.end;
|
|
1852
|
+
const offsets = keyframeOffsets(selector);
|
|
1853
|
+
if (!offsets) continue;
|
|
1854
|
+
let easing: string | null = null;
|
|
1855
|
+
const declarations: Declaration[] = [];
|
|
1856
|
+
for (const d of parseDeclarations(body.body)) {
|
|
1857
|
+
if (d.important) continue;
|
|
1858
|
+
if (
|
|
1859
|
+
d.prop === 'animation-timing-function' ||
|
|
1860
|
+
d.prop === '-webkit-animation-timing-function'
|
|
1861
|
+
) {
|
|
1862
|
+
easing = d.value;
|
|
1863
|
+
} else if (!NOT_IN_KEYFRAMES.test(d.prop)) {
|
|
1864
|
+
declarations.push(d);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
frames.push({ offsets, declarations, easing });
|
|
1868
|
+
}
|
|
1869
|
+
return { name, frames };
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
/** A `@keyframes` prelude's name: a string, or an identifier that is not
|
|
1873
|
+
* `none` or a CSS-wide keyword; null for anything else. */
|
|
1874
|
+
function keyframesName(prelude: string): string | null {
|
|
1875
|
+
const v = prelude.trim();
|
|
1876
|
+
if (!v) return null;
|
|
1877
|
+
if (v[0] === '"' || v[0] === "'") {
|
|
1878
|
+
const name = unquote(v);
|
|
1879
|
+
return name !== v && name ? name : null;
|
|
1880
|
+
}
|
|
1881
|
+
if (!startsIdent(v, 0)) return null;
|
|
1882
|
+
const ident = readIdent(v, 0);
|
|
1883
|
+
if (ident.end !== v.length) return null;
|
|
1884
|
+
return KEYFRAMES_RESERVED.has(ident.value.toLowerCase()) ? null : ident.value;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
const KEYFRAMES_RESERVED = new Set([
|
|
1888
|
+
'none',
|
|
1889
|
+
'inherit',
|
|
1890
|
+
'initial',
|
|
1891
|
+
'unset',
|
|
1892
|
+
'revert',
|
|
1893
|
+
'revert-layer',
|
|
1894
|
+
'default',
|
|
1895
|
+
]);
|
|
1896
|
+
|
|
1897
|
+
/** A keyframe selector's offsets, 0 to 1, or null where any of its list is
|
|
1898
|
+
* not one. */
|
|
1899
|
+
function keyframeOffsets(selector: string): number[] | null {
|
|
1900
|
+
const out: number[] = [];
|
|
1901
|
+
for (const part of selector.split(',')) {
|
|
1902
|
+
const v = part.trim().toLowerCase();
|
|
1903
|
+
if (v === 'from') out.push(0);
|
|
1904
|
+
else if (v === 'to') out.push(1);
|
|
1905
|
+
else if (KEYFRAME_PERCENT.test(v)) {
|
|
1906
|
+
const pct = Number(v.slice(0, -1));
|
|
1907
|
+
if (!(pct >= 0 && pct <= 100)) return null;
|
|
1908
|
+
out.push(pct / 100);
|
|
1909
|
+
} else {
|
|
1910
|
+
return null;
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
return out;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1767
1916
|
/**
|
|
1768
1917
|
* A `@font-face` block, or null when it names no family or no source — a
|
|
1769
1918
|
* rule CSS Fonts 4 (4.3) drops whole. A rule whose sources are all
|
package/src/html/css/style.ts
CHANGED
|
@@ -41,6 +41,13 @@ import type { ContentItem, CounterChange } from './content.js';
|
|
|
41
41
|
import type { CustomProps } from './vars.js';
|
|
42
42
|
import { parseRotate, parseScale, parseTransform } from './transform.js';
|
|
43
43
|
import type { TransformFunction } from './transform.js';
|
|
44
|
+
import {
|
|
45
|
+
ANIMATION_LONGHANDS,
|
|
46
|
+
NO_ANIMATIONS,
|
|
47
|
+
animationLonghand,
|
|
48
|
+
parseAnimation,
|
|
49
|
+
} from './animation.js';
|
|
50
|
+
import type { Animations } from './animation.js';
|
|
44
51
|
import {
|
|
45
52
|
LIGHT_DARK,
|
|
46
53
|
SYSTEM_COLOR,
|
|
@@ -590,6 +597,10 @@ export interface ComputedStyle {
|
|
|
590
597
|
/** `transform-origin`: the point a transform turns and scales about,
|
|
591
598
|
* from the border box's top left, a percentage of its width and height. */
|
|
592
599
|
transformOrigin: [Len, Len];
|
|
600
|
+
/** `animation` and its longhands, each a list (`css/animation.ts`):
|
|
601
|
+
* `NO_ANIMATIONS`, shared, where nothing sets one. What they leave on
|
|
602
|
+
* the style is in its other fields already (`Cascade._computeStyle`). */
|
|
603
|
+
animations: Animations;
|
|
593
604
|
zIndex: number | 'auto';
|
|
594
605
|
/** A keyword, a length to raise the box by, or a percentage of its own
|
|
595
606
|
* line height. */
|
|
@@ -1085,6 +1096,7 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
|
|
|
1085
1096
|
scale: null,
|
|
1086
1097
|
transform: null,
|
|
1087
1098
|
transformOrigin: [{ pct: 50 }, { pct: 50 }],
|
|
1099
|
+
animations: NO_ANIMATIONS,
|
|
1088
1100
|
zIndex: AUTO,
|
|
1089
1101
|
verticalAlign: 'baseline',
|
|
1090
1102
|
|
|
@@ -1438,13 +1450,23 @@ const BORDER_WIDTH_KEYWORDS: Record<string, number> = {
|
|
|
1438
1450
|
thick: 5,
|
|
1439
1451
|
};
|
|
1440
1452
|
|
|
1441
|
-
/** `-webkit-` names that are the multicol properties' own
|
|
1442
|
-
|
|
1453
|
+
/** `-webkit-` names that are the multicol properties' own, and the
|
|
1454
|
+
* animation's. */
|
|
1455
|
+
const PREFIXED_ALIASES: Record<string, string> = {
|
|
1443
1456
|
'-webkit-columns': 'columns',
|
|
1444
1457
|
'-webkit-column-count': 'column-count',
|
|
1445
1458
|
'-webkit-column-width': 'column-width',
|
|
1446
1459
|
'-webkit-column-gap': 'column-gap',
|
|
1447
1460
|
'-webkit-column-break-inside': 'break-inside',
|
|
1461
|
+
'-webkit-animation': 'animation',
|
|
1462
|
+
'-webkit-animation-name': 'animation-name',
|
|
1463
|
+
'-webkit-animation-duration': 'animation-duration',
|
|
1464
|
+
'-webkit-animation-timing-function': 'animation-timing-function',
|
|
1465
|
+
'-webkit-animation-delay': 'animation-delay',
|
|
1466
|
+
'-webkit-animation-iteration-count': 'animation-iteration-count',
|
|
1467
|
+
'-webkit-animation-direction': 'animation-direction',
|
|
1468
|
+
'-webkit-animation-fill-mode': 'animation-fill-mode',
|
|
1469
|
+
'-webkit-animation-play-state': 'animation-play-state',
|
|
1448
1470
|
};
|
|
1449
1471
|
|
|
1450
1472
|
/**
|
|
@@ -1463,11 +1485,11 @@ export function applyDeclaration(
|
|
|
1463
1485
|
ctx: UnitContext,
|
|
1464
1486
|
): void {
|
|
1465
1487
|
const written = prop.toLowerCase();
|
|
1466
|
-
// the multicol properties under the names WebKit had them
|
|
1467
|
-
// Blink still reads, each an alias of the property (`-webkit-
|
|
1468
|
-
// count` in its `css_properties.json5`): a page written for it
|
|
1469
|
-
// names no other
|
|
1470
|
-
const name =
|
|
1488
|
+
// the multicol and animation properties under the names WebKit had them
|
|
1489
|
+
// by, which Blink still reads, each an alias of the property (`-webkit-
|
|
1490
|
+
// column-count` in its `css_properties.json5`): a page written for it
|
|
1491
|
+
// alone names no other
|
|
1492
|
+
const name = PREFIXED_ALIASES[written] ?? written;
|
|
1471
1493
|
let value = rawValue.trim();
|
|
1472
1494
|
if (!value) return;
|
|
1473
1495
|
// a logical property is the physical one it stands for, the CSS-wide
|
|
@@ -1906,6 +1928,27 @@ export function applyDeclaration(
|
|
|
1906
1928
|
if (a !== null) style.opacity = a;
|
|
1907
1929
|
return;
|
|
1908
1930
|
}
|
|
1931
|
+
case 'animation': {
|
|
1932
|
+
const animations = parseAnimation(value);
|
|
1933
|
+
if (animations) style.animations = animations;
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
case 'animation-name':
|
|
1937
|
+
case 'animation-duration':
|
|
1938
|
+
case 'animation-timing-function':
|
|
1939
|
+
case 'animation-delay':
|
|
1940
|
+
case 'animation-iteration-count':
|
|
1941
|
+
case 'animation-direction':
|
|
1942
|
+
case 'animation-fill-mode':
|
|
1943
|
+
case 'animation-play-state': {
|
|
1944
|
+
const animations = animationLonghand(
|
|
1945
|
+
style.animations,
|
|
1946
|
+
ANIMATION_LONGHANDS[name],
|
|
1947
|
+
value,
|
|
1948
|
+
);
|
|
1949
|
+
if (animations) style.animations = animations;
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1909
1952
|
case 'translate': {
|
|
1910
1953
|
// `none`, or across, down, and a depth nothing here has
|
|
1911
1954
|
if (value.trim().toLowerCase() === 'none') {
|
|
@@ -5646,6 +5689,14 @@ export function initialOne(
|
|
|
5646
5689
|
style.mask = { ...style.mask, [list]: initial.mask[list] };
|
|
5647
5690
|
return;
|
|
5648
5691
|
}
|
|
5692
|
+
const animation = ANIMATION_LONGHANDS[PREFIXED_ALIASES[name] ?? name];
|
|
5693
|
+
if (animation) {
|
|
5694
|
+
style.animations = {
|
|
5695
|
+
...style.animations,
|
|
5696
|
+
[animation]: initial.animations[animation],
|
|
5697
|
+
};
|
|
5698
|
+
return;
|
|
5699
|
+
}
|
|
5649
5700
|
const keys = INHERIT_TARGETS[name];
|
|
5650
5701
|
if (!keys) return;
|
|
5651
5702
|
for (const key of keys) {
|
|
@@ -5663,6 +5714,15 @@ function inheritOne(
|
|
|
5663
5714
|
style.mask = { ...style.mask, [list]: parent.mask[list] };
|
|
5664
5715
|
return;
|
|
5665
5716
|
}
|
|
5717
|
+
// each longhand of the animation is a list of its own, as the mask's are
|
|
5718
|
+
const animation = ANIMATION_LONGHANDS[PREFIXED_ALIASES[name] ?? name];
|
|
5719
|
+
if (animation) {
|
|
5720
|
+
style.animations = {
|
|
5721
|
+
...style.animations,
|
|
5722
|
+
[animation]: parent.animations[animation],
|
|
5723
|
+
};
|
|
5724
|
+
return;
|
|
5725
|
+
}
|
|
5666
5726
|
const keys = INHERIT_TARGETS[name];
|
|
5667
5727
|
if (!keys) return;
|
|
5668
5728
|
// A border colour left to `currentColor` inherits as the keyword and
|
|
@@ -5717,6 +5777,8 @@ const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
|
|
|
5717
5777
|
'border-image': ['borderImage'],
|
|
5718
5778
|
mask: ['mask'],
|
|
5719
5779
|
'-webkit-mask': ['mask'],
|
|
5780
|
+
animation: ['animations'],
|
|
5781
|
+
'-webkit-animation': ['animations'],
|
|
5720
5782
|
'border-image-source': ['borderImage'],
|
|
5721
5783
|
'border-image-slice': ['borderImage'],
|
|
5722
5784
|
'border-image-width': ['borderImage'],
|
package/src/html/layout/flex.ts
CHANGED
|
@@ -252,11 +252,7 @@ export function layoutFlex(
|
|
|
252
252
|
const direction =
|
|
253
253
|
box.style.direction === 'rtl' ? Y.DIRECTION_RTL : Y.DIRECTION_LTR;
|
|
254
254
|
const row = box.style.flexDirection.startsWith('row');
|
|
255
|
-
|
|
256
|
-
// baselines, in one pass of its own, which drops a margin
|
|
257
|
-
// (`lineMarginFix`) — and not those of a column that does not wrap,
|
|
258
|
-
// whatever it aligns by, whose margins Yoga keeps
|
|
259
|
-
let baselines =
|
|
255
|
+
const baselines =
|
|
260
256
|
row &&
|
|
261
257
|
(box.style.alignItems === 'baseline' ||
|
|
262
258
|
items.some(({ box: child }) => child.style.alignSelf === 'baseline'));
|
|
@@ -273,35 +269,41 @@ export function layoutFlex(
|
|
|
273
269
|
if (aligned) node.setAlignSelf(ALIGN[aligned]);
|
|
274
270
|
}
|
|
275
271
|
} else if (baselines) {
|
|
276
|
-
// Yoga takes a row
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
// no height of its own was as tall as both. And it sets an item with
|
|
283
|
-
// an `auto` margin by its baseline: at the top, where the margin puts
|
|
284
|
-
// it at the bottom, and every item beside it as far down as its height
|
|
285
|
-
// reached. That item takes no part in aligning the line by baselines
|
|
286
|
-
// (9.4 step 8), and Yoga is told the alignment its margins come to.
|
|
287
|
-
// With fewer than two items left that do take part, they come to their
|
|
288
|
-
// line's start (8.3), which Yoga is told as well: then nothing in the
|
|
289
|
-
// row is aligned by its baseline, Yoga takes no such pass, and the line
|
|
290
|
-
// is the row. Two or more are lined up by their baselines once those
|
|
291
|
-
// are known (`baselineLines`), and the row laid out again with none.
|
|
272
|
+
// Yoga takes a row aligned by baselines through that pass whether it
|
|
273
|
+
// wraps or not, and sets an item with an `auto` margin there by its
|
|
274
|
+
// baseline: at the top, where the margin puts it at the bottom, and
|
|
275
|
+
// every item beside it as far down as its height reached. That item
|
|
276
|
+
// takes no part in aligning the line by baselines (9.4 step 8), and
|
|
277
|
+
// Yoga is told the alignment its margins come to.
|
|
292
278
|
for (const { box: child, node } of items) {
|
|
293
279
|
const aligned = autoAcross(child.style, false);
|
|
294
280
|
if (aligned) node.setAlignSelf(ALIGN[aligned]);
|
|
295
281
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
282
|
+
}
|
|
283
|
+
// Items aligned by their baselines, which Yoga cannot see, are told they
|
|
284
|
+
// are at their lines' starts, which is where one is that shares its line
|
|
285
|
+
// with no other (8.3); those that do share one are lined up by their
|
|
286
|
+
// baselines once those are known (`baselineLines`), and the row laid out
|
|
287
|
+
// again. Told an item is aligned by its baseline, Yoga sizes each line
|
|
288
|
+
// of the row as the most any item on it reaches down to its bottom edge
|
|
289
|
+
// — a baseline, to Yoga — and the largest bottom margin besides,
|
|
290
|
+
// whichever items those are, and stretches the items on the line to
|
|
291
|
+
// that: a row of no height of its own was as tall as both, and
|
|
292
|
+
// `align-content` had that much less room to share out. And it takes a
|
|
293
|
+
// row that does not wrap through its pass for lines, which sets the one
|
|
294
|
+
// line there as tall as its items and not the row: an item centred or at
|
|
295
|
+
// the end of a row of a height was centred or ended in the items'
|
|
296
|
+
// height. Told none is, it takes neither. A row whose lines wrap in
|
|
297
|
+
// reverse is left to Yoga.
|
|
298
|
+
const started = new Set<Box>();
|
|
299
|
+
if (baselines && box.style.flexWrap !== 'wrap-reverse') {
|
|
300
|
+
for (const { box: child, node } of items) {
|
|
301
|
+
if (!byBaseline(box.style, child.style)) continue;
|
|
302
|
+
node.setAlignSelf(Y.ALIGN_FLEX_START);
|
|
303
|
+
started.add(child);
|
|
304
|
+
}
|
|
305
|
+
if (box.style.alignItems === 'baseline') {
|
|
306
|
+
root.setAlignItems(Y.ALIGN_FLEX_START);
|
|
305
307
|
}
|
|
306
308
|
}
|
|
307
309
|
// frozen at its least size on the one line of a box that does not
|
|
@@ -449,8 +451,11 @@ export function layoutFlex(
|
|
|
449
451
|
}
|
|
450
452
|
}
|
|
451
453
|
|
|
452
|
-
//
|
|
453
|
-
|
|
454
|
+
// Yoga aligns the lines of a box that wraps in one pass of its own, which
|
|
455
|
+
// drops a margin (`lineMarginFix`), and keeps the margins of one that
|
|
456
|
+
// does not, which it takes through that pass only where it is told an
|
|
457
|
+
// item is aligned by its baseline: here, never
|
|
458
|
+
const lined = box.style.flexWrap !== 'nowrap';
|
|
454
459
|
let bottom = placeItems(
|
|
455
460
|
box,
|
|
456
461
|
ctx,
|
|
@@ -459,31 +464,20 @@ export function layoutFlex(
|
|
|
459
464
|
own,
|
|
460
465
|
lined,
|
|
461
466
|
contentWidth,
|
|
462
|
-
|
|
467
|
+
started,
|
|
463
468
|
crossed ? crossLines(box.style, items, contentWidth, columnGap) : undefined,
|
|
464
469
|
);
|
|
465
|
-
// Items aligned by their baselines
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
//
|
|
469
|
-
|
|
470
|
-
const lines = baselines ? baselineLines(box.style, items) : null;
|
|
471
|
-
const started = new Set<Box>();
|
|
470
|
+
// Items that share a line and are aligned by their baselines are laid
|
|
471
|
+
// out by the flex layout again, set at their lines' starts with the
|
|
472
|
+
// margins that put their baselines together (`baselineLines`), which
|
|
473
|
+
// makes each line as tall as that does
|
|
474
|
+
const lines = started.size > 1 ? baselineLines(box.style, items) : null;
|
|
472
475
|
if (lines) {
|
|
473
|
-
// every one of them, and the box's own `baseline` too, which is what
|
|
474
|
-
// takes Yoga down that pass
|
|
475
476
|
for (const line of lines) {
|
|
476
|
-
line.forEach(({
|
|
477
|
-
node.setAlignSelf(Y.ALIGN_FLEX_START);
|
|
477
|
+
line.forEach(({ node }, i) => {
|
|
478
478
|
node.setMargin(Y.EDGE_TOP, line.margins[i]);
|
|
479
|
-
started.add(child);
|
|
480
479
|
});
|
|
481
480
|
}
|
|
482
|
-
if (box.style.alignItems === 'baseline') {
|
|
483
|
-
root.setAlignItems(Y.ALIGN_FLEX_START);
|
|
484
|
-
}
|
|
485
|
-
// so it takes it only where the row wraps
|
|
486
|
-
lined = box.style.flexWrap !== 'nowrap';
|
|
487
481
|
ctx.flexDepth = depth + 1;
|
|
488
482
|
try {
|
|
489
483
|
calculate();
|
|
@@ -716,6 +710,12 @@ function placeItems(
|
|
|
716
710
|
let bottom = 0;
|
|
717
711
|
// the lines of a row that wraps in reverse are turned round to run up it
|
|
718
712
|
const reverse = row && box.style.flexWrap === 'wrap-reverse';
|
|
713
|
+
// the left edge a column's items aligned by baselines share, where it
|
|
714
|
+
// does not wrap (`baselineEdge`); a column that wraps is `crossed`
|
|
715
|
+
const edge =
|
|
716
|
+
row || box.style.flexWrap !== 'nowrap'
|
|
717
|
+
? NaN
|
|
718
|
+
: baselineEdge(box.style, items);
|
|
719
719
|
for (const { box: child, node, laid } of items) {
|
|
720
720
|
const placed = crossed?.get(child);
|
|
721
721
|
const across =
|
|
@@ -725,7 +725,11 @@ function placeItems(
|
|
|
725
725
|
const itemHeight = row ? heightOf(node, laid) : laid.main;
|
|
726
726
|
const left =
|
|
727
727
|
box.contentX +
|
|
728
|
-
(placed
|
|
728
|
+
(placed
|
|
729
|
+
? placed.x
|
|
730
|
+
: !Number.isNaN(edge) && byBaseline(box.style, child.style, false)
|
|
731
|
+
? edge
|
|
732
|
+
: node.getComputedLeft() + (row ? 0 : across));
|
|
729
733
|
// which Yoga turns an item round in by the height it laid it out at, and
|
|
730
734
|
// not the one it is (`unspace`)
|
|
731
735
|
const top =
|
|
@@ -2868,17 +2872,18 @@ function contentBottom(box: Box): number {
|
|
|
2868
2872
|
* its bottom edge, so it lined them up by their bottoms. With the items laid
|
|
2869
2873
|
* out, each line's, and for each the top margin that sets it at the line's
|
|
2870
2874
|
* start with its first baseline where the lowest-reaching one's is — or
|
|
2871
|
-
* null where no line has two to align.
|
|
2875
|
+
* null where no line has two to align. Not for a row whose lines wrap in
|
|
2876
|
+
* reverse, which grow from their ends and are left to Yoga (`layoutFlex`).
|
|
2872
2877
|
*/
|
|
2873
|
-
function baselineLines<T extends { box: Box }>(
|
|
2878
|
+
function baselineLines<T extends { box: Box; node: YogaNode }>(
|
|
2874
2879
|
style: ComputedStyle,
|
|
2875
2880
|
items: readonly T[],
|
|
2876
2881
|
): (T[] & { margins: number[] })[] | null {
|
|
2877
|
-
// a line that grows from its end is aligned from there, and left to Yoga
|
|
2878
|
-
if (style.flexWrap === 'wrap-reverse') return null;
|
|
2879
2882
|
const wraps = style.flexWrap !== 'nowrap';
|
|
2880
|
-
// along a line the items go the way the main axis does,
|
|
2881
|
-
// line starts
|
|
2883
|
+
// along a line the items go the way the main axis does, each margin box
|
|
2884
|
+
// after the last one's end, and the next line starts before it (9.3): at
|
|
2885
|
+
// the line's start, where an item with a margin before it was further
|
|
2886
|
+
// along than one alone on the line before
|
|
2882
2887
|
const back =
|
|
2883
2888
|
(style.flexDirection === 'row-reverse') !== (style.direction === 'rtl');
|
|
2884
2889
|
const out: (T[] & { margins: number[] })[] = [];
|
|
@@ -2900,12 +2905,21 @@ function baselineLines<T extends { box: Box }>(
|
|
|
2900
2905
|
out.push(Object.assign(line, { margins }));
|
|
2901
2906
|
};
|
|
2902
2907
|
for (const item of items) {
|
|
2903
|
-
const box = item
|
|
2904
|
-
|
|
2908
|
+
const { box, node } = item;
|
|
2909
|
+
const left = node.getComputedLeft() - node.getComputedMargin(Y.EDGE_LEFT);
|
|
2910
|
+
const right =
|
|
2911
|
+
node.getComputedLeft() +
|
|
2912
|
+
node.getComputedWidth() +
|
|
2913
|
+
node.getComputedMargin(Y.EDGE_RIGHT);
|
|
2914
|
+
if (
|
|
2915
|
+
wraps &&
|
|
2916
|
+
line.length &&
|
|
2917
|
+
(back ? right > last + 0.01 : left < last - 0.01)
|
|
2918
|
+
) {
|
|
2905
2919
|
close();
|
|
2906
2920
|
line = [];
|
|
2907
2921
|
}
|
|
2908
|
-
last =
|
|
2922
|
+
last = back ? left : right;
|
|
2909
2923
|
if (byBaseline(style, box.style)) line.push(item);
|
|
2910
2924
|
}
|
|
2911
2925
|
close();
|
|
@@ -2916,16 +2930,51 @@ function baselineLines<T extends { box: Box }>(
|
|
|
2916
2930
|
/** Whether an item takes part in aligning its line by baselines (CSS
|
|
2917
2931
|
* Flexbox 8.3, 9.4 step 8): aligned by its baseline, with no `auto`
|
|
2918
2932
|
* margin across the line, which takes it over its alignment. */
|
|
2919
|
-
function byBaseline(
|
|
2933
|
+
function byBaseline(
|
|
2934
|
+
container: ComputedStyle,
|
|
2935
|
+
item: ComputedStyle,
|
|
2936
|
+
/** Whether the line is a row's, across which its margins are its top
|
|
2937
|
+
* and bottom ones; a column's are its left and right ones. */
|
|
2938
|
+
row = true,
|
|
2939
|
+
): boolean {
|
|
2920
2940
|
const aligned =
|
|
2921
2941
|
item.alignSelf === AUTO ? container.alignItems : item.alignSelf;
|
|
2922
2942
|
return (
|
|
2923
2943
|
aligned === 'baseline' &&
|
|
2924
|
-
item.marginTop !== AUTO &&
|
|
2925
|
-
item.marginBottom !== AUTO
|
|
2944
|
+
(row ? item.marginTop : item.marginLeft) !== AUTO &&
|
|
2945
|
+
(row ? item.marginBottom : item.marginRight) !== AUTO
|
|
2926
2946
|
);
|
|
2927
2947
|
}
|
|
2928
2948
|
|
|
2949
|
+
/**
|
|
2950
|
+
* Where the items a column that does not wrap aligns by baselines have
|
|
2951
|
+
* their left edges, from its content box's: NaN where it has none.
|
|
2952
|
+
*
|
|
2953
|
+
* A horizontal item has no baseline across a column, and one is made from
|
|
2954
|
+
* its border box — the line-under edge of the vertical writing mode it is
|
|
2955
|
+
* taken to have, its left, whichever way its text runs (CSS Box Alignment
|
|
2956
|
+
* 3, 9.1). So the items share their left edges, and are set together at
|
|
2957
|
+
* the line's cross start, the column's left or, where it runs right to
|
|
2958
|
+
* left, its right (9.3; CSS Flexbox 8.3), as Chrome sets them: as far from
|
|
2959
|
+
* there as the most any of them reaches past its edge on that side. Yoga
|
|
2960
|
+
* never aligns a column by baselines, and sets each at its line's start
|
|
2961
|
+
* with its margin there, so that is the furthest any of them is from the
|
|
2962
|
+
* start: the rightmost left edge, or from the right the leftmost.
|
|
2963
|
+
*/
|
|
2964
|
+
function baselineEdge(
|
|
2965
|
+
container: ComputedStyle,
|
|
2966
|
+
items: readonly { box: Box; node: YogaNode }[],
|
|
2967
|
+
): number {
|
|
2968
|
+
const rtl = container.direction === 'rtl';
|
|
2969
|
+
let edge = NaN;
|
|
2970
|
+
for (const { box, node } of items) {
|
|
2971
|
+
if (!byBaseline(container, box.style, false)) continue;
|
|
2972
|
+
const left = node.getComputedLeft();
|
|
2973
|
+
if (!(rtl ? left >= edge : left <= edge)) edge = left;
|
|
2974
|
+
}
|
|
2975
|
+
return edge;
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2929
2978
|
/**
|
|
2930
2979
|
* Whether Yoga made a line of a row that wraps the row's height, where its
|
|
2931
2980
|
* items make it another (CSS Flexbox 9.4, steps 7 and 8; `layoutFlex`).
|
|
@@ -3035,13 +3084,13 @@ function autoAcross(
|
|
|
3035
3084
|
|
|
3036
3085
|
/**
|
|
3037
3086
|
* How far an item is from where it belongs across its line, where Yoga
|
|
3038
|
-
* (3.2.1) aligned the lines in the pass it takes for a box that wraps
|
|
3039
|
-
*
|
|
3040
|
-
*
|
|
3041
|
-
*
|
|
3042
|
-
*
|
|
3043
|
-
*
|
|
3044
|
-
*
|
|
3087
|
+
* (3.2.1) aligned the lines in the pass it takes for a box that wraps: it
|
|
3088
|
+
* sets an item aligned to its line's start there as though it had no
|
|
3089
|
+
* margin at that side, and one centred as though it had none at either —
|
|
3090
|
+
* `items-start` in a wrapping row put every card's top margin under it. By
|
|
3091
|
+
* the margin at the start of the line's cross axis, and by half what it
|
|
3092
|
+
* exceeds the one at the end by, as Yoga holds them: for an item set at
|
|
3093
|
+
* its line's start (`started`), its baseline's.
|
|
3045
3094
|
*
|
|
3046
3095
|
* A row whose lines wrap in reverse Yoga lays out as though they wrapped
|
|
3047
3096
|
* forward, and then turns each item over across the box, its margins with
|
|
@@ -3050,9 +3099,7 @@ function autoAcross(
|
|
|
3050
3099
|
* stretched, is its bottom margin down from its line's top where it is its
|
|
3051
3100
|
* top one; and one centred, half the difference. A column's, whose cross
|
|
3052
3101
|
* axis runs right to left, or that wraps in reverse, is left as Yoga has
|
|
3053
|
-
* it, and so is an item an `auto` margin aligns
|
|
3054
|
-
* wrap: where Yoga still takes that pass for such a row, its items are
|
|
3055
|
-
* laid out again without it, aligned by their baselines (`layoutFlex`).
|
|
3102
|
+
* it, and so is an item an `auto` margin aligns across a column.
|
|
3056
3103
|
*/
|
|
3057
3104
|
function lineMarginFix(
|
|
3058
3105
|
container: ComputedStyle,
|
|
@@ -3073,10 +3120,7 @@ function lineMarginFix(
|
|
|
3073
3120
|
let align: string | null;
|
|
3074
3121
|
if (started) align = 'flex-start';
|
|
3075
3122
|
else if (lead === AUTO || trail === AUTO) {
|
|
3076
|
-
align =
|
|
3077
|
-
row && container.flexWrap !== 'nowrap'
|
|
3078
|
-
? autoAcross(style, reverse)
|
|
3079
|
-
: null;
|
|
3123
|
+
align = row ? autoAcross(style, reverse) : null;
|
|
3080
3124
|
} else {
|
|
3081
3125
|
align = style.alignSelf === AUTO ? container.alignItems : style.alignSelf;
|
|
3082
3126
|
// which sets one whose height is a percentage of none at its line's
|
|
@@ -3446,9 +3490,10 @@ interface Across {
|
|
|
3446
3490
|
* item with an `auto` margin across its line takes the room the line has
|
|
3447
3491
|
* past it on that side (8.1), and none where there is none; a stretched
|
|
3448
3492
|
* item is as wide as its line less its margins, within its limits (9.4,
|
|
3449
|
-
* step 11), and as tall as the column made it;
|
|
3450
|
-
*
|
|
3451
|
-
*
|
|
3493
|
+
* step 11), and as tall as the column made it; those aligned by baselines
|
|
3494
|
+
* share their left edges, set together at the line's start, where a column
|
|
3495
|
+
* that does not wrap sets them (`baselineEdge`); and the rest are where
|
|
3496
|
+
* `align-self` says.
|
|
3452
3497
|
*/
|
|
3453
3498
|
function crossLines(
|
|
3454
3499
|
style: ComputedStyle,
|
|
@@ -3482,9 +3527,25 @@ function crossLines(
|
|
|
3482
3527
|
lead.push(fromRight ? r : l);
|
|
3483
3528
|
trail.push(fromRight ? l : r);
|
|
3484
3529
|
});
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3530
|
+
// A line is as wide as its widest item, margins and all, and as the
|
|
3531
|
+
// items it aligns by baselines come to, sharing their left edges: the
|
|
3532
|
+
// most any reaches from the line's start to that edge, and the most any
|
|
3533
|
+
// reaches past it (`baselineEdge`; 9.4 step 8). `reach` is each line's
|
|
3534
|
+
// first, where those items' left edges are from its start.
|
|
3535
|
+
const reach: number[] = [];
|
|
3536
|
+
const across = lines.map((line) => {
|
|
3537
|
+
let most = 0;
|
|
3538
|
+
let before = -Infinity;
|
|
3539
|
+
let after = -Infinity;
|
|
3540
|
+
for (const i of line) {
|
|
3541
|
+
most = Math.max(most, sizes[i] + lead[i] + trail[i]);
|
|
3542
|
+
if (!byBaseline(style, items[i].box.style, false)) continue;
|
|
3543
|
+
before = Math.max(before, lead[i] + (fromRight ? sizes[i] : 0));
|
|
3544
|
+
after = Math.max(after, trail[i] + (fromRight ? 0 : sizes[i]));
|
|
3545
|
+
}
|
|
3546
|
+
reach.push(before);
|
|
3547
|
+
return Math.max(most, before + after);
|
|
3548
|
+
});
|
|
3488
3549
|
const count = lines.length;
|
|
3489
3550
|
let room = width - gap * (count - 1);
|
|
3490
3551
|
for (const size of across) room -= size;
|
|
@@ -3542,6 +3603,8 @@ function crossLines(
|
|
|
3542
3603
|
if (itemWidth > most) itemWidth = most;
|
|
3543
3604
|
if (itemWidth < least) itemWidth = least;
|
|
3544
3605
|
itemWidth = Math.max(itemWidth, box.horizontalExtra);
|
|
3606
|
+
} else if (byBaseline(style, own, false)) {
|
|
3607
|
+
offset = reach[k] - (fromRight ? itemWidth : 0);
|
|
3545
3608
|
} else {
|
|
3546
3609
|
const align = own.alignSelf === AUTO ? style.alignItems : own.alignSelf;
|
|
3547
3610
|
if (align === 'center') offset += free / 2;
|