@react-x11/components 0.13.0 → 0.14.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/controls.d.ts +29 -2
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js +70 -2
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +26 -4
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +18 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +27 -0
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/parse.d.ts +18 -4
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +66 -28
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +128 -15
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +561 -144
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/transform.d.ts +85 -0
- package/dist/html/css/transform.d.ts.map +1 -0
- package/dist/html/css/transform.js +425 -0
- package/dist/html/css/transform.js.map +1 -0
- package/dist/html/css/ua.d.ts.map +1 -1
- package/dist/html/css/ua.js +14 -2
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +4 -0
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/fonts.d.ts +55 -5
- package/dist/html/fonts.d.ts.map +1 -1
- package/dist/html/fonts.js +284 -23
- package/dist/html/fonts.js.map +1 -1
- package/dist/html/layout/axes.d.ts +14 -0
- package/dist/html/layout/axes.d.ts.map +1 -0
- package/dist/html/layout/axes.js +28 -0
- package/dist/html/layout/axes.js.map +1 -0
- package/dist/html/layout/block.d.ts +93 -12
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +389 -39
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +59 -1
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +22 -2
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/cache.d.ts +1 -1
- package/dist/html/layout/cache.d.ts.map +1 -1
- package/dist/html/layout/cache.js +6 -0
- package/dist/html/layout/cache.js.map +1 -1
- package/dist/html/layout/css-grid.d.ts.map +1 -1
- package/dist/html/layout/css-grid.js +90 -18
- package/dist/html/layout/css-grid.js.map +1 -1
- package/dist/html/layout/flex.d.ts.map +1 -1
- package/dist/html/layout/flex.js +570 -83
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts +3 -0
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +144 -54
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/multicol.d.ts +42 -0
- package/dist/html/layout/multicol.d.ts.map +1 -0
- package/dist/html/layout/multicol.js +897 -0
- package/dist/html/layout/multicol.js.map +1 -0
- package/dist/html/layout/shaping.d.ts +7 -0
- package/dist/html/layout/shaping.d.ts.map +1 -1
- package/dist/html/layout/shaping.js +25 -20
- package/dist/html/layout/shaping.js.map +1 -1
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +62 -18
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +3 -0
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +263 -36
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +79 -4
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +1213 -141
- package/dist/html/paint.js.map +1 -1
- package/dist/html/svg.d.ts +15 -2
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +249 -17
- package/dist/html/svg.js.map +1 -1
- package/dist/html/widgets.js +87 -7
- package/dist/html/widgets.js.map +1 -1
- package/dist/html/woff2.d.ts +14 -0
- package/dist/html/woff2.d.ts.map +1 -0
- package/dist/html/woff2.js +608 -0
- package/dist/html/woff2.js.map +1 -0
- package/dist/maps/controller.d.ts +17 -1
- package/dist/maps/controller.d.ts.map +1 -1
- package/dist/maps/controller.js +27 -7
- package/dist/maps/controller.js.map +1 -1
- package/dist/richtext/node.d.ts +8 -1
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +5 -1
- package/dist/richtext/node.js.map +1 -1
- package/dist/richtext/runs.d.ts +25 -6
- package/dist/richtext/runs.d.ts.map +1 -1
- package/dist/richtext/runs.js +240 -24
- package/dist/richtext/runs.js.map +1 -1
- package/package.json +1 -1
- package/src/html/controls.ts +85 -2
- package/src/html/css/cascade.ts +23 -0
- package/src/html/css/color.ts +29 -0
- package/src/html/css/parse.ts +71 -31
- package/src/html/css/style.ts +664 -147
- package/src/html/css/transform.ts +459 -0
- package/src/html/css/ua.ts +15 -2
- package/src/html/css/values.ts +4 -0
- package/src/html/fonts.ts +312 -29
- package/src/html/layout/axes.ts +62 -0
- package/src/html/layout/block.ts +444 -37
- package/src/html/layout/boxes.ts +65 -3
- package/src/html/layout/cache.ts +6 -0
- package/src/html/layout/css-grid.ts +95 -22
- package/src/html/layout/flex.ts +671 -77
- package/src/html/layout/inline.ts +149 -55
- package/src/html/layout/multicol.ts +977 -0
- package/src/html/layout/shaping.ts +27 -18
- package/src/html/layout/table.ts +62 -19
- package/src/html/node.ts +300 -37
- package/src/html/paint.ts +1406 -206
- package/src/html/svg.ts +277 -19
- package/src/html/widgets.ts +95 -9
- package/src/html/woff2.ts +612 -0
- package/src/maps/controller.ts +28 -7
- package/src/richtext/node.ts +13 -2
- package/src/richtext/runs.ts +313 -35
package/src/html/layout/flex.ts
CHANGED
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
// independent Yoga pass inside the measure call. Not a nested Yoga node:
|
|
21
21
|
// that would need the whole child subtree mirrored into Yoga's tree, and the
|
|
22
22
|
// measure seam already answers the only question the outer pass asks. What
|
|
23
|
-
// the leaf shape costs is
|
|
24
|
-
//
|
|
23
|
+
// the leaf shape costs is a layout: an item the flex pass made taller or
|
|
24
|
+
// shorter than its content, which lays what is in it out by its height — a
|
|
25
|
+
// flex or grid container, or a box holding a percentage of a height — is
|
|
26
|
+
// laid out again at that height (`layoutItemAt`).
|
|
25
27
|
import { Yoga, layoutLoaded } from 'react-x11/yoga';
|
|
26
28
|
import type { Config as YogaConfig, Node as YogaNode } from 'react-x11/yoga';
|
|
27
29
|
|
|
@@ -32,6 +34,8 @@ import { Box, PAINT_ORDER, isBlank } from './boxes.js';
|
|
|
32
34
|
import {
|
|
33
35
|
FLEXED_HEIGHT,
|
|
34
36
|
MIN_CONTENT_PROBE,
|
|
37
|
+
USED_HEIGHT,
|
|
38
|
+
centreButton,
|
|
35
39
|
clampHeight,
|
|
36
40
|
clampWidth,
|
|
37
41
|
contentSizedWidth,
|
|
@@ -122,9 +126,14 @@ export function layoutFlex(
|
|
|
122
126
|
// percentage that resolves, or what `aspect-ratio` makes of the width —
|
|
123
127
|
// less the padding and borders a `border-box` height holds, which put
|
|
124
128
|
// `h-16 py-2 items-center` eight pixels low. Where it is not, the limits
|
|
125
|
-
// on it, so a column `min-h-screen` gives its `flex-1` the rest
|
|
129
|
+
// on it, so a column `min-h-screen` gives its `flex-1` the rest — or the
|
|
130
|
+
// height the column it is an item of flexed it to (`USED_HEIGHT`), which
|
|
131
|
+
// its items are laid out in and take no percentage of.
|
|
126
132
|
const definite = percentBaseInside(box);
|
|
127
|
-
const
|
|
133
|
+
const own = Number.isFinite(definite);
|
|
134
|
+
const height = USED_HEIGHT.get(box) ?? (own ? definite : null);
|
|
135
|
+
// whether its content may come to more than it is tall
|
|
136
|
+
let capped = height !== null;
|
|
128
137
|
if (height !== null) root.setHeight(height);
|
|
129
138
|
else {
|
|
130
139
|
root.setHeightAuto();
|
|
@@ -132,7 +141,10 @@ export function layoutFlex(
|
|
|
132
141
|
const min = clampHeight(box, extra) - extra;
|
|
133
142
|
if (min > 0) root.setMinHeight(min);
|
|
134
143
|
const max = clampHeight(box, Infinity) - extra;
|
|
135
|
-
if (Number.isFinite(max))
|
|
144
|
+
if (Number.isFinite(max)) {
|
|
145
|
+
root.setMaxHeight(Math.max(0, max));
|
|
146
|
+
capped = true;
|
|
147
|
+
}
|
|
136
148
|
}
|
|
137
149
|
|
|
138
150
|
const flowing: Box[] = [];
|
|
@@ -161,8 +173,18 @@ export function layoutFlex(
|
|
|
161
173
|
for (const child of flowing) {
|
|
162
174
|
const node = Y.Node.create(flexConfig());
|
|
163
175
|
resolveEdges(child, contentWidth);
|
|
164
|
-
const laid: Laid = {
|
|
165
|
-
|
|
176
|
+
const laid: Laid = {
|
|
177
|
+
width: NaN,
|
|
178
|
+
height: NaN,
|
|
179
|
+
tall: NaN,
|
|
180
|
+
set: NaN,
|
|
181
|
+
stretch: NaN,
|
|
182
|
+
main: NaN,
|
|
183
|
+
least: NaN,
|
|
184
|
+
held: NaN,
|
|
185
|
+
auto: false,
|
|
186
|
+
};
|
|
187
|
+
applyItem(node, child, ctx, contentWidth, laid, height !== null, own);
|
|
166
188
|
root.insertChild(node, items.length);
|
|
167
189
|
items.push({ box: child, node, laid });
|
|
168
190
|
}
|
|
@@ -170,12 +192,38 @@ export function layoutFlex(
|
|
|
170
192
|
const direction =
|
|
171
193
|
box.style.direction === 'rtl' ? Y.DIRECTION_RTL : Y.DIRECTION_LTR;
|
|
172
194
|
const row = box.style.flexDirection.startsWith('row');
|
|
173
|
-
|
|
195
|
+
// frozen at its least size on the one line of a box that does not
|
|
196
|
+
// wrap, by a basis where Yoga takes one along the main axis
|
|
197
|
+
// (`applyItem`'s `setBasis`)
|
|
198
|
+
const hold: Hold = {
|
|
199
|
+
frozen: box.style.flexWrap === 'nowrap',
|
|
200
|
+
basis: row ? bounded : height !== null,
|
|
201
|
+
};
|
|
202
|
+
const ask = (): void =>
|
|
174
203
|
root.calculateLayout(
|
|
175
204
|
bounded ? contentWidth : Number.NaN,
|
|
176
205
|
height ?? Number.NaN,
|
|
177
206
|
direction,
|
|
178
207
|
);
|
|
208
|
+
// the one line of a box that does not wrap may be short of room, where
|
|
209
|
+
// the box has a size along it that its items do not make: and Yoga's
|
|
210
|
+
// answer for a line every item of which its minimum stops is put right
|
|
211
|
+
// (`stoppedLine`)
|
|
212
|
+
const short = hold.frozen && (row ? bounded : capped);
|
|
213
|
+
// each item's size along the main axis, read the once a layout (`Laid`)
|
|
214
|
+
const read = (): void => {
|
|
215
|
+
for (const { node, laid } of items) {
|
|
216
|
+
laid.main = row ? node.getComputedWidth() : node.getComputedHeight();
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
const calculate = (): void => {
|
|
220
|
+
ask();
|
|
221
|
+
read();
|
|
222
|
+
if (!short) return;
|
|
223
|
+
const room = row ? contentWidth : (height ?? root.getComputedHeight());
|
|
224
|
+
const gap = row ? columnGap : rowGap;
|
|
225
|
+
if (stoppedLine(items, row, room, gap, hold, ask)) read();
|
|
226
|
+
};
|
|
179
227
|
ctx.flexDepth = depth + 1;
|
|
180
228
|
try {
|
|
181
229
|
calculate();
|
|
@@ -184,7 +232,7 @@ export function layoutFlex(
|
|
|
184
232
|
// another under its own, a few times over at most
|
|
185
233
|
for (
|
|
186
234
|
let pass = 0;
|
|
187
|
-
pass < 4 && autoMinimums(items, row, ctx, contentWidth);
|
|
235
|
+
pass < 4 && autoMinimums(items, row, ctx, contentWidth, hold);
|
|
188
236
|
pass += 1
|
|
189
237
|
) {
|
|
190
238
|
calculate();
|
|
@@ -203,8 +251,9 @@ export function layoutFlex(
|
|
|
203
251
|
ctx,
|
|
204
252
|
items,
|
|
205
253
|
row,
|
|
206
|
-
|
|
254
|
+
own,
|
|
207
255
|
box.style.flexWrap === 'wrap' || baselines,
|
|
256
|
+
contentWidth,
|
|
208
257
|
);
|
|
209
258
|
// Items aligned by their baselines, which Yoga cannot see, are aligned
|
|
210
259
|
// with their baselines known (`baselineLines`)
|
|
@@ -237,8 +286,9 @@ export function layoutFlex(
|
|
|
237
286
|
ctx,
|
|
238
287
|
items,
|
|
239
288
|
row,
|
|
240
|
-
|
|
289
|
+
own,
|
|
241
290
|
box.style.flexWrap === 'wrap',
|
|
291
|
+
contentWidth,
|
|
242
292
|
started,
|
|
243
293
|
);
|
|
244
294
|
}
|
|
@@ -264,8 +314,9 @@ export function layoutFlex(
|
|
|
264
314
|
ctx,
|
|
265
315
|
items,
|
|
266
316
|
row,
|
|
267
|
-
|
|
317
|
+
own,
|
|
268
318
|
box.style.flexWrap === 'wrap' || baselines,
|
|
319
|
+
contentWidth,
|
|
269
320
|
);
|
|
270
321
|
}
|
|
271
322
|
|
|
@@ -278,11 +329,11 @@ export function layoutFlex(
|
|
|
278
329
|
* base size the ratio makes of the stretched size; whether any changed. */
|
|
279
330
|
function keepRatios(
|
|
280
331
|
container: Box,
|
|
281
|
-
items: readonly { box: Box; node: YogaNode }[],
|
|
332
|
+
items: readonly { box: Box; node: YogaNode; laid: Laid }[],
|
|
282
333
|
row: boolean,
|
|
283
334
|
): boolean {
|
|
284
335
|
let changed = false;
|
|
285
|
-
for (const { box, node } of items) {
|
|
336
|
+
for (const { box, node, laid } of items) {
|
|
286
337
|
if (box.kind !== 'replaced' || !stretches(box, container.style, row)) {
|
|
287
338
|
continue;
|
|
288
339
|
}
|
|
@@ -295,9 +346,16 @@ function keepRatios(
|
|
|
295
346
|
box.horizontalExtra
|
|
296
347
|
: Math.max(0, node.getComputedWidth() - box.horizontalExtra) / ratio +
|
|
297
348
|
box.verticalExtra;
|
|
298
|
-
|
|
299
|
-
if (Math.abs(basis - main) < 0.5) continue;
|
|
349
|
+
if (Math.abs(basis - laid.main) < 0.5) continue;
|
|
300
350
|
node.setFlexBasis(basis);
|
|
351
|
+
// one held at its least size (`holdAt`) flexes from the new basis, no
|
|
352
|
+
// further down than that
|
|
353
|
+
if (!Number.isNaN(laid.held)) {
|
|
354
|
+
laid.held = NaN;
|
|
355
|
+
laid.auto = false;
|
|
356
|
+
node.setFlexGrow(style.flexGrow);
|
|
357
|
+
node.setFlexShrink(style.flexShrink);
|
|
358
|
+
}
|
|
301
359
|
changed = true;
|
|
302
360
|
}
|
|
303
361
|
return changed;
|
|
@@ -431,9 +489,12 @@ function placeItems(
|
|
|
431
489
|
ctx: LayoutContext,
|
|
432
490
|
items: readonly { box: Box; node: YogaNode; laid: Laid }[],
|
|
433
491
|
row: boolean,
|
|
434
|
-
|
|
492
|
+
/** Whether the flex box has a definite height. */
|
|
493
|
+
own: boolean,
|
|
435
494
|
/** Whether Yoga aligned the lines in its pass that drops a margin. */
|
|
436
495
|
lined: boolean,
|
|
496
|
+
/** The flex box's content width: its items' containing block's. */
|
|
497
|
+
contentWidth: number,
|
|
437
498
|
started?: ReadonlySet<Box>,
|
|
438
499
|
): number {
|
|
439
500
|
let bottom = 0;
|
|
@@ -444,12 +505,13 @@ function placeItems(
|
|
|
444
505
|
const left = box.contentX + node.getComputedLeft() + (row ? 0 : across);
|
|
445
506
|
const top = box.contentY + node.getComputedTop() + (row ? across : 0);
|
|
446
507
|
const width = meant(
|
|
447
|
-
node.getComputedWidth(),
|
|
508
|
+
row ? laid.main : node.getComputedWidth(),
|
|
448
509
|
laid.set,
|
|
449
510
|
laid.stretch,
|
|
450
511
|
laid.width,
|
|
512
|
+
row ? laid.held : NaN,
|
|
451
513
|
);
|
|
452
|
-
const itemHeight = node.getComputedHeight();
|
|
514
|
+
const itemHeight = row ? node.getComputedHeight() : laid.main;
|
|
453
515
|
// The item is laid out again at the width the flex pass settled on: the
|
|
454
516
|
// measure function answered a question, and the answer is not a layout —
|
|
455
517
|
// its line breaks were computed against a width that may have changed
|
|
@@ -459,12 +521,15 @@ function placeItems(
|
|
|
459
521
|
// 9.8): stretched across a row's line, or flexed in a column of a
|
|
460
522
|
// height of its own. What is in it takes its percentages of that —
|
|
461
523
|
// the `h-full` in a stretched sidebar — where they had nothing to be
|
|
462
|
-
// of, and the item is laid out again for them
|
|
524
|
+
// of, and the item is laid out again for them; and so is one that is a
|
|
525
|
+
// flex or grid container itself, whose own items are laid out in that
|
|
526
|
+
// height (9.4, step 11: "redo layout for its contents, treating this
|
|
527
|
+
// used size as its definite cross size").
|
|
463
528
|
const definite = row
|
|
464
529
|
? stretches(child, box.style)
|
|
465
530
|
? itemHeight
|
|
466
531
|
: null
|
|
467
|
-
:
|
|
532
|
+
: own
|
|
468
533
|
? itemHeight
|
|
469
534
|
: null;
|
|
470
535
|
layoutItemAt(
|
|
@@ -476,6 +541,7 @@ function placeItems(
|
|
|
476
541
|
itemHeight,
|
|
477
542
|
laid,
|
|
478
543
|
definite,
|
|
544
|
+
contentWidth,
|
|
479
545
|
!row,
|
|
480
546
|
);
|
|
481
547
|
// to the end of its margin box, as Yoga measured the box: a negative
|
|
@@ -496,6 +562,8 @@ function layoutItemAt(
|
|
|
496
562
|
laid: Laid,
|
|
497
563
|
/** Its border box's height where the flex layout made it definite. */
|
|
498
564
|
definite: number | null,
|
|
565
|
+
/** The flex box's content width, the item's containing block's. */
|
|
566
|
+
containing: number,
|
|
499
567
|
/** Whether its height is the one the flex layout gave it, in a column,
|
|
500
568
|
* whatever its own says: flexed, it is shrunk as well as grown. */
|
|
501
569
|
column = false,
|
|
@@ -509,29 +577,63 @@ function layoutItemAt(
|
|
|
509
577
|
box.height = height;
|
|
510
578
|
return;
|
|
511
579
|
}
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
580
|
+
const sameWidth = Math.abs(laid.width - width) <= 0.01;
|
|
581
|
+
// The content height the item is laid out at, where the flex layout gave
|
|
582
|
+
// it one that is not its own already: NaN where its content says how
|
|
583
|
+
// tall it is.
|
|
584
|
+
let tall = NaN;
|
|
585
|
+
// A flex or grid container shares its height out among its items: an
|
|
586
|
+
// `auto` margin takes what is free of it (8.1), a `flex: 1` grows into it
|
|
587
|
+
// (9.7), its lines are as tall as it (9.4), its `fr` rows fill it. So it
|
|
588
|
+
// is laid out in the height the flex layout gave it, definite or not —
|
|
589
|
+
// the one a column with no height of its own flexed it to is its used
|
|
590
|
+
// size all the same (9.7), and `min-h-screen flex flex-col` around a
|
|
591
|
+
// `flex-1 flex items-center` centres what is in that. At its content's
|
|
592
|
+
// own height there is nothing to share, and the layout it has is that
|
|
593
|
+
// one: the tallest card of a row is laid out the once.
|
|
594
|
+
const given = definite ?? (column ? height : null);
|
|
595
|
+
const shares =
|
|
596
|
+
box.kind === 'flex' &&
|
|
597
|
+
given !== null &&
|
|
598
|
+
!(
|
|
599
|
+
Math.abs(naturalHeight(box, ctx, width, containing, laid) - given) <= 0.01
|
|
600
|
+
);
|
|
601
|
+
if (given !== null) {
|
|
602
|
+
const inner = Math.max(0, given - box.verticalExtra);
|
|
603
|
+
if (Object.is(inner, percentBaseInside(box))) {
|
|
604
|
+
// the height it has of its own
|
|
605
|
+
} else if (shares || (definite !== null && percentHeightsIn(box))) {
|
|
606
|
+
tall = inner;
|
|
522
607
|
}
|
|
523
608
|
}
|
|
524
609
|
// `measureBox` lays the box out at (0, 0); re-running it at the final width
|
|
525
610
|
// and then moving it is one pass, not two, because the second call is the
|
|
526
|
-
// one whose result is kept — or no pass, where the last
|
|
527
|
-
// this width. An item Yoga never measured has none, NaN,
|
|
528
|
-
|
|
529
|
-
|
|
611
|
+
// one whose result is kept — or no pass, where the last layout was at
|
|
612
|
+
// this width and this height. An item Yoga never measured has none, NaN,
|
|
613
|
+
// and is laid out.
|
|
614
|
+
if (sameWidth && Object.is(laid.tall, tall)) box.height = laid.height;
|
|
615
|
+
else if (Number.isNaN(tall)) {
|
|
616
|
+
layoutNaturally(box, ctx, width, containing, laid);
|
|
617
|
+
} else {
|
|
618
|
+
// what is in it takes its percentages of a definite height alone (9.8)
|
|
619
|
+
const heights = definite !== null ? FLEXED_HEIGHT : USED_HEIGHT;
|
|
620
|
+
heights.set(box, tall);
|
|
621
|
+
try {
|
|
622
|
+
ctx.layoutSubtree(box, width, containing);
|
|
623
|
+
} finally {
|
|
624
|
+
heights.delete(box);
|
|
625
|
+
}
|
|
626
|
+
laid.width = box.width;
|
|
627
|
+
laid.height = box.height;
|
|
628
|
+
laid.tall = tall;
|
|
629
|
+
}
|
|
530
630
|
// A stretched item is taller than its content, and the box has to say so
|
|
531
631
|
// or its background stops short of the row — and it is no taller than its
|
|
532
632
|
// line where its content is, or than what its ratio makes of its width:
|
|
533
633
|
// its height is the line's (CSS Flexbox 9.4, step 11).
|
|
534
634
|
if (height > box.height || column || definite !== null) box.height = height;
|
|
635
|
+
// and a button's content is centred in the height it came to
|
|
636
|
+
centreButton(box);
|
|
535
637
|
moveTo(box, x, y);
|
|
536
638
|
}
|
|
537
639
|
|
|
@@ -561,8 +663,11 @@ function applyItem(
|
|
|
561
663
|
ctx: LayoutContext,
|
|
562
664
|
containingWidth: number,
|
|
563
665
|
laid: Laid,
|
|
564
|
-
/** Whether the flex box has a height
|
|
666
|
+
/** Whether the flex box has a height its items are laid out in. */
|
|
565
667
|
tall: boolean,
|
|
668
|
+
/** Whether that height is definite, and a percentage may be of it: one a
|
|
669
|
+
* column flexed the box to is not (`USED_HEIGHT`). */
|
|
670
|
+
own: boolean,
|
|
566
671
|
): void {
|
|
567
672
|
const style = box.style;
|
|
568
673
|
laid.stretch = containingWidth - box.marginLeft - box.marginRight;
|
|
@@ -590,9 +695,20 @@ function applyItem(
|
|
|
590
695
|
if (style.width !== AUTO) {
|
|
591
696
|
laid.set = setLength(node, true, style.width, containingWidth, across);
|
|
592
697
|
}
|
|
593
|
-
|
|
698
|
+
// a percentage of a height that is not definite is `auto` (CSS 2.1 10.5),
|
|
699
|
+
// which Yoga makes of one where the flex box has no height at all
|
|
700
|
+
if (style.height !== AUTO && (own || !tall || !isPct(style.height))) {
|
|
701
|
+
setLength(node, false, style.height, NaN, down);
|
|
702
|
+
}
|
|
703
|
+
// the least it may be along each axis, its border box's, as Yoga is
|
|
704
|
+
// told it
|
|
705
|
+
let leastWidth = NaN;
|
|
706
|
+
let leastHeight = NaN;
|
|
594
707
|
const minWidth = resolveOrNull(style.minWidth, containingWidth);
|
|
595
|
-
if (minWidth !== null)
|
|
708
|
+
if (minWidth !== null) {
|
|
709
|
+
leastWidth = minWidth + across;
|
|
710
|
+
node.setMinWidth(leastWidth);
|
|
711
|
+
}
|
|
596
712
|
if (style.maxWidth !== 'none') {
|
|
597
713
|
const maxWidth = resolveOrNull(style.maxWidth, containingWidth);
|
|
598
714
|
if (maxWidth !== null) node.setMaxWidth(maxWidth + across);
|
|
@@ -611,17 +727,24 @@ function applyItem(
|
|
|
611
727
|
laid.set = size(style.widthKeyword);
|
|
612
728
|
node.setWidth(laid.set);
|
|
613
729
|
}
|
|
614
|
-
if (style.minWidthKeyword)
|
|
730
|
+
if (style.minWidthKeyword) {
|
|
731
|
+
leastWidth = size(style.minWidthKeyword);
|
|
732
|
+
node.setMinWidth(leastWidth);
|
|
733
|
+
}
|
|
615
734
|
if (style.maxWidthKeyword) node.setMaxWidth(size(style.maxWidthKeyword));
|
|
616
735
|
}
|
|
617
736
|
const minHeight = resolveOrNull(style.minHeight, NaN);
|
|
618
|
-
if (minHeight !== null)
|
|
737
|
+
if (minHeight !== null) {
|
|
738
|
+
leastHeight = minHeight + down;
|
|
739
|
+
node.setMinHeight(leastHeight);
|
|
740
|
+
}
|
|
619
741
|
if (style.maxHeight !== 'none') {
|
|
620
742
|
const maxHeight = resolveOrNull(style.maxHeight, NaN);
|
|
621
743
|
if (maxHeight !== null) node.setMaxHeight(maxHeight + down);
|
|
622
744
|
}
|
|
623
745
|
|
|
624
746
|
const row = box.parent?.style.flexDirection.startsWith('row') ?? true;
|
|
747
|
+
laid.least = row ? leastWidth : leastHeight;
|
|
625
748
|
// Yoga takes a length for a basis only where the flex box's main size is
|
|
626
749
|
// definite, and else the item's own size along it: down a column of no
|
|
627
750
|
// height of its own it read `flex: 0 0 3rem` as the item's height, or
|
|
@@ -653,6 +776,9 @@ function applyItem(
|
|
|
653
776
|
setBasis(columnBasis);
|
|
654
777
|
} else if (style.flexBasis === 'content' || style.flexBasis === AUTO) {
|
|
655
778
|
// Yoga's own
|
|
779
|
+
} else if (isPct(style.flexBasis) && !row && tall && !own) {
|
|
780
|
+
// a percentage of a main size that is not definite is `content` (CSS
|
|
781
|
+
// Flexbox 7.2.3): Yoga's own, down a column
|
|
656
782
|
} else if (isPct(style.flexBasis)) {
|
|
657
783
|
// a percentage of the main size, which is known across a row
|
|
658
784
|
const basis = style.flexBasis;
|
|
@@ -738,7 +864,13 @@ function applyItem(
|
|
|
738
864
|
const width = meant(w, laid.set - extra, laid.stretch - extra);
|
|
739
865
|
const given = hm === Y.MEASURE_MODE_EXACTLY && Number.isFinite(h);
|
|
740
866
|
const exact = wm === Y.MEASURE_MODE_EXACTLY && Number.isFinite(width);
|
|
741
|
-
|
|
867
|
+
// the height it was flexed to, that is: one its automatic minimum
|
|
868
|
+
// holds it at (`holdAt`) is its content's, which a replaced element's
|
|
869
|
+
// width follows through its ratio and no other's does (CSS Sizing 4,
|
|
870
|
+
// 5.1)
|
|
871
|
+
const flexed = box.kind === 'replaced' || !laid.auto;
|
|
872
|
+
const through =
|
|
873
|
+
widthThrough && given && !exact && flexed ? widthThrough(h) : null;
|
|
742
874
|
// along a row, a replaced element is its own width wherever there is
|
|
743
875
|
// room for less — its flex base size is not fitted to the room (9.2)
|
|
744
876
|
// — and across a column it is fitted, as any item is
|
|
@@ -758,10 +890,8 @@ function applyItem(
|
|
|
758
890
|
}
|
|
759
891
|
let answer = answers.get(inner);
|
|
760
892
|
if (answer === undefined) {
|
|
761
|
-
answer = measureBox(box, ctx, inner);
|
|
893
|
+
answer = measureBox(box, ctx, inner, containingWidth, laid);
|
|
762
894
|
answers.set(inner, answer);
|
|
763
|
-
laid.width = box.width;
|
|
764
|
-
laid.height = box.height;
|
|
765
895
|
}
|
|
766
896
|
return answer;
|
|
767
897
|
});
|
|
@@ -777,12 +907,28 @@ const MAX_CONTENT = new WeakMap<Box, number>();
|
|
|
777
907
|
interface Laid {
|
|
778
908
|
width: number;
|
|
779
909
|
height: number;
|
|
910
|
+
/** The content height that layout was made at, where the flex layout gave
|
|
911
|
+
* the item one (`FLEXED_HEIGHT`, `USED_HEIGHT`), and NaN where its
|
|
912
|
+
* content's own. */
|
|
913
|
+
tall: number;
|
|
780
914
|
/** The border-box width this engine set on the item's node — a length,
|
|
781
915
|
* or the content's for `fit-content` and its kin — or NaN. */
|
|
782
916
|
set: number;
|
|
783
917
|
/** Its border-box width stretched across its container: the container's
|
|
784
918
|
* content width, less its margins. */
|
|
785
919
|
stretch: number;
|
|
920
|
+
/** Its border box's size along the main axis in the layout Yoga last
|
|
921
|
+
* made of the flex box, as Yoga has it. */
|
|
922
|
+
main: number;
|
|
923
|
+
/** The least it may be along the main axis, where its own style says:
|
|
924
|
+
* the minimum this engine set on its node, or NaN. */
|
|
925
|
+
least: number;
|
|
926
|
+
/** The size along the main axis it is frozen at, inflexible (`freezeAt`),
|
|
927
|
+
* or NaN. */
|
|
928
|
+
held: number;
|
|
929
|
+
/** Whether that is its automatic minimum (`holdAt`), its content's size,
|
|
930
|
+
* and not a minimum the line stopped at (`stoppedLine`). */
|
|
931
|
+
auto: boolean;
|
|
786
932
|
}
|
|
787
933
|
|
|
788
934
|
/**
|
|
@@ -855,14 +1001,236 @@ function measureBox(
|
|
|
855
1001
|
box: Box,
|
|
856
1002
|
ctx: LayoutContext,
|
|
857
1003
|
inner: number,
|
|
1004
|
+
/** The flex box's content width, the item's containing block's. */
|
|
1005
|
+
containing: number,
|
|
1006
|
+
laid: Laid,
|
|
858
1007
|
): { width: number; height: number } {
|
|
859
|
-
|
|
1008
|
+
// its padding as Yoga has it, a percentage of the flex box's width: a
|
|
1009
|
+
// probe of its content's width leaves a percentage at none
|
|
1010
|
+
resolveEdges(box, containing);
|
|
1011
|
+
const width = inner + box.horizontalExtra;
|
|
1012
|
+
// its height at this width is known, where a layout since has left it
|
|
1013
|
+
// another: the answer is the height, and the layout `layoutItemAt`'s
|
|
1014
|
+
const kept = keptNatural(box, ctx, width, containing);
|
|
1015
|
+
if (kept !== null && kept.serial !== box.layoutSerial) {
|
|
1016
|
+
return {
|
|
1017
|
+
width: inner,
|
|
1018
|
+
height: Math.max(0, kept.height - box.verticalExtra),
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
layoutNaturally(box, ctx, width, containing, laid);
|
|
860
1022
|
return {
|
|
861
1023
|
width: inner,
|
|
862
1024
|
height: Math.max(0, box.height - box.verticalExtra),
|
|
863
1025
|
};
|
|
864
1026
|
}
|
|
865
1027
|
|
|
1028
|
+
/** An item's height as its content's own makes it at a border-box width,
|
|
1029
|
+
* where this pass has laid it out so (`layoutNaturally`), and else NaN. */
|
|
1030
|
+
function naturalHeight(
|
|
1031
|
+
box: Box,
|
|
1032
|
+
ctx: LayoutContext,
|
|
1033
|
+
width: number,
|
|
1034
|
+
containing: number,
|
|
1035
|
+
laid: Laid,
|
|
1036
|
+
): number {
|
|
1037
|
+
if (Math.abs(laid.width - width) <= 0.01 && Number.isNaN(laid.tall)) {
|
|
1038
|
+
return laid.height;
|
|
1039
|
+
}
|
|
1040
|
+
return keptNatural(box, ctx, width, containing)?.height ?? NaN;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* Give an item the layout its content's own height makes of it at a
|
|
1045
|
+
* border-box width, and note it (`Laid`): the one this pass has made of it
|
|
1046
|
+
* there already, where it still has it.
|
|
1047
|
+
*
|
|
1048
|
+
* A flex box laid out a second time — at the height a stretch gave it, its
|
|
1049
|
+
* own flex layout made anew (`layoutItemAt`) — asks of each item what it
|
|
1050
|
+
* asked the first time, and the item's layout at a width is what it was.
|
|
1051
|
+
* Kept for the pass, a card laid out again for its height lays nothing in
|
|
1052
|
+
* it out that is where it was; made again at every layout of the box
|
|
1053
|
+
* around it, a card in a card in a card laid its innermost out twice a
|
|
1054
|
+
* level.
|
|
1055
|
+
*/
|
|
1056
|
+
function layoutNaturally(
|
|
1057
|
+
box: Box,
|
|
1058
|
+
ctx: LayoutContext,
|
|
1059
|
+
width: number,
|
|
1060
|
+
/** The flex box's content width, the item's containing block's. */
|
|
1061
|
+
containing: number,
|
|
1062
|
+
laid: Laid,
|
|
1063
|
+
): void {
|
|
1064
|
+
const kept = keptNatural(box, ctx, width, containing);
|
|
1065
|
+
if (kept !== null && kept.serial === box.layoutSerial) {
|
|
1066
|
+
// put back, where the flex layout stretched it
|
|
1067
|
+
box.height = kept.height;
|
|
1068
|
+
} else {
|
|
1069
|
+
const at = ctx.positioned.length;
|
|
1070
|
+
ctx.layoutSubtree(box, width, containing);
|
|
1071
|
+
// a replaced box is sized, not laid out, and has no layout to count
|
|
1072
|
+
if (box.kind !== 'replaced') {
|
|
1073
|
+
NATURAL.set(box, {
|
|
1074
|
+
ctx,
|
|
1075
|
+
serial: box.layoutSerial,
|
|
1076
|
+
width,
|
|
1077
|
+
containing: readsContaining(box) ? containing : NaN,
|
|
1078
|
+
base: box.percentHeightBase,
|
|
1079
|
+
edges: edgesOf(box),
|
|
1080
|
+
height: box.height,
|
|
1081
|
+
queued:
|
|
1082
|
+
ctx.positioned.length > at ? { at, first: ctx.positioned[at] } : null,
|
|
1083
|
+
drawn: NaN,
|
|
1084
|
+
content: NaN,
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
laid.width = box.width;
|
|
1089
|
+
laid.height = box.height;
|
|
1090
|
+
laid.tall = NaN;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* The layout this pass made of an item at its content's own height
|
|
1095
|
+
* (`layoutNaturally`), where one at `width` now would be the same: the same
|
|
1096
|
+
* width, the same edges — a percentage among them resolved, as the layout
|
|
1097
|
+
* leaves them — the same containing block's width where a limit on its own
|
|
1098
|
+
* is a percentage of it, and the same height for its percentages, where its
|
|
1099
|
+
* own style reads that at all, which the flex box around it coming to a
|
|
1100
|
+
* definite height changes for every item in it. Its `height` is the item's
|
|
1101
|
+
* there whatever has laid it out since, and the layout is the one the box
|
|
1102
|
+
* has where nothing has (`Box.layoutSerial`).
|
|
1103
|
+
*/
|
|
1104
|
+
function keptNatural(
|
|
1105
|
+
box: Box,
|
|
1106
|
+
ctx: LayoutContext,
|
|
1107
|
+
width: number,
|
|
1108
|
+
containing: number,
|
|
1109
|
+
): Natural | null {
|
|
1110
|
+
const kept = NATURAL.get(box);
|
|
1111
|
+
if (kept === undefined || kept.ctx !== ctx || kept.width !== width) {
|
|
1112
|
+
return null;
|
|
1113
|
+
}
|
|
1114
|
+
if (!Number.isNaN(kept.containing) && kept.containing !== containing) {
|
|
1115
|
+
return null;
|
|
1116
|
+
}
|
|
1117
|
+
if (!Object.is(kept.base, box.percentHeightBase) && readsPercentBase(box)) {
|
|
1118
|
+
return null;
|
|
1119
|
+
}
|
|
1120
|
+
// the boxes in it that are positioned last are still waiting to be: a
|
|
1121
|
+
// `line-clamp: auto` box laid out a second time forgets the ones its
|
|
1122
|
+
// first layout found
|
|
1123
|
+
if (kept.queued && ctx.positioned[kept.queued.at] !== kept.queued.first) {
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
const edges = kept.edges;
|
|
1127
|
+
return edges[0] === box.marginTop &&
|
|
1128
|
+
edges[1] === box.marginRight &&
|
|
1129
|
+
edges[2] === box.marginBottom &&
|
|
1130
|
+
edges[3] === box.marginLeft &&
|
|
1131
|
+
edges[4] === box.borderTop &&
|
|
1132
|
+
edges[5] === box.borderRight &&
|
|
1133
|
+
edges[6] === box.borderBottom &&
|
|
1134
|
+
edges[7] === box.borderLeft &&
|
|
1135
|
+
edges[8] === box.padTop &&
|
|
1136
|
+
edges[9] === box.padRight &&
|
|
1137
|
+
edges[10] === box.padBottom &&
|
|
1138
|
+
edges[11] === box.padLeft
|
|
1139
|
+
? kept
|
|
1140
|
+
: null;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/** A layout of an item at its content's own height (`layoutNaturally`): in
|
|
1144
|
+
* which pass, which of the box's layouts it was, and what it was made
|
|
1145
|
+
* from. */
|
|
1146
|
+
interface Natural {
|
|
1147
|
+
ctx: LayoutContext;
|
|
1148
|
+
serial: number;
|
|
1149
|
+
/** The border-box width it was asked for at. */
|
|
1150
|
+
width: number;
|
|
1151
|
+
/** Its containing block's width, where a limit on its own width is a
|
|
1152
|
+
* percentage of that (`readsContaining`), and else NaN: a nest of flex
|
|
1153
|
+
* boxes is laid out in rooms of many widths, and an item with no such
|
|
1154
|
+
* limit is laid out the same in each. */
|
|
1155
|
+
containing: number;
|
|
1156
|
+
/** The height its percentages were of. */
|
|
1157
|
+
base: number;
|
|
1158
|
+
/** Its margins, borders and padding once laid out (`edgesOf`). */
|
|
1159
|
+
edges: number[];
|
|
1160
|
+
height: number;
|
|
1161
|
+
/** The first of the out-of-flow boxes that layout queued to be
|
|
1162
|
+
* positioned (`LayoutContext.positioned`) and where; null for none. */
|
|
1163
|
+
queued: { at: number; first: LayoutContext['positioned'][number] } | null;
|
|
1164
|
+
/** What `autoMinimums` read from that layout, NaN until it has: how wide
|
|
1165
|
+
* it drew, and how far down its content came, each its border box's. */
|
|
1166
|
+
drawn: number;
|
|
1167
|
+
content: number;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
const NATURAL = new WeakMap<Box, Natural>();
|
|
1171
|
+
|
|
1172
|
+
/** Note what was read from the layout an item has, where that is the one
|
|
1173
|
+
* its content's own height makes of it at `width`. */
|
|
1174
|
+
function remember(
|
|
1175
|
+
box: Box,
|
|
1176
|
+
ctx: LayoutContext,
|
|
1177
|
+
width: number,
|
|
1178
|
+
containing: number,
|
|
1179
|
+
field: 'drawn' | 'content',
|
|
1180
|
+
value: number,
|
|
1181
|
+
): void {
|
|
1182
|
+
const made = keptNatural(box, ctx, width, containing);
|
|
1183
|
+
if (made !== null && made.serial === box.layoutSerial) made[field] = value;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/** A box's edges, which a layout resolves again against the width it is
|
|
1187
|
+
* given: margins, borders, padding, each from the top round. */
|
|
1188
|
+
function edgesOf(box: Box): number[] {
|
|
1189
|
+
return [
|
|
1190
|
+
box.marginTop,
|
|
1191
|
+
box.marginRight,
|
|
1192
|
+
box.marginBottom,
|
|
1193
|
+
box.marginLeft,
|
|
1194
|
+
box.borderTop,
|
|
1195
|
+
box.borderRight,
|
|
1196
|
+
box.borderBottom,
|
|
1197
|
+
box.borderLeft,
|
|
1198
|
+
box.padTop,
|
|
1199
|
+
box.padRight,
|
|
1200
|
+
box.padBottom,
|
|
1201
|
+
box.padLeft,
|
|
1202
|
+
];
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/** Whether the width a box is laid out at is held to a percentage of its
|
|
1206
|
+
* containing block's: a least or greatest width that is one. */
|
|
1207
|
+
function readsContaining(box: Box): boolean {
|
|
1208
|
+
const style = box.style;
|
|
1209
|
+
return (
|
|
1210
|
+
isPct(style.minWidth) ||
|
|
1211
|
+
(style.maxWidth !== 'none' && isPct(style.maxWidth))
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
/** Whether a box's own layout reads the height its percentages are of: a
|
|
1216
|
+
* height, a least or greatest one, or an offset that is a percentage, a
|
|
1217
|
+
* height that is a keyword — `stretch` fills it — or an anonymous box,
|
|
1218
|
+
* which hands it on to what is in it (`percentBaseInside`). */
|
|
1219
|
+
function readsPercentBase(box: Box): boolean {
|
|
1220
|
+
if (!box.el && !box.pseudo) return true;
|
|
1221
|
+
const style = box.style;
|
|
1222
|
+
return (
|
|
1223
|
+
isPct(style.height) ||
|
|
1224
|
+
isPct(style.minHeight) ||
|
|
1225
|
+
(style.maxHeight !== 'none' && isPct(style.maxHeight)) ||
|
|
1226
|
+
isPct(style.top) ||
|
|
1227
|
+
isPct(style.bottom) ||
|
|
1228
|
+
style.heightKeyword !== null ||
|
|
1229
|
+
style.minHeightKeyword !== null ||
|
|
1230
|
+
style.maxHeightKeyword !== null
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
866
1234
|
/** The width an item's content is measured at: the width Yoga gives it
|
|
867
1235
|
* exactly, or its max-content width, no wider than any it may take up to
|
|
868
1236
|
* (CSS Flexbox 9.2). */
|
|
@@ -903,6 +1271,7 @@ function autoMinimums(
|
|
|
903
1271
|
row: boolean,
|
|
904
1272
|
ctx: LayoutContext,
|
|
905
1273
|
containingWidth: number,
|
|
1274
|
+
hold: Hold,
|
|
906
1275
|
): boolean {
|
|
907
1276
|
let changed = false;
|
|
908
1277
|
for (const { box, node, laid } of items) {
|
|
@@ -915,17 +1284,18 @@ function autoMinimums(
|
|
|
915
1284
|
continue;
|
|
916
1285
|
}
|
|
917
1286
|
const width = meant(
|
|
918
|
-
node.getComputedWidth(),
|
|
1287
|
+
row ? laid.main : node.getComputedWidth(),
|
|
919
1288
|
laid.set,
|
|
920
1289
|
laid.stretch,
|
|
921
1290
|
laid.width,
|
|
1291
|
+
row ? laid.held : NaN,
|
|
922
1292
|
);
|
|
923
1293
|
// a replaced item's content along a row is its natural width
|
|
924
1294
|
if (row && box.kind === 'replaced') {
|
|
925
1295
|
if (style.minWidth !== AUTO || style.minWidthKeyword) continue;
|
|
926
1296
|
const least = replacedMinimum(box, containingWidth);
|
|
927
1297
|
if (least === null || width >= least - 0.01) continue;
|
|
928
|
-
node
|
|
1298
|
+
holdAt(node, laid, row, least, hold);
|
|
929
1299
|
changed = true;
|
|
930
1300
|
continue;
|
|
931
1301
|
}
|
|
@@ -952,7 +1322,7 @@ function autoMinimums(
|
|
|
952
1322
|
if (most !== null) least = Math.min(least, most + extra);
|
|
953
1323
|
}
|
|
954
1324
|
if (width >= least - 0.01) continue;
|
|
955
|
-
node
|
|
1325
|
+
holdAt(node, laid, row, least, hold);
|
|
956
1326
|
changed = true;
|
|
957
1327
|
continue;
|
|
958
1328
|
}
|
|
@@ -980,8 +1350,8 @@ function autoMinimums(
|
|
|
980
1350
|
}
|
|
981
1351
|
if (width >= least - 0.01) continue;
|
|
982
1352
|
least = Math.min(least, minContentOf(box, ctx, laid));
|
|
983
|
-
if (
|
|
984
|
-
node
|
|
1353
|
+
if (laid.main >= least - 0.01) continue;
|
|
1354
|
+
holdAt(node, laid, row, least, hold);
|
|
985
1355
|
changed = true;
|
|
986
1356
|
continue;
|
|
987
1357
|
}
|
|
@@ -995,16 +1365,38 @@ function autoMinimums(
|
|
|
995
1365
|
) {
|
|
996
1366
|
continue;
|
|
997
1367
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1368
|
+
// What its content's own height makes of it at this width — how wide
|
|
1369
|
+
// that drew, along a row, and how far down, along a column — is read
|
|
1370
|
+
// from that layout, which the item is given where it has another: or
|
|
1371
|
+
// is what this pass read from it before, where the flex box is laid
|
|
1372
|
+
// out a second time (`layoutNaturally`)
|
|
1373
|
+
const kept = keptNatural(box, ctx, width, containingWidth);
|
|
1374
|
+
const read = kept === null ? NaN : row ? kept.drawn : kept.content;
|
|
1375
|
+
if (Number.isNaN(read) && !(Math.abs(laid.width - width) <= 0.01)) {
|
|
1376
|
+
layoutNaturally(box, ctx, width, containingWidth, laid);
|
|
1002
1377
|
}
|
|
1378
|
+
const height = Number.isNaN(read) ? laid.height : kept!.height;
|
|
1003
1379
|
let least: number;
|
|
1004
1380
|
if (row) {
|
|
1005
|
-
// or than what it drew at the width it has: nothing overflows it
|
|
1006
|
-
|
|
1007
|
-
|
|
1381
|
+
// or than what it drew at the width it has: nothing overflows it —
|
|
1382
|
+
// unless a line in it fits for being cut short by `text-overflow`,
|
|
1383
|
+
// which is how the line is drawn and no part of how wide it is: a
|
|
1384
|
+
// button whose label is a `truncate` span is no narrower than the
|
|
1385
|
+
// label, where it was shrunk and the label cut. No width, then, that
|
|
1386
|
+
// it drew at, which is what is kept of it
|
|
1387
|
+
let drawn = read;
|
|
1388
|
+
if (Number.isNaN(drawn)) {
|
|
1389
|
+
const seen = { cut: false };
|
|
1390
|
+
drawn = intrinsicWidth(box, seen) + box.horizontalExtra;
|
|
1391
|
+
if (seen.cut) drawn = Infinity;
|
|
1392
|
+
remember(box, ctx, width, containingWidth, 'drawn', drawn);
|
|
1393
|
+
}
|
|
1394
|
+
if (
|
|
1395
|
+
Number.isFinite(drawn) &&
|
|
1396
|
+
transferredWidth(box, drawn) <= width + 0.5
|
|
1397
|
+
) {
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1008
1400
|
// within what a ratio makes of its least and greatest heights (4.5's
|
|
1009
1401
|
// content size suggestion)
|
|
1010
1402
|
least = transferredWidth(box, minContentOf(box, ctx, laid));
|
|
@@ -1013,55 +1405,257 @@ function autoMinimums(
|
|
|
1013
1405
|
const most = resolveOrNull(style.maxWidth, containingWidth);
|
|
1014
1406
|
if (most !== null) least = Math.min(least, most + extra);
|
|
1015
1407
|
}
|
|
1016
|
-
if (
|
|
1017
|
-
node
|
|
1408
|
+
if (laid.main >= least - 0.01) continue;
|
|
1409
|
+
holdAt(node, laid, row, least, hold);
|
|
1018
1410
|
} else {
|
|
1019
1411
|
// its height, or where it has one of its own and its content comes to
|
|
1020
1412
|
// less, its content's: the lesser of the two (4.5)
|
|
1021
|
-
let content
|
|
1022
|
-
if (
|
|
1413
|
+
let content = read;
|
|
1414
|
+
if (!Number.isNaN(content)) {
|
|
1415
|
+
// read before
|
|
1416
|
+
} else if (box.kind === 'replaced') content = laid.height;
|
|
1023
1417
|
else if (style.height !== AUTO && percentHeightsIn(box)) {
|
|
1024
1418
|
// what its content comes to where it has no height to take
|
|
1025
1419
|
// percentages of, as an intrinsic size is measured: laid out so
|
|
1026
1420
|
// apart, and the final pass lays it out again
|
|
1027
1421
|
FLEXED_HEIGHT.set(box, NaN);
|
|
1028
1422
|
try {
|
|
1029
|
-
ctx.layoutSubtree(box, width);
|
|
1423
|
+
ctx.layoutSubtree(box, width, containingWidth);
|
|
1030
1424
|
} finally {
|
|
1031
1425
|
FLEXED_HEIGHT.delete(box);
|
|
1032
1426
|
}
|
|
1033
|
-
content = contentBottom(box) + box.verticalExtra;
|
|
1427
|
+
content = ratioContent(box, contentBottom(box) + box.verticalExtra);
|
|
1034
1428
|
laid.width = NaN;
|
|
1035
|
-
} else
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
const aspect = style.aspectRatio;
|
|
1039
|
-
if (aspect && box.kind !== 'replaced') {
|
|
1040
|
-
content = Math.max(
|
|
1041
|
-
content,
|
|
1042
|
-
style.boxSizing === 'border-box'
|
|
1043
|
-
? box.width / aspect.ratio
|
|
1044
|
-
: box.contentWidth / aspect.ratio + box.verticalExtra,
|
|
1045
|
-
);
|
|
1429
|
+
} else {
|
|
1430
|
+
content = ratioContent(box, contentBottom(box) + box.verticalExtra);
|
|
1431
|
+
remember(box, ctx, width, containingWidth, 'content', content);
|
|
1046
1432
|
}
|
|
1047
1433
|
// within what the ratio makes of its least and greatest widths
|
|
1048
1434
|
if (box.kind !== 'replaced') {
|
|
1049
1435
|
content = transferredHeight(box, content, containingWidth);
|
|
1050
1436
|
}
|
|
1051
|
-
least = Math.min(
|
|
1437
|
+
least = Math.min(height, content);
|
|
1052
1438
|
const extra = style.boxSizing === 'border-box' ? 0 : box.verticalExtra;
|
|
1053
1439
|
if (style.maxHeight !== 'none') {
|
|
1054
1440
|
const most = resolveOrNull(style.maxHeight, NaN);
|
|
1055
1441
|
if (most !== null) least = Math.min(least, most + extra);
|
|
1056
1442
|
}
|
|
1057
|
-
if (
|
|
1058
|
-
node
|
|
1443
|
+
if (laid.main >= least - 0.01) continue;
|
|
1444
|
+
holdAt(node, laid, row, least, hold);
|
|
1059
1445
|
}
|
|
1060
1446
|
changed = true;
|
|
1061
1447
|
}
|
|
1062
1448
|
return changed;
|
|
1063
1449
|
}
|
|
1064
1450
|
|
|
1451
|
+
/** A laid-out item's content height, its border box's: no less than its
|
|
1452
|
+
* width through its ratio, height of its own or not (CSS Sizing 4, 5.1). */
|
|
1453
|
+
function ratioContent(box: Box, content: number): number {
|
|
1454
|
+
const aspect = box.style.aspectRatio;
|
|
1455
|
+
if (!aspect || box.kind === 'replaced') return content;
|
|
1456
|
+
return Math.max(
|
|
1457
|
+
content,
|
|
1458
|
+
box.style.boxSizing === 'border-box'
|
|
1459
|
+
? box.width / aspect.ratio
|
|
1460
|
+
: box.contentWidth / aspect.ratio + box.verticalExtra,
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
/** How a flex box holds its items to their least sizes (`holdAt`). */
|
|
1465
|
+
interface Hold {
|
|
1466
|
+
/** Whether an item is frozen at its least size: on the one line of a
|
|
1467
|
+
* box that does not wrap. */
|
|
1468
|
+
frozen: boolean;
|
|
1469
|
+
/** Whether Yoga takes a length for a basis along the main axis
|
|
1470
|
+
* (`applyItem`'s `setBasis`), and else the item's own size. */
|
|
1471
|
+
basis: boolean;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
/**
|
|
1475
|
+
* Hold an item to its least size along the main axis, the size Yoga gave
|
|
1476
|
+
* it being under that. It is frozen there, as CSS Flexbox freezes an item
|
|
1477
|
+
* its minimum stops (9.7, step 4): inflexible, at that size, the rest of
|
|
1478
|
+
* the line sharing what is left. Handed the minimum as a `min-width`
|
|
1479
|
+
* alone, Yoga makes it the item's flex base size, which is two things the
|
|
1480
|
+
* specification does not do:
|
|
1481
|
+
*
|
|
1482
|
+
* - where the line has room to share out, the item takes its share on top
|
|
1483
|
+
* of the minimum: of three `flex: 1` items in 300 pixels, the one with a
|
|
1484
|
+
* word 200 wide was 233, where it is 200 and the others 50 each;
|
|
1485
|
+
* - where every item of a line is held, Yoga divides what the line is
|
|
1486
|
+
* short of by what is left of the shrink factors once it has taken each
|
|
1487
|
+
* item's away — nothing, or a float's rounding of it. Two items 110.992
|
|
1488
|
+
* and 62.706 pixels wide in a row too narrow for them came out billions
|
|
1489
|
+
* of pixels wide, and 31.3 and 17.7 came out as they are: by the digits
|
|
1490
|
+
* of their sizes, which are a text's widths in whatever font it is set.
|
|
1491
|
+
*
|
|
1492
|
+
* On the one line of a box that does not wrap, an item Yoga had under its
|
|
1493
|
+
* minimum has no more room when another is held too, so frozen is where it
|
|
1494
|
+
* ends. In a box that wraps it may have: the minimums move the line breaks
|
|
1495
|
+
* (9.3), and an item held on a crowded line may be on one with room to
|
|
1496
|
+
* grow in once they have. There the minimum is left to Yoga, as a minimum;
|
|
1497
|
+
* only a line of one item is short of room there, and what Yoga takes away
|
|
1498
|
+
* from its shrink factors is exactly what it added.
|
|
1499
|
+
*/
|
|
1500
|
+
function holdAt(
|
|
1501
|
+
node: YogaNode,
|
|
1502
|
+
laid: Laid,
|
|
1503
|
+
row: boolean,
|
|
1504
|
+
least: number,
|
|
1505
|
+
hold: Hold,
|
|
1506
|
+
): void {
|
|
1507
|
+
if (row) node.setMinWidth(least);
|
|
1508
|
+
else node.setMinHeight(least);
|
|
1509
|
+
if (!hold.frozen) return;
|
|
1510
|
+
laid.auto = true;
|
|
1511
|
+
freezeAt(node, laid, row, least, hold);
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
/** Freeze an item at a size along the main axis: inflexible, and that size
|
|
1515
|
+
* its flex base size. */
|
|
1516
|
+
function freezeAt(
|
|
1517
|
+
node: YogaNode,
|
|
1518
|
+
laid: Laid,
|
|
1519
|
+
row: boolean,
|
|
1520
|
+
size: number,
|
|
1521
|
+
hold: Hold,
|
|
1522
|
+
): void {
|
|
1523
|
+
laid.held = size;
|
|
1524
|
+
node.setFlexGrow(0);
|
|
1525
|
+
node.setFlexShrink(0);
|
|
1526
|
+
if (hold.basis) node.setFlexBasis(size);
|
|
1527
|
+
else if (row) node.setWidth(size);
|
|
1528
|
+
else node.setHeight(size);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/**
|
|
1532
|
+
* Put right the one line of a box that does not wrap, where every item of
|
|
1533
|
+
* it that may shrink is stopped by its minimum and Yoga made them wider for
|
|
1534
|
+
* it; whether Yoga was asked again. The line is laid out with each of them
|
|
1535
|
+
* frozen at its minimum, which is where CSS Flexbox leaves them (9.7, step
|
|
1536
|
+
* 4): the line is short of room with every one of them as small as it
|
|
1537
|
+
* goes.
|
|
1538
|
+
*
|
|
1539
|
+
* Yoga shares what a line is short of in two passes. The first takes each
|
|
1540
|
+
* item a minimum stops out of the sum of the scaled shrink factors, one
|
|
1541
|
+
* subtraction an item, and the second divides what the line is still short
|
|
1542
|
+
* of by what is left of the sum. With every item stopped that is nothing:
|
|
1543
|
+
* `(a + b) - a - b`, in float32s, which is 0 for some sizes and a rounding
|
|
1544
|
+
* either side of it for others, 2^-18 for sizes about a hundred. Yoga
|
|
1545
|
+
* 3.2.1 tests for 0, and the guard it has had since, a sum under 1e-6, is
|
|
1546
|
+
* under that rounding (react/yoga#1665, #1974). A negative rounding sends
|
|
1547
|
+
* every item under its minimum, which stops it, and a positive one makes
|
|
1548
|
+
* each of them wider by billions of pixels: `width: 110.992px; min-width:
|
|
1549
|
+
* 110.992px` beside `62.705625px` of the same, in a row 50 wide, were
|
|
1550
|
+
* 3599091712 and 2033329408 wide, where 100 and 60 were as they are. So
|
|
1551
|
+
* were two items with no minimum but their padding, and two with
|
|
1552
|
+
* `min-width: 0` beside one that does not shrink and is wider than the row
|
|
1553
|
+
* alone.
|
|
1554
|
+
*
|
|
1555
|
+
* A minimum of the item's own is Yoga's to hold it to, so this looks at
|
|
1556
|
+
* its answer, for a line that is two things at once. It is short of room
|
|
1557
|
+
* with every item that may shrink at its minimum — where 9.7 leaves each
|
|
1558
|
+
* of them at that, whatever their sizes. And one of them is wider than its
|
|
1559
|
+
* hypothetical size (9.7, step 2), which no sharing out of what a line is
|
|
1560
|
+
* short of makes an item: Yoga is asked for the line with nothing
|
|
1561
|
+
* shrinking, where each item is that size. Any other line is put back as
|
|
1562
|
+
* it was, the two layouts that took being the cost of one that is short of
|
|
1563
|
+
* room at its minimums and was not shared out to them; a line with room
|
|
1564
|
+
* for its minimums, or one whose every item sits at its own, has cost the
|
|
1565
|
+
* sum of its sizes.
|
|
1566
|
+
*
|
|
1567
|
+
* An automatic minimum is no part of it: an item held at one is frozen
|
|
1568
|
+
* already (`holdAt`), and one not yet held has no minimum to Yoga but its
|
|
1569
|
+
* padding and border, where this leaves it for `autoMinimums` to find
|
|
1570
|
+
* short of its content.
|
|
1571
|
+
*/
|
|
1572
|
+
function stoppedLine(
|
|
1573
|
+
items: readonly { box: Box; node: YogaNode; laid: Laid }[],
|
|
1574
|
+
row: boolean,
|
|
1575
|
+
/** The flex box's size along the main axis. */
|
|
1576
|
+
room: number,
|
|
1577
|
+
/** The gap between two items. */
|
|
1578
|
+
gap: number,
|
|
1579
|
+
hold: Hold,
|
|
1580
|
+
/** Lay the flex box out, as its nodes now are. */
|
|
1581
|
+
ask: () => void,
|
|
1582
|
+
): boolean {
|
|
1583
|
+
// what is between the items' border boxes: the gaps, and their margins
|
|
1584
|
+
let between = gap * (items.length - 1);
|
|
1585
|
+
// the line's size with each item that may shrink at its minimum
|
|
1586
|
+
let least = 0;
|
|
1587
|
+
let shrinking = 0;
|
|
1588
|
+
let over = false;
|
|
1589
|
+
for (const { box, laid } of items) {
|
|
1590
|
+
const style = box.style;
|
|
1591
|
+
// an `auto` margin is none on a line with no room (8.1)
|
|
1592
|
+
if (row) {
|
|
1593
|
+
if (style.marginLeft !== AUTO) between += box.marginLeft;
|
|
1594
|
+
if (style.marginRight !== AUTO) between += box.marginRight;
|
|
1595
|
+
} else {
|
|
1596
|
+
if (style.marginTop !== AUTO) between += box.marginTop;
|
|
1597
|
+
if (style.marginBottom !== AUTO) between += box.marginBottom;
|
|
1598
|
+
}
|
|
1599
|
+
if (!shrinks(box, laid)) least += laid.main;
|
|
1600
|
+
else {
|
|
1601
|
+
const min = leastOf(box, laid, row);
|
|
1602
|
+
shrinking += 1;
|
|
1603
|
+
least += min;
|
|
1604
|
+
if (laid.main > min + 0.01) over = true;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
// two at the least: what is left of a sum of one, less it, is 0 exactly.
|
|
1608
|
+
// A line within a thousandth of a pixel of room is short of it: what
|
|
1609
|
+
// Yoga makes of that is as far out, and the minimums are that near
|
|
1610
|
+
if (shrinking < 2 || !over || !(between + least > room - SHORT)) {
|
|
1611
|
+
return false;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
const stopped = items.filter(({ box, laid }) => shrinks(box, laid));
|
|
1615
|
+
for (const { node } of stopped) node.setFlexShrink(0);
|
|
1616
|
+
ask();
|
|
1617
|
+
// each item its hypothetical size, which one that does not shrink is on
|
|
1618
|
+
// a line short of room — where Yoga may have grown it, on a line it took
|
|
1619
|
+
// for having some
|
|
1620
|
+
let wider = false;
|
|
1621
|
+
least = 0;
|
|
1622
|
+
for (const { box, node, laid } of items) {
|
|
1623
|
+
const hypothetical = row
|
|
1624
|
+
? node.getComputedWidth()
|
|
1625
|
+
: node.getComputedHeight();
|
|
1626
|
+
if (!shrinks(box, laid)) least += hypothetical;
|
|
1627
|
+
else {
|
|
1628
|
+
least += leastOf(box, laid, row);
|
|
1629
|
+
if (laid.main > hypothetical && !nearly(laid.main, hypothetical)) {
|
|
1630
|
+
wider = true;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
const freeze = wider && between + least > room - SHORT;
|
|
1635
|
+
for (const { box, node, laid } of stopped) {
|
|
1636
|
+
if (freeze) freezeAt(node, laid, row, leastOf(box, laid, row), hold);
|
|
1637
|
+
else node.setFlexShrink(box.style.flexShrink);
|
|
1638
|
+
}
|
|
1639
|
+
ask();
|
|
1640
|
+
return true;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/** Whether Yoga may shrink an item: it has a shrink factor, and is not
|
|
1644
|
+
* frozen (`freezeAt`). */
|
|
1645
|
+
function shrinks(box: Box, laid: Laid): boolean {
|
|
1646
|
+
return box.style.flexShrink > 0 && Number.isNaN(laid.held);
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
/** How near to its room a line may be and be short of it (`stoppedLine`). */
|
|
1650
|
+
const SHORT = 0.001;
|
|
1651
|
+
|
|
1652
|
+
/** The least an item may be along the main axis, its border box: the
|
|
1653
|
+
* minimum of its own, and no less than its padding and borders. */
|
|
1654
|
+
function leastOf(box: Box, laid: Laid, row: boolean): number {
|
|
1655
|
+
const extra = row ? box.horizontalExtra : box.verticalExtra;
|
|
1656
|
+
return Number.isNaN(laid.least) ? extra : Math.max(laid.least, extra);
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1065
1659
|
/** An item's min-content width, its border box's, taken the once: its
|
|
1066
1660
|
* content's, whatever width it has of its own (`keywordWidth`'s too). */
|
|
1067
1661
|
function minContentOf(box: Box, ctx: LayoutContext, laid: Laid): number {
|