@react-x11/components 0.14.1 → 0.16.1
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/code-editor/node.d.ts +13 -0
- package/dist/code-editor/node.d.ts.map +1 -1
- package/dist/code-editor/node.js +23 -5
- package/dist/code-editor/node.js.map +1 -1
- package/dist/html/css/cascade.d.ts +171 -26
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +616 -216
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +25 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +307 -19
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/content.d.ts +11 -6
- package/dist/html/css/content.d.ts.map +1 -1
- package/dist/html/css/content.js +171 -12
- package/dist/html/css/content.js.map +1 -1
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +62 -9
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/shapes.d.ts +18 -0
- package/dist/html/css/shapes.d.ts.map +1 -1
- package/dist/html/css/shapes.js +21 -1
- package/dist/html/css/shapes.js.map +1 -1
- package/dist/html/css/style.d.ts +42 -5
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +74 -12
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +27 -8
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +5 -0
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +18 -9
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +7 -0
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/block.d.ts +29 -9
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +141 -52
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +29 -2
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +172 -23
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/css-grid.d.ts.map +1 -1
- package/dist/html/layout/css-grid.js +11 -9
- 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 +1759 -238
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts +15 -3
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +64 -19
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/table.d.ts +27 -1
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +886 -324
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +35 -4
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +122 -26
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +558 -178
- package/dist/html/paint.js.map +1 -1
- package/dist/html/resources.js +14 -1
- package/dist/html/resources.js.map +1 -1
- package/dist/html/svg.d.ts +23 -7
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +264 -59
- package/dist/html/svg.js.map +1 -1
- package/dist/html/widgets.d.ts.map +1 -1
- package/dist/html/widgets.js +57 -4
- package/dist/html/widgets.js.map +1 -1
- package/package.json +3 -3
- package/src/code-editor/node.ts +24 -5
- package/src/html/css/cascade.ts +697 -221
- package/src/html/css/color.ts +345 -18
- package/src/html/css/content.ts +173 -15
- package/src/html/css/parse.ts +60 -8
- package/src/html/css/shapes.ts +35 -1
- package/src/html/css/style.ts +122 -17
- package/src/html/css/ua.ts +27 -8
- package/src/html/css/values.ts +23 -9
- package/src/html/index.ts +7 -0
- package/src/html/layout/block.ts +149 -58
- package/src/html/layout/boxes.ts +211 -27
- package/src/html/layout/css-grid.ts +12 -9
- package/src/html/layout/flex.ts +1975 -231
- package/src/html/layout/inline.ts +70 -16
- package/src/html/layout/table.ts +961 -332
- package/src/html/node.ts +155 -27
- package/src/html/paint.ts +692 -234
- package/src/html/resources.ts +13 -1
- package/src/html/svg.ts +299 -56
- package/src/html/widgets.ts +61 -4
package/src/html/layout/flex.ts
CHANGED
|
@@ -34,6 +34,7 @@ import { Box, PAINT_ORDER, isBlank } from './boxes.js';
|
|
|
34
34
|
import {
|
|
35
35
|
FLEXED_HEIGHT,
|
|
36
36
|
MIN_CONTENT_PROBE,
|
|
37
|
+
STRETCHED_ACROSS,
|
|
37
38
|
USED_HEIGHT,
|
|
38
39
|
centreButton,
|
|
39
40
|
clampHeight,
|
|
@@ -50,11 +51,13 @@ import {
|
|
|
50
51
|
transferredHeight,
|
|
51
52
|
transferredWidth,
|
|
52
53
|
heightFromWidth,
|
|
54
|
+
heightThroughRatio,
|
|
53
55
|
widthFromHeight,
|
|
54
56
|
resolveEdges,
|
|
55
57
|
} from './block.js';
|
|
56
58
|
import { layoutGrid } from './css-grid.js';
|
|
57
59
|
import { firstBaselineIn } from './inline.js';
|
|
60
|
+
import { TABLE_CONTENT } from './table.js';
|
|
58
61
|
import type { LayoutContext } from './block.js';
|
|
59
62
|
|
|
60
63
|
// `react-x11/yoga` re-exports yoga's own declarations, so the node shape and
|
|
@@ -121,7 +124,6 @@ export function layoutFlex(
|
|
|
121
124
|
// and the measure that asked came back vast: a row beside such an item
|
|
122
125
|
// was squeezed to wrap every word.
|
|
123
126
|
const bounded = Number.isFinite(contentWidth);
|
|
124
|
-
if (bounded) root.setWidth(contentWidth);
|
|
125
127
|
// The content box's height, where it is definite: a length or a
|
|
126
128
|
// percentage that resolves, or what `aspect-ratio` makes of the width —
|
|
127
129
|
// less the padding and borders a `border-box` height holds, which put
|
|
@@ -132,18 +134,22 @@ export function layoutFlex(
|
|
|
132
134
|
const definite = percentBaseInside(box);
|
|
133
135
|
const own = Number.isFinite(definite);
|
|
134
136
|
const height = USED_HEIGHT.get(box) ?? (own ? definite : null);
|
|
135
|
-
//
|
|
136
|
-
let
|
|
137
|
+
// the limits on its content box's height where it has none of its own
|
|
138
|
+
let floor = 0;
|
|
139
|
+
let ceiling = Infinity;
|
|
137
140
|
if (height !== null) root.setHeight(height);
|
|
138
141
|
else {
|
|
139
142
|
root.setHeightAuto();
|
|
140
143
|
const extra = box.verticalExtra;
|
|
141
144
|
const min = clampHeight(box, extra) - extra;
|
|
142
|
-
if (min > 0)
|
|
145
|
+
if (min > 0) {
|
|
146
|
+
floor = min;
|
|
147
|
+
root.setMinHeight(min);
|
|
148
|
+
}
|
|
143
149
|
const max = clampHeight(box, Infinity) - extra;
|
|
144
150
|
if (Number.isFinite(max)) {
|
|
145
|
-
|
|
146
|
-
|
|
151
|
+
ceiling = Math.max(0, max);
|
|
152
|
+
root.setMaxHeight(ceiling);
|
|
147
153
|
}
|
|
148
154
|
}
|
|
149
155
|
|
|
@@ -169,6 +175,17 @@ export function layoutFlex(
|
|
|
169
175
|
);
|
|
170
176
|
} else PAINT_ORDER.delete(box);
|
|
171
177
|
|
|
178
|
+
// Whether a line weighs its items one against another as it shrinks
|
|
179
|
+
// them, each by its flex base size (CSS Flexbox 9.7, step 4c): the one
|
|
180
|
+
// line of a row that does not wrap, two or more of whose items may
|
|
181
|
+
// shrink. One that shrinks alone comes to the room the rest leave it,
|
|
182
|
+
// whatever its base, and a line of a row that wraps is too narrow for
|
|
183
|
+
// an item only where that is alone on it.
|
|
184
|
+
const weighs =
|
|
185
|
+
box.style.flexWrap === 'nowrap' &&
|
|
186
|
+
box.style.flexDirection.startsWith('row') &&
|
|
187
|
+
flowing.filter((child) => child.style.flexShrink > 0).length > 1;
|
|
188
|
+
|
|
172
189
|
const items: { box: Box; node: YogaNode; laid: Laid }[] = [];
|
|
173
190
|
for (const child of flowing) {
|
|
174
191
|
const node = Y.Node.create(flexConfig());
|
|
@@ -181,17 +198,112 @@ export function layoutFlex(
|
|
|
181
198
|
stretch: NaN,
|
|
182
199
|
main: NaN,
|
|
183
200
|
least: NaN,
|
|
201
|
+
most: NaN,
|
|
202
|
+
basis: NaN,
|
|
203
|
+
sized: false,
|
|
204
|
+
content: NaN,
|
|
205
|
+
fitted: NaN,
|
|
206
|
+
base: NaN,
|
|
207
|
+
narrowest: NaN,
|
|
184
208
|
held: NaN,
|
|
209
|
+
minimum: NaN,
|
|
185
210
|
auto: false,
|
|
211
|
+
across: false,
|
|
212
|
+
unstretched: NaN,
|
|
186
213
|
};
|
|
187
|
-
applyItem(
|
|
214
|
+
applyItem(
|
|
215
|
+
node,
|
|
216
|
+
child,
|
|
217
|
+
ctx,
|
|
218
|
+
contentWidth,
|
|
219
|
+
laid,
|
|
220
|
+
height !== null,
|
|
221
|
+
own ? definite : NaN,
|
|
222
|
+
weighs,
|
|
223
|
+
);
|
|
188
224
|
root.insertChild(node, items.length);
|
|
189
225
|
items.push({ box: child, node, laid });
|
|
190
226
|
}
|
|
191
227
|
|
|
228
|
+
// A column that wraps is as wide as its content box, but each of its
|
|
229
|
+
// lines is as wide as its widest item, every item `fit-content` across
|
|
230
|
+
// (CSS Flexbox 9.4, steps 7 and 8), and so is placed across here and not
|
|
231
|
+
// by Yoga (`crossLines`). Yoga stretched an item to the box wherever the
|
|
232
|
+
// column had room for every item down it — with no height, always — so a
|
|
233
|
+
// line was the box's width, an item wider at its narrowest ran out of
|
|
234
|
+
// it, and `align-content` had no room to centre or end a line in. And
|
|
235
|
+
// stretching a line, it laid each stretched item out again at its height
|
|
236
|
+
// less its top and bottom margins and plus its side ones (its multi-line
|
|
237
|
+
// pass adds the margins across a column where it means the ones down
|
|
238
|
+
// it), and added what `space-around` puts between lines to its width. So
|
|
239
|
+
// Yoga lays the box out with every item at its line's start, where it
|
|
240
|
+
// measures each `fit-content` (`innerWidth`) and down the column as wide
|
|
241
|
+
// as that, as the specification measures a line's items (9.2, step 3),
|
|
242
|
+
// and the lines one more pixel apart than they are, so that each line
|
|
243
|
+
// starts where the last does not and says which items are on it.
|
|
244
|
+
const crossed = wrapsColumn(box.style, contentWidth);
|
|
245
|
+
if (crossed) {
|
|
246
|
+
root.setAlignContent(Y.ALIGN_FLEX_START);
|
|
247
|
+
root.setGap(Y.GUTTER_COLUMN, columnGap + 1);
|
|
248
|
+
for (const { node } of items) node.setAlignSelf(Y.ALIGN_FLEX_START);
|
|
249
|
+
}
|
|
250
|
+
if (bounded) root.setWidth(contentWidth);
|
|
251
|
+
|
|
192
252
|
const direction =
|
|
193
253
|
box.style.direction === 'rtl' ? Y.DIRECTION_RTL : Y.DIRECTION_LTR;
|
|
194
254
|
const row = box.style.flexDirection.startsWith('row');
|
|
255
|
+
// Yoga aligns the lines of a box that wraps, or of a row that aligns by
|
|
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 =
|
|
260
|
+
row &&
|
|
261
|
+
(box.style.alignItems === 'baseline' ||
|
|
262
|
+
items.some(({ box: child }) => child.style.alignSelf === 'baseline'));
|
|
263
|
+
// An `auto` margin across a row that wraps takes the room its line has
|
|
264
|
+
// past the item (CSS Flexbox 8.1, 9.6), where Yoga's pass that sets the
|
|
265
|
+
// items across their lines reads an item's alignment and not its
|
|
266
|
+
// margins: it stretched one with `margin-top: auto` to its line, and set
|
|
267
|
+
// one with a height at its line's start. So Yoga is told the alignment
|
|
268
|
+
// the margins come to (`autoAcross`), which it then places it by.
|
|
269
|
+
if (row && box.style.flexWrap !== 'nowrap') {
|
|
270
|
+
const reverse = box.style.flexWrap === 'wrap-reverse';
|
|
271
|
+
for (const { box: child, node } of items) {
|
|
272
|
+
const aligned = autoAcross(child.style, reverse);
|
|
273
|
+
if (aligned) node.setAlignSelf(ALIGN[aligned]);
|
|
274
|
+
}
|
|
275
|
+
} else if (baselines) {
|
|
276
|
+
// Yoga takes a row that does not wrap through that pass as well, and
|
|
277
|
+
// sets its line there as tall as its items and not the row: an item
|
|
278
|
+
// centred or at the end of a row of a height was centred or ended in
|
|
279
|
+
// the items' height. It has sized that line already, as the most any
|
|
280
|
+
// item reaches down to its bottom edge — a baseline, to Yoga — and the
|
|
281
|
+
// largest bottom margin besides, whichever items those are: a row of
|
|
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.
|
|
292
|
+
for (const { box: child, node } of items) {
|
|
293
|
+
const aligned = autoAcross(child.style, false);
|
|
294
|
+
if (aligned) node.setAlignSelf(ALIGN[aligned]);
|
|
295
|
+
}
|
|
296
|
+
const aligned = items.filter(({ box: child }) =>
|
|
297
|
+
byBaseline(box.style, child.style),
|
|
298
|
+
);
|
|
299
|
+
if (aligned.length < 2) {
|
|
300
|
+
for (const { node } of aligned) node.setAlignSelf(Y.ALIGN_FLEX_START);
|
|
301
|
+
if (box.style.alignItems === 'baseline') {
|
|
302
|
+
root.setAlignItems(Y.ALIGN_FLEX_START);
|
|
303
|
+
}
|
|
304
|
+
baselines = false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
195
307
|
// frozen at its least size on the one line of a box that does not
|
|
196
308
|
// wrap, by a basis where Yoga takes one along the main axis
|
|
197
309
|
// (`applyItem`'s `setBasis`)
|
|
@@ -199,30 +311,81 @@ export function layoutFlex(
|
|
|
199
311
|
frozen: box.style.flexWrap === 'nowrap',
|
|
200
312
|
basis: row ? bounded : height !== null,
|
|
201
313
|
};
|
|
202
|
-
const
|
|
314
|
+
const layout = (): void =>
|
|
203
315
|
root.calculateLayout(
|
|
204
316
|
bounded ? contentWidth : Number.NaN,
|
|
205
317
|
height ?? Number.NaN,
|
|
206
318
|
direction,
|
|
207
319
|
);
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
const
|
|
320
|
+
// Down a column that wraps, the items Yoga stretches across their lines
|
|
321
|
+
// come out of its layout at another height than it sized them at, where
|
|
322
|
+
// their side margins are not their top and bottom ones (`unstretch`):
|
|
323
|
+
// laid out so that each has the height it was sized at
|
|
324
|
+
const restretched = row || hold.frozen ? [] : stretchedDown(root, items);
|
|
325
|
+
const sized = bounded ? contentWidth : null;
|
|
326
|
+
// and across a row that wraps, the items Yoga stretches across lines
|
|
327
|
+
// `align-content` spaces out come out of its layout as tall as their
|
|
328
|
+
// line and the room after it as well (`unspace`): laid out so that each
|
|
329
|
+
// is as tall as its line, where the box has room to space them out in —
|
|
330
|
+
// a height, or a minimum one — and with the lines at its start where it
|
|
331
|
+
// may be too short for them, a height or a maximum one
|
|
332
|
+
const spaced =
|
|
333
|
+
row &&
|
|
334
|
+
bounded &&
|
|
335
|
+
!hold.frozen &&
|
|
336
|
+
(height !== null || floor > 0 || ceiling < Infinity) &&
|
|
337
|
+
spacesLines(box.style);
|
|
338
|
+
const ask =
|
|
339
|
+
restretched.length > 0
|
|
340
|
+
? (): void => unstretch(items, restretched, sized, layout)
|
|
341
|
+
: spaced
|
|
342
|
+
? (): void =>
|
|
343
|
+
unspace(root, items, box.style.flexWrap === 'wrap-reverse', layout)
|
|
344
|
+
: layout;
|
|
345
|
+
// The one line of a box that does not wrap is shared out by CSS
|
|
346
|
+
// Flexbox 9.7 where Yoga's answer for it may not be what that makes of
|
|
347
|
+
// it (`resolveLine`): along a row of a width, or down a column of a
|
|
348
|
+
// height or of limits on one, where a line may have room to share out
|
|
349
|
+
const line: FlexLine | null =
|
|
350
|
+
hold.frozen &&
|
|
351
|
+
(row ? bounded : height !== null || floor > 0 || ceiling < Infinity)
|
|
352
|
+
? {
|
|
353
|
+
resolved: false,
|
|
354
|
+
size: row ? contentWidth : (height ?? NaN),
|
|
355
|
+
floor,
|
|
356
|
+
ceiling,
|
|
357
|
+
between: betweenOf(items, row, row ? columnGap : rowGap),
|
|
358
|
+
probe: row && contentWidth === MIN_CONTENT_PROBE,
|
|
359
|
+
}
|
|
360
|
+
: null;
|
|
361
|
+
// and the lines of a box that wraps may have room left over, where a
|
|
362
|
+
// row has a width to have it in: Yoga's answer for one every item of
|
|
363
|
+
// which that may grow its maximum stops is put right (`cappedLines`) —
|
|
364
|
+
// broken into lines as Yoga breaks them, at the box's size along them
|
|
365
|
+
const roomy = !hold.frozen && (row ? bounded : true);
|
|
366
|
+
const breaks = row ? contentWidth : (height ?? Infinity);
|
|
213
367
|
// each item's size along the main axis, read the once a layout (`Laid`)
|
|
214
368
|
const read = (): void => {
|
|
215
369
|
for (const { node, laid } of items) {
|
|
216
|
-
laid.main = row ? node.getComputedWidth() : node
|
|
370
|
+
laid.main = row ? node.getComputedWidth() : heightOf(node, laid);
|
|
217
371
|
}
|
|
218
372
|
};
|
|
219
373
|
const calculate = (): void => {
|
|
374
|
+
// a line shared out here is again, before Yoga places it: an item
|
|
375
|
+
// since held at its content's size is frozen there (`autoMinimums`)
|
|
376
|
+
if (line?.resolved) shareOut(items, row, line, hold);
|
|
220
377
|
ask();
|
|
221
378
|
read();
|
|
222
|
-
if (!
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
379
|
+
if (line && !line.resolved) {
|
|
380
|
+
const room = Number.isNaN(line.size)
|
|
381
|
+
? root.getComputedHeight()
|
|
382
|
+
: line.size;
|
|
383
|
+
if (resolveLine(items, row, line, room, hold, ask)) read();
|
|
384
|
+
} else if (roomy) {
|
|
385
|
+
const room = row ? contentWidth : (height ?? root.getComputedHeight());
|
|
386
|
+
const gap = row ? columnGap : rowGap;
|
|
387
|
+
if (cappedLines(items, row, room, breaks, gap, hold, ask)) read();
|
|
388
|
+
}
|
|
226
389
|
};
|
|
227
390
|
ctx.flexDepth = depth + 1;
|
|
228
391
|
try {
|
|
@@ -241,30 +404,74 @@ export function layoutFlex(
|
|
|
241
404
|
ctx.flexDepth = depth;
|
|
242
405
|
}
|
|
243
406
|
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
407
|
+
// The lines of a row that wraps are as tall as their tallest items, each
|
|
408
|
+
// as tall as its content across (CSS Flexbox 9.4, steps 7 and 8), and
|
|
409
|
+
// `align-content` stretches lines short of the box to it, or centres
|
|
410
|
+
// them, or ends them, past its edges where they are taller (step 9).
|
|
411
|
+
// Handed the box's height as a size, Yoga measures a stretched item at it
|
|
412
|
+
// wherever the row has room along it for every item, and so its line is
|
|
413
|
+
// the box's height: an item taller than the box was squashed to it, and a
|
|
414
|
+
// line `align-content` centres stayed at the box's top. Where that is so
|
|
415
|
+
// (`heldLine`), the box is held to its height from both sides instead,
|
|
416
|
+
// and laid out again: Yoga measures each item as at most that tall, which
|
|
417
|
+
// its content passes where it is taller — the maximum a percentage, as a
|
|
418
|
+
// minimum and a maximum that are the same are a size to Yoga. Only there,
|
|
419
|
+
// which takes in the one line `space-around` and `space-evenly` centre,
|
|
420
|
+
// and not where the row is measured for its widest, which is laid out
|
|
421
|
+
// for its width alone.
|
|
422
|
+
if (
|
|
423
|
+
row &&
|
|
424
|
+
bounded &&
|
|
425
|
+
height !== null &&
|
|
426
|
+
box.style.flexWrap !== 'nowrap' &&
|
|
427
|
+
heldLine(box, ctx, items, height, contentWidth, columnGap)
|
|
428
|
+
) {
|
|
429
|
+
root.setHeightAuto();
|
|
430
|
+
root.setMinHeight(height);
|
|
431
|
+
root.setMaxHeightPercent(100);
|
|
432
|
+
// and a percentage height resolved: Yoga takes one inside a box it
|
|
433
|
+
// does not hold to a size for none, and measured `h-1/2` as tall as
|
|
434
|
+
// its content
|
|
435
|
+
if (own) {
|
|
436
|
+
for (const { box: child, node } of items) {
|
|
437
|
+
const set = child.style.height;
|
|
438
|
+
if (!isPct(set)) continue;
|
|
439
|
+
const down =
|
|
440
|
+
child.style.boxSizing === 'border-box' ? 0 : child.verticalExtra;
|
|
441
|
+
node.setHeight(resolve(set, definite) + down);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
ctx.flexDepth = depth + 1;
|
|
445
|
+
try {
|
|
446
|
+
calculate();
|
|
447
|
+
} finally {
|
|
448
|
+
ctx.flexDepth = depth;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// whether Yoga's last layout took its pass for lines (`placeItems`)
|
|
453
|
+
let lined = box.style.flexWrap !== 'nowrap' || baselines;
|
|
249
454
|
let bottom = placeItems(
|
|
250
455
|
box,
|
|
251
456
|
ctx,
|
|
252
457
|
items,
|
|
253
458
|
row,
|
|
254
459
|
own,
|
|
255
|
-
|
|
460
|
+
lined,
|
|
256
461
|
contentWidth,
|
|
462
|
+
undefined,
|
|
463
|
+
crossed ? crossLines(box.style, items, contentWidth, columnGap) : undefined,
|
|
257
464
|
);
|
|
258
465
|
// Items aligned by their baselines, which Yoga cannot see, are aligned
|
|
259
466
|
// with their baselines known (`baselineLines`)
|
|
260
467
|
// by the flex layout again, set at their lines' starts with the margins
|
|
261
468
|
// that put their baselines together, which makes each line as tall as
|
|
262
469
|
// that does
|
|
263
|
-
const lines =
|
|
470
|
+
const lines = baselines ? baselineLines(box.style, items) : null;
|
|
471
|
+
const started = new Set<Box>();
|
|
264
472
|
if (lines) {
|
|
265
473
|
// every one of them, and the box's own `baseline` too, which is what
|
|
266
474
|
// takes Yoga down that pass
|
|
267
|
-
const started = new Set<Box>();
|
|
268
475
|
for (const line of lines) {
|
|
269
476
|
line.forEach(({ box: child, node }, i) => {
|
|
270
477
|
node.setAlignSelf(Y.ALIGN_FLEX_START);
|
|
@@ -275,6 +482,8 @@ export function layoutFlex(
|
|
|
275
482
|
if (box.style.alignItems === 'baseline') {
|
|
276
483
|
root.setAlignItems(Y.ALIGN_FLEX_START);
|
|
277
484
|
}
|
|
485
|
+
// so it takes it only where the row wraps
|
|
486
|
+
lined = box.style.flexWrap !== 'nowrap';
|
|
278
487
|
ctx.flexDepth = depth + 1;
|
|
279
488
|
try {
|
|
280
489
|
calculate();
|
|
@@ -287,7 +496,7 @@ export function layoutFlex(
|
|
|
287
496
|
items,
|
|
288
497
|
row,
|
|
289
498
|
own,
|
|
290
|
-
|
|
499
|
+
lined,
|
|
291
500
|
contentWidth,
|
|
292
501
|
started,
|
|
293
502
|
);
|
|
@@ -315,8 +524,9 @@ export function layoutFlex(
|
|
|
315
524
|
items,
|
|
316
525
|
row,
|
|
317
526
|
own,
|
|
318
|
-
|
|
527
|
+
lined,
|
|
319
528
|
contentWidth,
|
|
529
|
+
started,
|
|
320
530
|
);
|
|
321
531
|
}
|
|
322
532
|
|
|
@@ -348,6 +558,10 @@ function keepRatios(
|
|
|
348
558
|
box.verticalExtra;
|
|
349
559
|
if (Math.abs(basis - laid.main) < 0.5) continue;
|
|
350
560
|
node.setFlexBasis(basis);
|
|
561
|
+
// and is shared out from it where its line is here (`shareOut`)
|
|
562
|
+
laid.basis = basis;
|
|
563
|
+
laid.sized = false;
|
|
564
|
+
laid.base = basis;
|
|
351
565
|
// one held at its least size (`holdAt`) flexes from the new basis, no
|
|
352
566
|
// further down than that
|
|
353
567
|
if (!Number.isNaN(laid.held)) {
|
|
@@ -496,22 +710,38 @@ function placeItems(
|
|
|
496
710
|
/** The flex box's content width: its items' containing block's. */
|
|
497
711
|
contentWidth: number,
|
|
498
712
|
started?: ReadonlySet<Box>,
|
|
713
|
+
/** Where each item is across a column that wraps (`crossLines`). */
|
|
714
|
+
crossed?: ReadonlyMap<Box, Across>,
|
|
499
715
|
): number {
|
|
500
716
|
let bottom = 0;
|
|
717
|
+
// the lines of a row that wraps in reverse are turned round to run up it
|
|
718
|
+
const reverse = row && box.style.flexWrap === 'wrap-reverse';
|
|
501
719
|
for (const { box: child, node, laid } of items) {
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
720
|
+
const placed = crossed?.get(child);
|
|
721
|
+
const across =
|
|
722
|
+
lined && !placed
|
|
723
|
+
? lineMarginFix(box.style, child, node, row, own, started?.has(child))
|
|
724
|
+
: 0;
|
|
725
|
+
const itemHeight = row ? heightOf(node, laid) : laid.main;
|
|
726
|
+
const left =
|
|
727
|
+
box.contentX +
|
|
728
|
+
(placed ? placed.x : node.getComputedLeft() + (row ? 0 : across));
|
|
729
|
+
// which Yoga turns an item round in by the height it laid it out at, and
|
|
730
|
+
// not the one it is (`unspace`)
|
|
731
|
+
const top =
|
|
732
|
+
box.contentY +
|
|
733
|
+
node.getComputedTop() +
|
|
734
|
+
(row ? across : 0) +
|
|
735
|
+
(reverse ? node.getComputedHeight() - itemHeight : 0);
|
|
736
|
+
const width = placed
|
|
737
|
+
? placed.width
|
|
738
|
+
: meant(
|
|
739
|
+
row ? laid.main : node.getComputedWidth(),
|
|
740
|
+
laid.set,
|
|
741
|
+
laid.stretch,
|
|
742
|
+
laid.width,
|
|
743
|
+
row ? laid.held : NaN,
|
|
744
|
+
);
|
|
515
745
|
// The item is laid out again at the width the flex pass settled on: the
|
|
516
746
|
// measure function answered a question, and the answer is not a layout —
|
|
517
747
|
// its line breaks were computed against a width that may have changed
|
|
@@ -593,7 +823,8 @@ function layoutItemAt(
|
|
|
593
823
|
// one: the tallest card of a row is laid out the once. A table shares
|
|
594
824
|
// what it is given past its rows among them (`layoutTable`), and a
|
|
595
825
|
// height short of them is none it can have: it is laid out again where
|
|
596
|
-
// it was given more
|
|
826
|
+
// it was given more — or less than a height of its own, which it takes
|
|
827
|
+
// in place of that one, down to its rows.
|
|
597
828
|
const given = definite ?? (column ? height : null);
|
|
598
829
|
let shares = false;
|
|
599
830
|
if (given !== null && (box.kind === 'flex' || box.kind === 'table')) {
|
|
@@ -601,7 +832,8 @@ function layoutItemAt(
|
|
|
601
832
|
shares =
|
|
602
833
|
box.kind === 'flex'
|
|
603
834
|
? !(Math.abs(natural - given) <= 0.01)
|
|
604
|
-
: !(natural >= given - 0.01)
|
|
835
|
+
: !(natural >= given - 0.01) ||
|
|
836
|
+
(box.style.height !== AUTO && natural > given + 0.01);
|
|
605
837
|
}
|
|
606
838
|
if (given !== null) {
|
|
607
839
|
const inner = Math.max(0, given - box.verticalExtra);
|
|
@@ -624,7 +856,7 @@ function layoutItemAt(
|
|
|
624
856
|
const heights = definite !== null ? FLEXED_HEIGHT : USED_HEIGHT;
|
|
625
857
|
heights.set(box, tall);
|
|
626
858
|
try {
|
|
627
|
-
|
|
859
|
+
layoutAt(box, ctx, width, containing, laid);
|
|
628
860
|
} finally {
|
|
629
861
|
heights.delete(box);
|
|
630
862
|
}
|
|
@@ -666,7 +898,7 @@ function applyContainer(node: YogaNode, style: ComputedStyle): void {
|
|
|
666
898
|
if (justify !== Y.JUSTIFY_FLEX_START) node.setJustifyContent(justify);
|
|
667
899
|
const items = ALIGN[style.alignItems] ?? Y.ALIGN_STRETCH;
|
|
668
900
|
if (items !== Y.ALIGN_STRETCH) node.setAlignItems(items);
|
|
669
|
-
const lines = ALIGN[style
|
|
901
|
+
const lines = ALIGN[crossContent(style)] ?? Y.ALIGN_STRETCH;
|
|
670
902
|
if (lines !== Y.ALIGN_FLEX_START) node.setAlignContent(lines);
|
|
671
903
|
}
|
|
672
904
|
|
|
@@ -678,11 +910,16 @@ function applyItem(
|
|
|
678
910
|
laid: Laid,
|
|
679
911
|
/** Whether the flex box has a height its items are laid out in. */
|
|
680
912
|
tall: boolean,
|
|
681
|
-
/**
|
|
682
|
-
* column flexed the box to is not (`USED_HEIGHT`). */
|
|
683
|
-
|
|
913
|
+
/** That height where it is definite, and a percentage may be of it, and
|
|
914
|
+
* else NaN: one a column flexed the box to is not (`USED_HEIGHT`). */
|
|
915
|
+
definite: number,
|
|
916
|
+
/** Whether its line weighs it against another as it shrinks them, by
|
|
917
|
+
* their flex base sizes (`layoutFlex`). */
|
|
918
|
+
weighed: boolean,
|
|
684
919
|
): void {
|
|
685
920
|
const style = box.style;
|
|
921
|
+
const own = Number.isFinite(definite);
|
|
922
|
+
const row = box.parent?.style.flexDirection.startsWith('row') ?? true;
|
|
686
923
|
laid.stretch = containingWidth - box.marginLeft - box.marginRight;
|
|
687
924
|
// an `auto` margin takes the free space on its side, which is how
|
|
688
925
|
// `margin-left: auto` puts an item at the end of its row (CSS Flexbox
|
|
@@ -699,6 +936,14 @@ function applyItem(
|
|
|
699
936
|
if (style.flexShrink) node.setFlexShrink(style.flexShrink);
|
|
700
937
|
if (style.alignSelf !== AUTO)
|
|
701
938
|
node.setAlignSelf(ALIGN[style.alignSelf] ?? Y.ALIGN_AUTO);
|
|
939
|
+
// `stretch` sets an item whose height is a percentage of none at its
|
|
940
|
+
// line's start, as tall as its content (`heightOfNone`), where Yoga,
|
|
941
|
+
// which is handed no height for it, stretched it across the line
|
|
942
|
+
if (row && box.parent && heightOfNone(style, own)) {
|
|
943
|
+
const align =
|
|
944
|
+
style.alignSelf === AUTO ? box.parent.style.alignItems : style.alignSelf;
|
|
945
|
+
if (align === 'stretch') node.setAlignSelf(Y.ALIGN_FLEX_START);
|
|
946
|
+
}
|
|
702
947
|
|
|
703
948
|
// Yoga's sizes are border boxes, as `box-sizing: border-box` has them;
|
|
704
949
|
// a `content-box` length is the content's, and the item's padding and
|
|
@@ -708,15 +953,34 @@ function applyItem(
|
|
|
708
953
|
if (style.width !== AUTO) {
|
|
709
954
|
laid.set = setLength(node, true, style.width, containingWidth, across);
|
|
710
955
|
}
|
|
956
|
+
// Down a column, a basis of the content's — `content`, or a percentage
|
|
957
|
+
// of a height that is not definite (CSS Flexbox 7.2.3) — is its content's
|
|
958
|
+
// height, whatever height it has of its own (9.2.3, E), and Yoga takes
|
|
959
|
+
// the height of its own for a basis where it has one. So that is kept
|
|
960
|
+
// from Yoga, and the measure function answers with the content's.
|
|
961
|
+
const contentColumn =
|
|
962
|
+
!row && (style.flexBasis === 'content' || (isPct(style.flexBasis) && !own));
|
|
711
963
|
// a percentage of a height that is not definite is `auto` (CSS 2.1 10.5),
|
|
712
|
-
//
|
|
713
|
-
|
|
714
|
-
|
|
964
|
+
// and is not handed to Yoga, which makes one of the height of a flex box
|
|
965
|
+
// with none of its own but limits on one — and the height set on its
|
|
966
|
+
// node, where that is a length, and whether one is
|
|
967
|
+
let height = NaN;
|
|
968
|
+
let heightSet = false;
|
|
969
|
+
if (
|
|
970
|
+
style.height !== AUTO &&
|
|
971
|
+
(own || !isPct(style.height)) &&
|
|
972
|
+
!contentColumn
|
|
973
|
+
) {
|
|
974
|
+
height = setLength(node, false, style.height, definite, down);
|
|
975
|
+
heightSet = true;
|
|
715
976
|
}
|
|
716
977
|
// the least it may be along each axis, its border box's, as Yoga is
|
|
717
978
|
// told it
|
|
718
979
|
let leastWidth = NaN;
|
|
719
980
|
let leastHeight = NaN;
|
|
981
|
+
// and the most, as Yoga is told it
|
|
982
|
+
let mostWidth = NaN;
|
|
983
|
+
let mostHeight = NaN;
|
|
720
984
|
const minWidth = resolveOrNull(style.minWidth, containingWidth);
|
|
721
985
|
if (minWidth !== null) {
|
|
722
986
|
leastWidth = minWidth + across;
|
|
@@ -724,7 +988,10 @@ function applyItem(
|
|
|
724
988
|
}
|
|
725
989
|
if (style.maxWidth !== 'none') {
|
|
726
990
|
const maxWidth = resolveOrNull(style.maxWidth, containingWidth);
|
|
727
|
-
if (maxWidth !== null)
|
|
991
|
+
if (maxWidth !== null) {
|
|
992
|
+
mostWidth = maxWidth + across;
|
|
993
|
+
node.setMaxWidth(mostWidth);
|
|
994
|
+
}
|
|
728
995
|
}
|
|
729
996
|
if (style.widthKeyword || style.minWidthKeyword || style.maxWidthKeyword) {
|
|
730
997
|
// an intrinsic size is the item's content's, measured here and handed
|
|
@@ -744,29 +1011,87 @@ function applyItem(
|
|
|
744
1011
|
leastWidth = size(style.minWidthKeyword);
|
|
745
1012
|
node.setMinWidth(leastWidth);
|
|
746
1013
|
}
|
|
747
|
-
if (style.maxWidthKeyword)
|
|
1014
|
+
if (style.maxWidthKeyword) {
|
|
1015
|
+
mostWidth = size(style.maxWidthKeyword);
|
|
1016
|
+
node.setMaxWidth(mostWidth);
|
|
1017
|
+
}
|
|
748
1018
|
}
|
|
749
|
-
|
|
1019
|
+
// a percentage of a definite height, which a limit on a flex item's
|
|
1020
|
+
// height is where its flex box has one
|
|
1021
|
+
const minHeight = resolveOrNull(style.minHeight, definite);
|
|
750
1022
|
if (minHeight !== null) {
|
|
751
1023
|
leastHeight = minHeight + down;
|
|
752
1024
|
node.setMinHeight(leastHeight);
|
|
753
1025
|
}
|
|
754
1026
|
if (style.maxHeight !== 'none') {
|
|
755
|
-
const maxHeight = resolveOrNull(style.maxHeight,
|
|
756
|
-
if (maxHeight !== null)
|
|
1027
|
+
const maxHeight = resolveOrNull(style.maxHeight, definite);
|
|
1028
|
+
if (maxHeight !== null) {
|
|
1029
|
+
mostHeight = maxHeight + down;
|
|
1030
|
+
node.setMaxHeight(mostHeight);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// a `border-box` maximum under the item's padding and borders is them,
|
|
1035
|
+
// its content box none wide (CSS Sizing 3, 3.3), where Yoga lays the
|
|
1036
|
+
// item out as wide as them and shares out its line as though it were
|
|
1037
|
+
// as wide as the maximum: the `auto` margins after `max-width:
|
|
1038
|
+
// 20.668px` with 23px of padding and border took the 2.33px between
|
|
1039
|
+
if (mostWidth < box.horizontalExtra) {
|
|
1040
|
+
mostWidth = box.horizontalExtra;
|
|
1041
|
+
node.setMaxWidth(mostWidth);
|
|
1042
|
+
}
|
|
1043
|
+
if (mostHeight < box.verticalExtra) {
|
|
1044
|
+
mostHeight = box.verticalExtra;
|
|
1045
|
+
node.setMaxHeight(mostHeight);
|
|
1046
|
+
}
|
|
1047
|
+
// a maximum under the minimum is the minimum (CSS 2.1 10.4, 10.7), where
|
|
1048
|
+
// Yoga holds an item that comes to more than both at the maximum
|
|
1049
|
+
if (mostWidth < leastWidth) {
|
|
1050
|
+
mostWidth = leastWidth;
|
|
1051
|
+
node.setMaxWidth(mostWidth);
|
|
1052
|
+
}
|
|
1053
|
+
if (mostHeight < leastHeight) {
|
|
1054
|
+
mostHeight = leastHeight;
|
|
1055
|
+
node.setMaxHeight(mostHeight);
|
|
757
1056
|
}
|
|
758
1057
|
|
|
759
|
-
const row = box.parent?.style.flexDirection.startsWith('row') ?? true;
|
|
760
1058
|
laid.least = row ? leastWidth : leastHeight;
|
|
1059
|
+
laid.most = row ? mostWidth : mostHeight;
|
|
1060
|
+
laid.narrowest = leastWidth;
|
|
1061
|
+
// A table is as wide as the flex layout makes it, as any item is: the
|
|
1062
|
+
// size its line flexed it to along a row (9.7), and across a column's
|
|
1063
|
+
// line the line's, stretched (9.4, step 11) — where in a block's flow it
|
|
1064
|
+
// is as wide as its columns. Laid out at that width and then shrunk to
|
|
1065
|
+
// them, it was a table its columns wide at the start of a column, and one
|
|
1066
|
+
// `flex: 1` along a row stopped at them where Chrome's fills its share
|
|
1067
|
+
laid.across =
|
|
1068
|
+
box.kind === 'table' &&
|
|
1069
|
+
(row ||
|
|
1070
|
+
(box.parent !== null &&
|
|
1071
|
+
!style.widthKeyword &&
|
|
1072
|
+
stretches(box, box.parent.style, false)));
|
|
761
1073
|
// Yoga takes a length for a basis only where the flex box's main size is
|
|
762
1074
|
// definite, and else the item's own size along it: down a column of no
|
|
763
1075
|
// height of its own it read `flex: 0 0 3rem` as the item's height, or
|
|
764
1076
|
// its content's. There the basis is handed over as that height, which is
|
|
765
1077
|
// what the item's own is to a basis anyway — no more than its start
|
|
766
1078
|
const setBasis = (px: number): void => {
|
|
1079
|
+
laid.basis = px;
|
|
767
1080
|
if (row || tall) node.setFlexBasis(px);
|
|
768
1081
|
else node.setHeight(px);
|
|
769
1082
|
};
|
|
1083
|
+
// Where Yoga takes the item's own size along the main axis for its
|
|
1084
|
+
// basis, that size as a length, or its content's (`Laid.sized`) — and
|
|
1085
|
+
// its content's, whatever size it has, for a basis that says so, which
|
|
1086
|
+
// Yoga reads as its size down a column
|
|
1087
|
+
const ownSize = (content = false): void => {
|
|
1088
|
+
if (
|
|
1089
|
+
content ||
|
|
1090
|
+
(row ? style.width === AUTO && !style.widthKeyword : !heightSet)
|
|
1091
|
+
) {
|
|
1092
|
+
laid.sized = true;
|
|
1093
|
+
} else laid.basis = row ? laid.set : height;
|
|
1094
|
+
};
|
|
770
1095
|
// down a column, an item with a ratio and a width is as tall as that
|
|
771
1096
|
// makes it, for a basis of its content or of its own `auto` height (CSS
|
|
772
1097
|
// Flexbox 9.2.3, B) — where Yoga measured it, and its block layout held
|
|
@@ -782,24 +1107,36 @@ function applyItem(
|
|
|
782
1107
|
// the content's size along a row, whatever the item's own width says
|
|
783
1108
|
// (CSS Flexbox 7.2.3), which Yoga reads as `auto` and takes the width
|
|
784
1109
|
// for; along a column, Yoga's measure is the content's
|
|
785
|
-
|
|
1110
|
+
laid.basis = contentBasis(box, ctx, containingWidth);
|
|
1111
|
+
node.setFlexBasis(laid.basis);
|
|
786
1112
|
} else if (columnBasis !== null) {
|
|
787
1113
|
// but a height of its own hides it from Yoga, and an item with a ratio
|
|
788
1114
|
// and a width is as tall as that makes it
|
|
789
1115
|
setBasis(columnBasis);
|
|
790
1116
|
} else if (style.flexBasis === 'content' || style.flexBasis === AUTO) {
|
|
791
1117
|
// Yoga's own
|
|
792
|
-
|
|
1118
|
+
ownSize(style.flexBasis === 'content');
|
|
1119
|
+
} else if (isPct(style.flexBasis) && !row && !own) {
|
|
793
1120
|
// a percentage of a main size that is not definite is `content` (CSS
|
|
794
1121
|
// Flexbox 7.2.3): Yoga's own, down a column
|
|
1122
|
+
ownSize(true);
|
|
795
1123
|
} else if (isPct(style.flexBasis)) {
|
|
796
|
-
// a percentage of the main size, which is known across a row
|
|
1124
|
+
// a percentage of the main size, which is known across a row and down
|
|
1125
|
+
// a column of a definite height: of a content box's size, as a width
|
|
1126
|
+
// is, where the item's padding and border go on top of it
|
|
797
1127
|
const basis = style.flexBasis;
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
1128
|
+
const extra = row ? across : down;
|
|
1129
|
+
const known = row ? Number.isFinite(containingWidth) : own;
|
|
1130
|
+
if (known) {
|
|
1131
|
+
laid.basis = resolve(basis, row ? containingWidth : definite) + extra;
|
|
1132
|
+
}
|
|
1133
|
+
if (!basis.px && !basis.of && !(known && extra)) {
|
|
1134
|
+
node.setFlexBasisPercent(basis.pct);
|
|
1135
|
+
} else if (known) node.setFlexBasis(laid.basis);
|
|
1136
|
+
else if (basis.of) {
|
|
1137
|
+
node.setFlexBasisAuto();
|
|
1138
|
+
ownSize();
|
|
1139
|
+
} else node.setFlexBasisPercent(basis.pct);
|
|
803
1140
|
} else setBasis(style.flexBasis + (row ? across : down));
|
|
804
1141
|
|
|
805
1142
|
// The item's padding and border belong to Yoga so it can size the item,
|
|
@@ -821,8 +1158,14 @@ function applyItem(
|
|
|
821
1158
|
// table cell's is: taken per layout of the container, a flex box in a
|
|
822
1159
|
// flex box in a flex box laid its innermost out three times a level,
|
|
823
1160
|
// and twelve levels took two seconds.
|
|
824
|
-
const alongRow =
|
|
1161
|
+
const alongRow = row;
|
|
825
1162
|
const content = (): number => {
|
|
1163
|
+
const width = maxContent();
|
|
1164
|
+
// its flex base size, where that is its content's (`Laid.sized`)
|
|
1165
|
+
if (alongRow) laid.content = width + box.horizontalExtra;
|
|
1166
|
+
return width;
|
|
1167
|
+
};
|
|
1168
|
+
function maxContent(): number {
|
|
826
1169
|
// a replaced element's is the width it has with no limit — its
|
|
827
1170
|
// natural one, or what its ratio makes of a height of its own — which
|
|
828
1171
|
// its image, loading, may change, so it is not kept; taken as its
|
|
@@ -842,12 +1185,17 @@ function applyItem(
|
|
|
842
1185
|
if (width === undefined) {
|
|
843
1186
|
width = measureIntrinsicWidth(box, ctx, Infinity) - box.horizontalExtra;
|
|
844
1187
|
MAX_CONTENT.set(box, width);
|
|
1188
|
+
// measured, it is laid out with no limit on its width, and not at
|
|
1189
|
+
// the one it was last laid out at, which Yoga may give it again: an
|
|
1190
|
+
// item `min-width: min-content` that Yoga asked at its minimum and
|
|
1191
|
+
// then with no limit was left laid out infinitely wide
|
|
1192
|
+
laid.width = NaN;
|
|
845
1193
|
}
|
|
846
1194
|
// within what its least and greatest heights make of widths through
|
|
847
1195
|
// its ratio: its size before it is flexed, and not after
|
|
848
1196
|
const extra = box.horizontalExtra;
|
|
849
1197
|
return Math.max(0, transferredWidth(box, width + extra) - extra);
|
|
850
|
-
}
|
|
1198
|
+
}
|
|
851
1199
|
// An item with a ratio is as wide, across a column, as the height it
|
|
852
1200
|
// was given makes it through the ratio, where its width is its own to
|
|
853
1201
|
// find (CSS Flexbox 9.4, its hypothetical cross size from its used main
|
|
@@ -884,27 +1232,114 @@ function applyItem(
|
|
|
884
1232
|
const flexed = box.kind === 'replaced' || !laid.auto;
|
|
885
1233
|
const through =
|
|
886
1234
|
widthThrough && given && !exact && flexed ? widthThrough(h) : null;
|
|
887
|
-
//
|
|
888
|
-
//
|
|
889
|
-
//
|
|
890
|
-
|
|
1235
|
+
// Along a row, an item's flex base size is its content at its widest,
|
|
1236
|
+
// whatever the room (CSS Flexbox 9.2.3 E, `content` as `max-content`),
|
|
1237
|
+
// which Yoga asks for as at most the row's width. One that may not
|
|
1238
|
+
// shrink is as wide as that (9.7): fitted to the room, a `shrink-0`
|
|
1239
|
+
// item of words was held to its content at its narrowest, and wrapped
|
|
1240
|
+
// where it overflows the row. And one that may shrink gives up room in
|
|
1241
|
+
// proportion to it, where its line weighs it against another (9.7,
|
|
1242
|
+
// step 4c, its flex shrink factor times its base): fitted, an item of
|
|
1243
|
+
// words wider than the row was weighed as no wider than the row, and
|
|
1244
|
+
// gave up too little of it beside a narrower one — 230 and 70 of a row
|
|
1245
|
+
// of 300, where Chrome has 245 and 55. One that shrinks alone is
|
|
1246
|
+
// fitted, which comes to the same and keeps the layout it is measured
|
|
1247
|
+
// at for the one it is given: at its widest, a paragraph in a row laid
|
|
1248
|
+
// itself out twice.
|
|
1249
|
+
// And every item of a row laid out in no room is fitted, which is the
|
|
1250
|
+
// flex box measured for its content at its narrowest
|
|
1251
|
+
// (`MIN_CONTENT_PROBE`): that is the sum of its items' content at their
|
|
1252
|
+
// narrowest, whatever they may shrink by, as Chrome and the css-flexbox
|
|
1253
|
+
// suite have it (`intrinsic-size/row-001`, `gap-015`), and not their
|
|
1254
|
+
// bases.
|
|
1255
|
+
// Across a column, the item is `fit-content` wide where it is not
|
|
1256
|
+
// stretched (9.4, step 7): fitted to the column, but no narrower than
|
|
1257
|
+
// its content at its narrowest, past the column where that is wider,
|
|
1258
|
+
// as a centred one is on both sides. Its `min-width` has no say in
|
|
1259
|
+
// that: an automatic minimum is the main axis's (4.5), so `auto` is 0
|
|
1260
|
+
// across a column, the same as the `min-width: 0` that lets an item
|
|
1261
|
+
// shrink along a row. Fitted to the column, a word wider than it ran
|
|
1262
|
+
// out of the item, and a centred item started at the column's start.
|
|
1263
|
+
// A replaced element's narrowest is its natural width, or one through
|
|
1264
|
+
// its ratio, and none where it has neither: an SVG with only a
|
|
1265
|
+
// `viewBox` is as wide as the column. And an item with something in
|
|
1266
|
+
// it that takes a percentage of a height is fitted to the column still:
|
|
1267
|
+
// the flex layout makes its height definite only after this, and
|
|
1268
|
+
// measured without it, an image `height: 100%` is its natural width.
|
|
1269
|
+
const widest =
|
|
1270
|
+
alongRow &&
|
|
1271
|
+
!exact &&
|
|
1272
|
+
(box.kind === 'replaced' ||
|
|
1273
|
+
(containingWidth !== MIN_CONTENT_PROBE &&
|
|
1274
|
+
(style.flexShrink === 0 || weighed)));
|
|
1275
|
+
const narrowest =
|
|
1276
|
+
alongRow || percentHeightsIn(box)
|
|
1277
|
+
? undefined
|
|
1278
|
+
: box.kind === 'replaced'
|
|
1279
|
+
? () => (replacedMinimum(box, containingWidth) ?? extra) - extra
|
|
1280
|
+
: () => minContentOf(box, ctx, laid) - extra;
|
|
1281
|
+
const fitted =
|
|
891
1282
|
through !== null
|
|
892
1283
|
? through
|
|
893
|
-
:
|
|
1284
|
+
: widest
|
|
894
1285
|
? content()
|
|
895
|
-
: innerWidth(width, wm, content);
|
|
1286
|
+
: innerWidth(width, wm, content, narrowest);
|
|
1287
|
+
// Across a column, no wider than its maximum: Yoga hands the measure
|
|
1288
|
+
// a room within it, but holds the answer's width to it only after,
|
|
1289
|
+
// and keeps the height measured at the wider — an image `max-width:
|
|
1290
|
+
// 100%` its natural width at its narrowest was as tall as that made it
|
|
1291
|
+
const capped =
|
|
1292
|
+
!alongRow && fitted > mostWidth - extra ? mostWidth - extra : fitted;
|
|
1293
|
+
// And no narrower than its minimum: Yoga holds the item to that only
|
|
1294
|
+
// after it has the answer, which it keeps for the width that makes of
|
|
1295
|
+
// it, so an item measured at the room and held to a wider minimum was
|
|
1296
|
+
// as tall as its content wrapped at the room — `min-w-max` in a narrow
|
|
1297
|
+
// row, or down a column narrower than it, one line of words two lines
|
|
1298
|
+
// tall, and an item held to its longest word in a row that wraps
|
|
1299
|
+
// (`holdAt`) a line taller than that makes it. Stretched across a
|
|
1300
|
+
// column, it is asked at the column's width.
|
|
1301
|
+
const inner = Number.isNaN(laid.narrowest)
|
|
1302
|
+
? capped
|
|
1303
|
+
: Math.max(capped, laid.narrowest - extra);
|
|
1304
|
+
let answer: { width: number; height: number } | undefined;
|
|
896
1305
|
if (
|
|
897
1306
|
box.kind === 'replaced' &&
|
|
898
1307
|
exact &&
|
|
899
1308
|
ratio > 0 &&
|
|
900
1309
|
style.height === AUTO
|
|
901
1310
|
) {
|
|
902
|
-
|
|
1311
|
+
answer = { width: inner, height: inner / ratio };
|
|
1312
|
+
} else {
|
|
1313
|
+
answer = answers.get(inner);
|
|
1314
|
+
if (answer === undefined) {
|
|
1315
|
+
answer = measureBox(box, ctx, inner, containingWidth, laid);
|
|
1316
|
+
answers.set(inner, answer);
|
|
1317
|
+
}
|
|
903
1318
|
}
|
|
904
|
-
|
|
905
|
-
if (
|
|
906
|
-
|
|
907
|
-
|
|
1319
|
+
// along a row, the width it answered where it might choose one
|
|
1320
|
+
if (alongRow && wm !== Y.MEASURE_MODE_EXACTLY) {
|
|
1321
|
+
laid.fitted = answer.width + box.horizontalExtra;
|
|
1322
|
+
}
|
|
1323
|
+
// down a column, its content's height where nothing held it to one:
|
|
1324
|
+
// its flex base size, where that is its content's (`Laid.sized`) — for
|
|
1325
|
+
// a basis that says so, what a height of its own has no say in
|
|
1326
|
+
if (!alongRow && !given) {
|
|
1327
|
+
if (contentColumn && style.height !== AUTO && box.kind !== 'replaced') {
|
|
1328
|
+
const width = inner + box.horizontalExtra;
|
|
1329
|
+
const kept = keptNatural(box, ctx, width, containingWidth);
|
|
1330
|
+
let content = kept?.content ?? NaN;
|
|
1331
|
+
if (Number.isNaN(content)) {
|
|
1332
|
+
if (kept === null || kept.serial !== box.layoutSerial) {
|
|
1333
|
+
layoutNaturally(box, ctx, width, containingWidth, laid);
|
|
1334
|
+
}
|
|
1335
|
+
content = columnContent(box, ctx, laid, width, containingWidth);
|
|
1336
|
+
}
|
|
1337
|
+
answer = {
|
|
1338
|
+
width: answer.width,
|
|
1339
|
+
height: Math.max(0, content - box.verticalExtra),
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
laid.content = answer.height + box.verticalExtra;
|
|
908
1343
|
}
|
|
909
1344
|
return answer;
|
|
910
1345
|
});
|
|
@@ -936,12 +1371,51 @@ interface Laid {
|
|
|
936
1371
|
/** The least it may be along the main axis, where its own style says:
|
|
937
1372
|
* the minimum this engine set on its node, or NaN. */
|
|
938
1373
|
least: number;
|
|
1374
|
+
/** The most it may be along the main axis, where its own style says: the
|
|
1375
|
+
* maximum this engine set on its node, or NaN. */
|
|
1376
|
+
most: number;
|
|
1377
|
+
/** Its flex base size (CSS Flexbox 9.2, step 3), its border box's, where
|
|
1378
|
+
* its style says what that is — a basis or a size along the main axis
|
|
1379
|
+
* that is a length — or NaN where its content does (`content`) or Yoga
|
|
1380
|
+
* resolves it. */
|
|
1381
|
+
basis: number;
|
|
1382
|
+
/** Whether its flex base size is its content's size along the main axis,
|
|
1383
|
+
* which the measure function notes (`content`). */
|
|
1384
|
+
sized: boolean;
|
|
1385
|
+
/** Its content's size along the main axis, its border box's, as the
|
|
1386
|
+
* measure function last found it with nothing holding it to a size
|
|
1387
|
+
* there: its max-content width along a row, its height at its width
|
|
1388
|
+
* down a column. NaN until measured so. */
|
|
1389
|
+
content: number;
|
|
1390
|
+
/** The border-box width the measure function last answered along a row
|
|
1391
|
+
* where Yoga asked for one it might choose, its content's fitted to the
|
|
1392
|
+
* room: the flex base size Yoga takes for it (`FlexLine.probe`). */
|
|
1393
|
+
fitted: number;
|
|
1394
|
+
/** The flex base size its line was shared out from where this engine
|
|
1395
|
+
* shared it (`resolveLine`), or NaN. */
|
|
1396
|
+
base: number;
|
|
1397
|
+
/** The least border-box width its node was given, along a row or across
|
|
1398
|
+
* a column: its own minimum, or along a row the automatic one it is
|
|
1399
|
+
* held to (`holdAt`), or NaN. It is measured at no narrower. */
|
|
1400
|
+
narrowest: number;
|
|
939
1401
|
/** The size along the main axis it is frozen at, inflexible (`freezeAt`),
|
|
940
1402
|
* or NaN. */
|
|
941
1403
|
held: number;
|
|
1404
|
+
/** Its automatic minimum along the main axis (CSS Flexbox 4.5), where it
|
|
1405
|
+
* is held to it (`holdAt`), or NaN. */
|
|
1406
|
+
minimum: number;
|
|
942
1407
|
/** Whether that is its automatic minimum (`holdAt`), its content's size,
|
|
943
|
-
* and not a
|
|
1408
|
+
* and not a size its line was shared out to (`resolveLine`). */
|
|
944
1409
|
auto: boolean;
|
|
1410
|
+
/** Whether it is a table the flex layout gives its width — flexed along a
|
|
1411
|
+
* row, stretched across a column's line — which it is laid out at as its
|
|
1412
|
+
* own (`STRETCHED_ACROSS`, `layoutAt`). */
|
|
1413
|
+
across: boolean;
|
|
1414
|
+
/** Its border box's height as Yoga sized it before it laid it out again
|
|
1415
|
+
* across its line, at another: down a column that wraps (`unstretch`),
|
|
1416
|
+
* or across a row whose lines are spaced out (`unspace`). NaN where that
|
|
1417
|
+
* is the height Yoga has for it. */
|
|
1418
|
+
unstretched: number;
|
|
945
1419
|
}
|
|
946
1420
|
|
|
947
1421
|
/**
|
|
@@ -1080,7 +1554,7 @@ function layoutNaturally(
|
|
|
1080
1554
|
box.height = kept.height;
|
|
1081
1555
|
} else {
|
|
1082
1556
|
const at = ctx.positioned.length;
|
|
1083
|
-
|
|
1557
|
+
layoutAt(box, ctx, width, containing, laid);
|
|
1084
1558
|
// a replaced box is sized, not laid out, and has no layout to count
|
|
1085
1559
|
if (box.kind !== 'replaced') {
|
|
1086
1560
|
NATURAL.set(box, {
|
|
@@ -1103,6 +1577,33 @@ function layoutNaturally(
|
|
|
1103
1577
|
laid.tall = NaN;
|
|
1104
1578
|
}
|
|
1105
1579
|
|
|
1580
|
+
/**
|
|
1581
|
+
* Lay an item out at a border-box width: a table the flex layout gives its
|
|
1582
|
+
* width at that width as its own (`Laid.across`), for this layout alone. A
|
|
1583
|
+
* width Yoga asks its content at is one the table comes to by its columns
|
|
1584
|
+
* all the same — its widest, or the room where that is less — so that is
|
|
1585
|
+
* no matter; an unbounded one, a probe of a box around the flex box, is
|
|
1586
|
+
* the table's to shrink in.
|
|
1587
|
+
*/
|
|
1588
|
+
function layoutAt(
|
|
1589
|
+
box: Box,
|
|
1590
|
+
ctx: LayoutContext,
|
|
1591
|
+
width: number,
|
|
1592
|
+
containing: number,
|
|
1593
|
+
laid: Laid,
|
|
1594
|
+
): void {
|
|
1595
|
+
if (!laid.across || !Number.isFinite(width)) {
|
|
1596
|
+
ctx.layoutSubtree(box, width, containing);
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
STRETCHED_ACROSS.add(box);
|
|
1600
|
+
try {
|
|
1601
|
+
ctx.layoutSubtree(box, width, containing);
|
|
1602
|
+
} finally {
|
|
1603
|
+
STRETCHED_ACROSS.delete(box);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1106
1607
|
/**
|
|
1107
1608
|
* The layout this pass made of an item at its content's own height
|
|
1108
1609
|
* (`layoutNaturally`), where one at `width` now would be the same: the same
|
|
@@ -1246,11 +1747,13 @@ function readsPercentBase(box: Box): boolean {
|
|
|
1246
1747
|
|
|
1247
1748
|
/** The width an item's content is measured at: the width Yoga gives it
|
|
1248
1749
|
* exactly, or its max-content width, no wider than any it may take up to
|
|
1249
|
-
* (CSS Flexbox 9.2)
|
|
1750
|
+
* (CSS Flexbox 9.2) — and, given its min-content width, no narrower than
|
|
1751
|
+
* that either. */
|
|
1250
1752
|
function innerWidth(
|
|
1251
1753
|
width: number,
|
|
1252
1754
|
mode: number,
|
|
1253
1755
|
maxContent: () => number,
|
|
1756
|
+
minContent?: () => number,
|
|
1254
1757
|
): number {
|
|
1255
1758
|
if (mode === Y.MEASURE_MODE_EXACTLY && Number.isFinite(width)) {
|
|
1256
1759
|
return Math.max(0, width);
|
|
@@ -1260,9 +1763,9 @@ function innerWidth(
|
|
|
1260
1763
|
return content;
|
|
1261
1764
|
}
|
|
1262
1765
|
// a room exactly as wide as the content, as Yoga holds it, is room for it
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1766
|
+
if (content <= width || nearly(content, width)) return content;
|
|
1767
|
+
const room = Math.max(0, width);
|
|
1768
|
+
return minContent ? Math.min(content, Math.max(room, minContent())) : room;
|
|
1266
1769
|
}
|
|
1267
1770
|
|
|
1268
1771
|
/**
|
|
@@ -1290,12 +1793,6 @@ function autoMinimums(
|
|
|
1290
1793
|
for (const { box, node, laid } of items) {
|
|
1291
1794
|
if (box.kind === 'text' || box.kind === 'break') continue;
|
|
1292
1795
|
const style = box.style;
|
|
1293
|
-
// a box that scrolls or clips has none, but for one that asks for its
|
|
1294
|
-
// content's height
|
|
1295
|
-
const asked = !row && style.minHeightKeyword !== null;
|
|
1296
|
-
if (!asked && scrolls(style)) {
|
|
1297
|
-
continue;
|
|
1298
|
-
}
|
|
1299
1796
|
const width = meant(
|
|
1300
1797
|
row ? laid.main : node.getComputedWidth(),
|
|
1301
1798
|
laid.set,
|
|
@@ -1303,6 +1800,39 @@ function autoMinimums(
|
|
|
1303
1800
|
laid.width,
|
|
1304
1801
|
row ? laid.held : NaN,
|
|
1305
1802
|
);
|
|
1803
|
+
// A table is no smaller than it can be along either axis, whatever its
|
|
1804
|
+
// own least says, `min-width: 0` and `min-height: 0` among it: its
|
|
1805
|
+
// columns at their narrowest along a row, and down a column its rows
|
|
1806
|
+
// and captions at the width it has, however short a height of its own
|
|
1807
|
+
// is (CSS 2.1 17.5.2 and 17.5.3; Blink, `length_utils.cc`: "Tables
|
|
1808
|
+
// can't shrink below their min-intrinsic size"). The line is laid out
|
|
1809
|
+
// with it so, and the items after it start where it ends: Yoga had it
|
|
1810
|
+
// as small as its line, and the next item over the rest of it — read
|
|
1811
|
+
// as a block's content, two pixels into it, the spacing under its rows
|
|
1812
|
+
if (box.kind === 'table') {
|
|
1813
|
+
// as wide as its columns at their widest, where Yoga measured that,
|
|
1814
|
+
// it has room for them at their narrowest, which is a layout less
|
|
1815
|
+
const widest = row ? MAX_CONTENT.get(box) : undefined;
|
|
1816
|
+
if (
|
|
1817
|
+
widest !== undefined &&
|
|
1818
|
+
laid.main >= widest + box.horizontalExtra - 0.01
|
|
1819
|
+
) {
|
|
1820
|
+
continue;
|
|
1821
|
+
}
|
|
1822
|
+
const least = row
|
|
1823
|
+
? minContentOf(box, ctx, laid)
|
|
1824
|
+
: tableContent(box, ctx, width, containingWidth, laid);
|
|
1825
|
+
if (laid.main >= least - 0.01) continue;
|
|
1826
|
+
holdAt(node, laid, row, least, hold);
|
|
1827
|
+
changed = true;
|
|
1828
|
+
continue;
|
|
1829
|
+
}
|
|
1830
|
+
// a box that scrolls or clips has none, but for one that asks for its
|
|
1831
|
+
// content's height
|
|
1832
|
+
const asked = !row && style.minHeightKeyword !== null;
|
|
1833
|
+
if (!asked && scrolls(style)) {
|
|
1834
|
+
continue;
|
|
1835
|
+
}
|
|
1306
1836
|
// a replaced item's content along a row is its natural width
|
|
1307
1837
|
if (row && box.kind === 'replaced') {
|
|
1308
1838
|
if (style.minWidth !== AUTO || style.minWidthKeyword) continue;
|
|
@@ -1423,26 +1953,9 @@ function autoMinimums(
|
|
|
1423
1953
|
} else {
|
|
1424
1954
|
// its height, or where it has one of its own and its content comes to
|
|
1425
1955
|
// less, its content's: the lesser of the two (4.5)
|
|
1426
|
-
let content = read
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
} else if (box.kind === 'replaced') content = laid.height;
|
|
1430
|
-
else if (style.height !== AUTO && percentHeightsIn(box)) {
|
|
1431
|
-
// what its content comes to where it has no height to take
|
|
1432
|
-
// percentages of, as an intrinsic size is measured: laid out so
|
|
1433
|
-
// apart, and the final pass lays it out again
|
|
1434
|
-
FLEXED_HEIGHT.set(box, NaN);
|
|
1435
|
-
try {
|
|
1436
|
-
ctx.layoutSubtree(box, width, containingWidth);
|
|
1437
|
-
} finally {
|
|
1438
|
-
FLEXED_HEIGHT.delete(box);
|
|
1439
|
-
}
|
|
1440
|
-
content = ratioContent(box, contentBottom(box) + box.verticalExtra);
|
|
1441
|
-
laid.width = NaN;
|
|
1442
|
-
} else {
|
|
1443
|
-
content = ratioContent(box, contentBottom(box) + box.verticalExtra);
|
|
1444
|
-
remember(box, ctx, width, containingWidth, 'content', content);
|
|
1445
|
-
}
|
|
1956
|
+
let content = Number.isNaN(read)
|
|
1957
|
+
? columnContent(box, ctx, laid, width, containingWidth)
|
|
1958
|
+
: read;
|
|
1446
1959
|
// within what the ratio makes of its least and greatest widths
|
|
1447
1960
|
if (box.kind !== 'replaced') {
|
|
1448
1961
|
content = transferredHeight(box, content, containingWidth);
|
|
@@ -1461,6 +1974,58 @@ function autoMinimums(
|
|
|
1461
1974
|
return changed;
|
|
1462
1975
|
}
|
|
1463
1976
|
|
|
1977
|
+
/**
|
|
1978
|
+
* How far down a column item's content comes at a border-box width, its
|
|
1979
|
+
* border box's, from the layout its content's own height makes of it
|
|
1980
|
+
* there (`layoutNaturally`), which it has: what a height of its own has no
|
|
1981
|
+
* say in, which its automatic minimum reads (CSS Flexbox 4.5), and a flex
|
|
1982
|
+
* basis of `content` (9.2.3, E).
|
|
1983
|
+
*/
|
|
1984
|
+
function columnContent(
|
|
1985
|
+
box: Box,
|
|
1986
|
+
ctx: LayoutContext,
|
|
1987
|
+
laid: Laid,
|
|
1988
|
+
width: number,
|
|
1989
|
+
containingWidth: number,
|
|
1990
|
+
): number {
|
|
1991
|
+
if (box.kind === 'replaced') return laid.height;
|
|
1992
|
+
if (box.style.height !== AUTO && percentHeightsIn(box)) {
|
|
1993
|
+
// what its content comes to where it has no height to take
|
|
1994
|
+
// percentages of, as an intrinsic size is measured: laid out so
|
|
1995
|
+
// apart, and the final pass lays it out again
|
|
1996
|
+
FLEXED_HEIGHT.set(box, NaN);
|
|
1997
|
+
try {
|
|
1998
|
+
ctx.layoutSubtree(box, width, containingWidth);
|
|
1999
|
+
} finally {
|
|
2000
|
+
FLEXED_HEIGHT.delete(box);
|
|
2001
|
+
}
|
|
2002
|
+
laid.width = NaN;
|
|
2003
|
+
return ratioContent(box, contentBottom(box) + box.verticalExtra);
|
|
2004
|
+
}
|
|
2005
|
+
const content = ratioContent(box, contentBottom(box) + box.verticalExtra);
|
|
2006
|
+
remember(box, ctx, width, containingWidth, 'content', content);
|
|
2007
|
+
return content;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
/** A table item's rows and captions at a border-box width, its border box's
|
|
2011
|
+
* height apart from one it sets (`TABLE_CONTENT`): read from its layout
|
|
2012
|
+
* there, which it is given where it has another, and kept for the pass. */
|
|
2013
|
+
function tableContent(
|
|
2014
|
+
box: Box,
|
|
2015
|
+
ctx: LayoutContext,
|
|
2016
|
+
width: number,
|
|
2017
|
+
containing: number,
|
|
2018
|
+
laid: Laid,
|
|
2019
|
+
): number {
|
|
2020
|
+
const kept = keptNatural(box, ctx, width, containing);
|
|
2021
|
+
if (kept !== null && !Number.isNaN(kept.content)) return kept.content;
|
|
2022
|
+
// where the box has that layout still, it is put back and not made again
|
|
2023
|
+
layoutNaturally(box, ctx, width, containing, laid);
|
|
2024
|
+
const content = TABLE_CONTENT.get(box) ?? box.height;
|
|
2025
|
+
remember(box, ctx, width, containing, 'content', content);
|
|
2026
|
+
return content;
|
|
2027
|
+
}
|
|
2028
|
+
|
|
1464
2029
|
/** A laid-out item's content height, its border box's: no less than its
|
|
1465
2030
|
* width through its ratio, height of its own or not (CSS Sizing 4, 5.1). */
|
|
1466
2031
|
function ratioContent(box: Box, content: number): number {
|
|
@@ -1517,10 +2082,13 @@ function holdAt(
|
|
|
1517
2082
|
least: number,
|
|
1518
2083
|
hold: Hold,
|
|
1519
2084
|
): void {
|
|
1520
|
-
if (row)
|
|
1521
|
-
|
|
2085
|
+
if (row) {
|
|
2086
|
+
node.setMinWidth(least);
|
|
2087
|
+
laid.narrowest = least;
|
|
2088
|
+
} else node.setMinHeight(least);
|
|
1522
2089
|
if (!hold.frozen) return;
|
|
1523
2090
|
laid.auto = true;
|
|
2091
|
+
laid.minimum = least;
|
|
1524
2092
|
freezeAt(node, laid, row, least, hold);
|
|
1525
2093
|
}
|
|
1526
2094
|
|
|
@@ -1541,134 +2109,725 @@ function freezeAt(
|
|
|
1541
2109
|
else node.setHeight(size);
|
|
1542
2110
|
}
|
|
1543
2111
|
|
|
2112
|
+
/** The one line of a flex box that does not wrap, as it is shared out here
|
|
2113
|
+
* (`resolveLine`). */
|
|
2114
|
+
interface FlexLine {
|
|
2115
|
+
/** Whether its items are frozen at the sizes 9.7 shares it out to. */
|
|
2116
|
+
resolved: boolean;
|
|
2117
|
+
/** Its size along the main axis, where the box has one: its content
|
|
2118
|
+
* box's width along a row, its height down a column of a height. NaN
|
|
2119
|
+
* down a column that is as tall as its items make it, within limits. */
|
|
2120
|
+
size: number;
|
|
2121
|
+
/** Those limits, the least and the most its content box may be tall. */
|
|
2122
|
+
floor: number;
|
|
2123
|
+
ceiling: number;
|
|
2124
|
+
/** What is between the items' border boxes along it: the gaps, and
|
|
2125
|
+
* their margins, an `auto` one none (8.1). */
|
|
2126
|
+
between: number;
|
|
2127
|
+
/**
|
|
2128
|
+
* Whether it is a row laid out in no room at all — how the box's
|
|
2129
|
+
* min-content width is measured (`MIN_CONTENT_PROBE`), where what an
|
|
2130
|
+
* item contributes to that is its min-content width and not a share of
|
|
2131
|
+
* a line. An item whose flex base size is its content's is taken there
|
|
2132
|
+
* at the size Yoga took, its content fitted to no room, and not at its
|
|
2133
|
+
* max-content width (`Laid.fitted`): a `flex: none` box of two floats
|
|
2134
|
+
* counted at its widest made a `width: min-content` flex box half as
|
|
2135
|
+
* wide again as Chrome has it. A row that is truly as wide as nothing is
|
|
2136
|
+
* taken the same way, and is laid out as Yoga has it.
|
|
2137
|
+
*/
|
|
2138
|
+
probe: boolean;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
/** What is between the border boxes of a line's items along the main
|
|
2142
|
+
* axis: the gaps, and their margins, an `auto` one none — which is what
|
|
2143
|
+
* it is while the line is shared out (CSS Flexbox 9.7, 8.1). */
|
|
2144
|
+
function betweenOf(
|
|
2145
|
+
items: readonly { box: Box }[],
|
|
2146
|
+
row: boolean,
|
|
2147
|
+
gap: number,
|
|
2148
|
+
): number {
|
|
2149
|
+
let between = gap * Math.max(0, items.length - 1);
|
|
2150
|
+
for (const { box } of items) {
|
|
2151
|
+
const style = box.style;
|
|
2152
|
+
if (row) {
|
|
2153
|
+
if (style.marginLeft !== AUTO) between += box.marginLeft;
|
|
2154
|
+
if (style.marginRight !== AUTO) between += box.marginRight;
|
|
2155
|
+
} else {
|
|
2156
|
+
if (style.marginTop !== AUTO) between += box.marginTop;
|
|
2157
|
+
if (style.marginBottom !== AUTO) between += box.marginBottom;
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
return between;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
1544
2163
|
/**
|
|
1545
|
-
*
|
|
1546
|
-
*
|
|
1547
|
-
* it; whether Yoga was asked again.
|
|
1548
|
-
*
|
|
1549
|
-
*
|
|
1550
|
-
*
|
|
2164
|
+
* Share out the one line of a box that does not wrap as CSS Flexbox 9.7
|
|
2165
|
+
* does, where Yoga's answer for it may not be that, and freeze every item
|
|
2166
|
+
* at the size it comes to; whether Yoga was asked again.
|
|
2167
|
+
*
|
|
2168
|
+
* Yoga 3.2.1 shares a line out in two passes where 9.7 goes round until
|
|
2169
|
+
* no item's limit stops it, and each pass has its own way of coming out
|
|
2170
|
+
* elsewhere:
|
|
1551
2171
|
*
|
|
1552
|
-
*
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1555
|
-
*
|
|
1556
|
-
* `
|
|
1557
|
-
*
|
|
1558
|
-
*
|
|
1559
|
-
*
|
|
1560
|
-
*
|
|
1561
|
-
*
|
|
1562
|
-
*
|
|
1563
|
-
*
|
|
1564
|
-
*
|
|
1565
|
-
*
|
|
1566
|
-
*
|
|
2172
|
+
* - the first takes each item a limit stops out of the sum of the flex
|
|
2173
|
+
* factors as it goes, and divides what the line is short of by that
|
|
2174
|
+
* running sum, but takes the item's size off what the line is short of
|
|
2175
|
+
* only once it has been round them all (react/yoga#2006, put right on
|
|
2176
|
+
* Yoga's `main` behind an errata bit and in no release). Every item
|
|
2177
|
+
* after the first one stopped is shrunk too far for it, and may be
|
|
2178
|
+
* stopped too: `width: 100px` three times with `min-width` 95px, 45px
|
|
2179
|
+
* and 0 in a row of 150 was 100, 100 and 100 wide, and overflowed it,
|
|
2180
|
+
* where 9.7 has 95, 45 and 10 — with every item stopped, the line was
|
|
2181
|
+
* left no shorter than it started, and the second pass shared out
|
|
2182
|
+
* nothing. Where what is left of the sum is a float's rounding of
|
|
2183
|
+
* nothing, the second pass divided by that, and each item came out
|
|
2184
|
+
* billions of pixels wide, or none;
|
|
2185
|
+
* - it weighs an item's share of a shortfall by its flex base size where
|
|
2186
|
+
* the item is stopped by a limit before it is shrunk, and adds it to the
|
|
2187
|
+
* sum at its size within the limit, or the other way round: `width:
|
|
2188
|
+
* 200px; max-width: 110.992px` beside `200px` within `62.706px`, each
|
|
2189
|
+
* with a `min-width`, in a row of 50 were 76.67 and 43.31, where 9.7
|
|
2190
|
+
* shrinks both from 200 and has them at their minimums;
|
|
2191
|
+
* - and with no limit at work, it weighs that share by the flex base size,
|
|
2192
|
+
* its border box's, where 9.7 weighs it by the inner flex base size, its
|
|
2193
|
+
* content box's (step 4c), so an item gives up more the more padding
|
|
2194
|
+
* and borders it has (`weighsPadding`);
|
|
2195
|
+
* - a limit that is more than the flex base size stops the item before
|
|
2196
|
+
* any sharing out, and Yoga shares out from there: `flex: 1;
|
|
2197
|
+
* min-width: 200px` beside a `flex: 1` in a row of 600 was 400 and
|
|
2198
|
+
* 200, where each grows from 0 and is 300;
|
|
2199
|
+
* - where the shrink factors come to less than 1, the line keeps that
|
|
2200
|
+
* part of what it is short of (9.7, step 4b), and Yoga shares all of it
|
|
2201
|
+
* out: a `flex-shrink: 0.5` alone was as wide as its row, where it
|
|
2202
|
+
* keeps half of what it overflows by.
|
|
1567
2203
|
*
|
|
1568
|
-
*
|
|
1569
|
-
*
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1572
|
-
*
|
|
1573
|
-
*
|
|
1574
|
-
*
|
|
1575
|
-
* it was, the two layouts that took being the cost of one that is short of
|
|
1576
|
-
* room at its minimums and was not shared out to them; a line with room
|
|
1577
|
-
* for its minimums, or one whose every item sits at its own, has cost the
|
|
1578
|
-
* sum of its sizes.
|
|
2204
|
+
* Which is why a line is looked at only where Yoga's answer shows one of
|
|
2205
|
+
* these could be at work (`unshared`), and else left as it is: a line
|
|
2206
|
+
* none of whose items is stopped, that has been shared out, whose items'
|
|
2207
|
+
* limits do not clamp their flex base sizes, whose shrink factors come to
|
|
2208
|
+
* 1 or more, and which is not short of room with padding and borders more
|
|
2209
|
+
* of one item's flex base size than of another's is shared out by Yoga as
|
|
2210
|
+
* by 9.7.
|
|
1579
2211
|
*
|
|
1580
|
-
*
|
|
1581
|
-
*
|
|
1582
|
-
*
|
|
1583
|
-
*
|
|
2212
|
+
* Each item's flex base size is what its style says it is, or its
|
|
2213
|
+
* content's size as the measure function found it (`Laid.sized`), and
|
|
2214
|
+
* where neither is known, Yoga is asked for the line with no limits and
|
|
2215
|
+
* nothing flexing, where each item is that size (`askBases`). An item held
|
|
2216
|
+
* at its automatic minimum is frozen there already (`holdAt`), and the
|
|
2217
|
+
* rest are shared out around it.
|
|
1584
2218
|
*/
|
|
1585
|
-
function
|
|
2219
|
+
function resolveLine(
|
|
1586
2220
|
items: readonly { box: Box; node: YogaNode; laid: Laid }[],
|
|
1587
2221
|
row: boolean,
|
|
1588
|
-
|
|
2222
|
+
line: FlexLine,
|
|
2223
|
+
/** The size along the main axis Yoga laid the line out in. */
|
|
1589
2224
|
room: number,
|
|
1590
|
-
/** The gap between two items. */
|
|
1591
|
-
gap: number,
|
|
1592
2225
|
hold: Hold,
|
|
1593
2226
|
/** Lay the flex box out, as its nodes now are. */
|
|
1594
2227
|
ask: () => void,
|
|
1595
2228
|
): boolean {
|
|
1596
|
-
|
|
1597
|
-
let
|
|
1598
|
-
|
|
1599
|
-
|
|
2229
|
+
if (!unshared(items, row, room - line.between, line.probe)) return false;
|
|
2230
|
+
let unknown = false;
|
|
2231
|
+
for (const { box, laid } of items) {
|
|
2232
|
+
laid.base = baseOf(box, laid, row, line.probe);
|
|
2233
|
+
if (Number.isNaN(laid.base) && Number.isNaN(laid.held)) unknown = true;
|
|
2234
|
+
}
|
|
2235
|
+
if (unknown) askBases(items, row, ask);
|
|
2236
|
+
const sizes = shareLine(items, row, line);
|
|
2237
|
+
if (items.every(({ laid }, i) => Math.abs(sizes[i] - laid.main) <= NEAR)) {
|
|
2238
|
+
// Yoga's answer, which asking for the bases has to be put back to
|
|
2239
|
+
if (unknown) ask();
|
|
2240
|
+
return unknown;
|
|
2241
|
+
}
|
|
2242
|
+
line.resolved = true;
|
|
2243
|
+
freezeLine(items, row, sizes, hold);
|
|
2244
|
+
ask();
|
|
2245
|
+
return true;
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
/** Freeze each item of a line at the size it was shared out to, but for
|
|
2249
|
+
* one held at its automatic minimum whose flex base size is not known,
|
|
2250
|
+
* which is frozen there already. */
|
|
2251
|
+
function freezeLine(
|
|
2252
|
+
items: readonly { node: YogaNode; laid: Laid }[],
|
|
2253
|
+
row: boolean,
|
|
2254
|
+
sizes: readonly number[],
|
|
2255
|
+
hold: Hold,
|
|
2256
|
+
): void {
|
|
2257
|
+
items.forEach(({ node, laid }, i) => {
|
|
2258
|
+
if (Number.isNaN(laid.base)) return;
|
|
2259
|
+
freezeAt(node, laid, row, sizes[i], hold);
|
|
2260
|
+
// its content's size, where that is what it came to (`Laid.auto`)
|
|
2261
|
+
laid.auto = sizes[i] <= laid.minimum + NEAR;
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
/** How near to its room a line may be and have room to spare
|
|
2266
|
+
* (`cappedLines`). */
|
|
2267
|
+
const SHORT = 0.001;
|
|
2268
|
+
|
|
2269
|
+
/** Share a line this engine has shared out again from its items' flex base
|
|
2270
|
+
* sizes, and freeze each item at its size: where one is since held at its
|
|
2271
|
+
* content's size, the rest of the line is shared out around it. */
|
|
2272
|
+
function shareOut(
|
|
2273
|
+
items: readonly { box: Box; node: YogaNode; laid: Laid }[],
|
|
2274
|
+
row: boolean,
|
|
2275
|
+
line: FlexLine,
|
|
2276
|
+
hold: Hold,
|
|
2277
|
+
): void {
|
|
2278
|
+
const sizes = shareLine(items, row, line);
|
|
2279
|
+
freezeLine(items, row, sizes, hold);
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
/** Each item's size along the main axis as 9.7 shares the line out from
|
|
2283
|
+
* their flex base sizes (`Laid.base`); one held at its automatic minimum
|
|
2284
|
+
* is frozen there. */
|
|
2285
|
+
function shareLine(
|
|
2286
|
+
items: readonly { box: Box; laid: Laid }[],
|
|
2287
|
+
row: boolean,
|
|
2288
|
+
line: FlexLine,
|
|
2289
|
+
): number[] {
|
|
2290
|
+
const flexing: Flexing[] = items.map(({ box, laid }) => {
|
|
2291
|
+
// an automatic minimum it is held to is its minimum, which 9.7 freezes
|
|
2292
|
+
// it at where the line shares out less to it — and not before, which
|
|
2293
|
+
// would leave it out of the free space the line started with (4b)
|
|
2294
|
+
let least = leastOf(box, laid, row);
|
|
2295
|
+
if (!Number.isNaN(laid.minimum)) least = Math.max(least, laid.minimum);
|
|
2296
|
+
return {
|
|
2297
|
+
base: laid.base,
|
|
2298
|
+
least,
|
|
2299
|
+
most: Math.max(mostOf(box, laid, row), least),
|
|
2300
|
+
extra: row ? box.horizontalExtra : box.verticalExtra,
|
|
2301
|
+
grow: box.style.flexGrow,
|
|
2302
|
+
shrink: box.style.flexShrink,
|
|
2303
|
+
frozen: Number.isNaN(laid.base) ? laid.held : NaN,
|
|
2304
|
+
};
|
|
2305
|
+
});
|
|
2306
|
+
let room = line.size;
|
|
2307
|
+
if (Number.isNaN(room)) {
|
|
2308
|
+
// down a column that is as tall as its items, within its limits: the
|
|
2309
|
+
// sum of their hypothetical sizes (9.9.1)
|
|
2310
|
+
room = line.between;
|
|
2311
|
+
for (const item of flexing) room += hypotheticalOf(item);
|
|
2312
|
+
room = Math.max(line.floor, Math.min(line.ceiling, room));
|
|
2313
|
+
}
|
|
2314
|
+
return resolveLengths(flexing, room - line.between);
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
/**
|
|
2318
|
+
* Whether Yoga's answer for the one line of a box that does not wrap may
|
|
2319
|
+
* not be what CSS Flexbox 9.7 makes of it (`resolveLine`): where an item
|
|
2320
|
+
* has a limit of its own along the main axis, which 9.7 is then asked —
|
|
2321
|
+
* sums of numbers, where each item's flex base size is known — and where
|
|
2322
|
+
* none has, what shows in Yoga's answer: an item a limit stopped as the
|
|
2323
|
+
* line was shared out, which with none of its own is its padding and
|
|
2324
|
+
* borders, a line not shared out while an item may yet flex the way it is
|
|
2325
|
+
* out, or flex factors that come to less than 1, which Yoga shares out as
|
|
2326
|
+
* though they came to 1 — or, for one item that may grow and shrink, from
|
|
2327
|
+
* a flex basis of nothing.
|
|
2328
|
+
*/
|
|
2329
|
+
function unshared(
|
|
2330
|
+
items: readonly { box: Box; laid: Laid }[],
|
|
2331
|
+
row: boolean,
|
|
2332
|
+
/** The room for the items' border boxes. */
|
|
2333
|
+
room: number,
|
|
2334
|
+
/** Whether the line is a min-content probe's (`FlexLine.probe`). */
|
|
2335
|
+
probe: boolean,
|
|
2336
|
+
): boolean {
|
|
2337
|
+
let sum = 0;
|
|
2338
|
+
for (const { laid } of items) sum += laid.main;
|
|
2339
|
+
const over = sum > room + NEAR;
|
|
2340
|
+
const under = sum < room - NEAR;
|
|
2341
|
+
let grows = 0;
|
|
2342
|
+
let shrinks = 0;
|
|
2343
|
+
let growable = false;
|
|
2344
|
+
for (const { box, laid } of items) {
|
|
2345
|
+
// held at its content's size, and frozen (`holdAt`)
|
|
2346
|
+
if (!Number.isNaN(laid.held)) continue;
|
|
2347
|
+
if (!Number.isNaN(laid.least) || !Number.isNaN(laid.most)) return true;
|
|
2348
|
+
const { flexGrow: grow, flexShrink: shrink } = box.style;
|
|
2349
|
+
const least = leastOf(box, laid, row);
|
|
2350
|
+
const base = baseOf(box, laid, row, probe);
|
|
2351
|
+
const size = laid.main;
|
|
2352
|
+
grows += grow;
|
|
2353
|
+
shrinks += shrink;
|
|
2354
|
+
// stopped by its padding where it was shrunk to it, and not where it
|
|
2355
|
+
// is that size unflexed
|
|
2356
|
+
if (
|
|
2357
|
+
shrink > 0 &&
|
|
2358
|
+
!under &&
|
|
2359
|
+
size <= least + NEAR &&
|
|
2360
|
+
!(base <= size + NEAR)
|
|
2361
|
+
) {
|
|
2362
|
+
return true;
|
|
2363
|
+
}
|
|
2364
|
+
if (over && shrink > 0 && size > least + NEAR) return true;
|
|
2365
|
+
if (under && grow > 0) growable = true;
|
|
2366
|
+
}
|
|
2367
|
+
return (
|
|
2368
|
+
(growable && grows >= 1) ||
|
|
2369
|
+
(!under && shrinks > 0 && shrinks < 1) ||
|
|
2370
|
+
(!over && grows > 0 && grows < 1) ||
|
|
2371
|
+
(!under && weighsPadding(items, row, room, probe))
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
/**
|
|
2376
|
+
* Whether Yoga may have shared out what a line is short of by its items'
|
|
2377
|
+
* padding and borders, where 9.7 leaves them out: Yoga weighs an item's
|
|
2378
|
+
* share by its flex shrink factor times its flex base size, its border
|
|
2379
|
+
* box's, and 9.7 by the factor times its inner flex base size, its content
|
|
2380
|
+
* box's (step 4c). The two are the same shares where padding and borders
|
|
2381
|
+
* are the same part of the flex base size of every item that shrinks —
|
|
2382
|
+
* none of it, most often — and else Yoga takes more from an item the more
|
|
2383
|
+
* of them it has: `width: 100px; padding-left: 50px` beside `width: 100px`
|
|
2384
|
+
* in a row of 150 was 90 and 60 wide, where each gives up 50 of its
|
|
2385
|
+
* content and is 100 and 50. Where an item's flex base size is not known,
|
|
2386
|
+
* its padding may be any part of it, and the line is looked at.
|
|
2387
|
+
*/
|
|
2388
|
+
function weighsPadding(
|
|
2389
|
+
items: readonly { box: Box; laid: Laid }[],
|
|
2390
|
+
row: boolean,
|
|
2391
|
+
/** The room for the items' border boxes. */
|
|
2392
|
+
room: number,
|
|
2393
|
+
probe: boolean,
|
|
2394
|
+
): boolean {
|
|
2395
|
+
let sum = 0;
|
|
1600
2396
|
let shrinking = 0;
|
|
1601
|
-
let
|
|
2397
|
+
let padded = false;
|
|
2398
|
+
let unknown = false;
|
|
2399
|
+
// the part of the first flex base size that shrinks that is padding and
|
|
2400
|
+
// borders, and whether that of another is not the same
|
|
2401
|
+
let part = NaN;
|
|
2402
|
+
let uneven = false;
|
|
1602
2403
|
for (const { box, laid } of items) {
|
|
1603
|
-
|
|
1604
|
-
|
|
2404
|
+
if (!Number.isNaN(laid.held)) {
|
|
2405
|
+
sum += laid.held;
|
|
2406
|
+
continue;
|
|
2407
|
+
}
|
|
2408
|
+
const base = baseOf(box, laid, row, probe);
|
|
2409
|
+
if (Number.isNaN(base)) unknown = true;
|
|
2410
|
+
else sum += base;
|
|
2411
|
+
if (!(box.style.flexShrink > 0)) continue;
|
|
2412
|
+
shrinking += 1;
|
|
2413
|
+
const extra = row ? box.horizontalExtra : box.verticalExtra;
|
|
2414
|
+
if (extra > 0) padded = true;
|
|
2415
|
+
if (Number.isNaN(base)) continue;
|
|
2416
|
+
const of = base > 0 ? extra / base : 0;
|
|
2417
|
+
if (Number.isNaN(part)) part = of;
|
|
2418
|
+
else if (Math.abs(of - part) > 1e-9) uneven = true;
|
|
2419
|
+
}
|
|
2420
|
+
if (shrinking < 2 || !padded) return false;
|
|
2421
|
+
return unknown || (uneven && sum > room + NEAR);
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
/** How near two sizes along the main axis are the same, as Yoga's float32s
|
|
2425
|
+
* have them. */
|
|
2426
|
+
const NEAR = 0.01;
|
|
2427
|
+
|
|
2428
|
+
/** An item's flex base size, its border box's, where this engine knows it:
|
|
2429
|
+
* what its style says, or its content's size as last measured — fitted
|
|
2430
|
+
* to no room, in a min-content probe (`FlexLine.probe`) — and no less than
|
|
2431
|
+
* its padding and borders, as Yoga has it. Else NaN. */
|
|
2432
|
+
function baseOf(box: Box, laid: Laid, row: boolean, probe: boolean): number {
|
|
2433
|
+
const extra = row ? box.horizontalExtra : box.verticalExtra;
|
|
2434
|
+
const content = probe ? laid.fitted : laid.content;
|
|
2435
|
+
return Math.max(laid.sized ? content : laid.basis, extra);
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
/**
|
|
2439
|
+
* Give each item that is not frozen the flex base size Yoga makes of it,
|
|
2440
|
+
* where this engine does not know it (`baseOf`): the line laid out with no
|
|
2441
|
+
* limit on any of them along the main axis and none of them flexing, where
|
|
2442
|
+
* each is that size. Their limits and flex factors are put back after.
|
|
2443
|
+
*/
|
|
2444
|
+
function askBases(
|
|
2445
|
+
items: readonly { box: Box; node: YogaNode; laid: Laid }[],
|
|
2446
|
+
row: boolean,
|
|
2447
|
+
ask: () => void,
|
|
2448
|
+
): void {
|
|
2449
|
+
const open = items.filter(({ laid }) => Number.isNaN(laid.held));
|
|
2450
|
+
for (const { node } of open) {
|
|
1605
2451
|
if (row) {
|
|
1606
|
-
|
|
1607
|
-
|
|
2452
|
+
node.setMinWidth(Number.NaN);
|
|
2453
|
+
node.setMaxWidth(Number.NaN);
|
|
1608
2454
|
} else {
|
|
1609
|
-
|
|
1610
|
-
|
|
2455
|
+
node.setMinHeight(Number.NaN);
|
|
2456
|
+
node.setMaxHeight(Number.NaN);
|
|
2457
|
+
}
|
|
2458
|
+
node.setFlexGrow(0);
|
|
2459
|
+
node.setFlexShrink(0);
|
|
2460
|
+
}
|
|
2461
|
+
ask();
|
|
2462
|
+
for (const { box, node, laid } of open) {
|
|
2463
|
+
if (Number.isNaN(laid.base)) {
|
|
2464
|
+
laid.base = row ? node.getComputedWidth() : node.getComputedHeight();
|
|
2465
|
+
}
|
|
2466
|
+
if (!Number.isNaN(laid.least)) {
|
|
2467
|
+
if (row) node.setMinWidth(laid.least);
|
|
2468
|
+
else node.setMinHeight(laid.least);
|
|
2469
|
+
}
|
|
2470
|
+
if (!Number.isNaN(laid.most)) {
|
|
2471
|
+
if (row) node.setMaxWidth(laid.most);
|
|
2472
|
+
else node.setMaxHeight(laid.most);
|
|
2473
|
+
}
|
|
2474
|
+
node.setFlexGrow(box.style.flexGrow);
|
|
2475
|
+
node.setFlexShrink(box.style.flexShrink);
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
/** An item as its line is shared out (`resolveLengths`), each size its
|
|
2480
|
+
* border box's along the main axis. */
|
|
2481
|
+
interface Flexing {
|
|
2482
|
+
base: number;
|
|
2483
|
+
/** Its least size, no less than `extra`, and its most, no less than
|
|
2484
|
+
* that. */
|
|
2485
|
+
least: number;
|
|
2486
|
+
most: number;
|
|
2487
|
+
/** Its padding and borders. */
|
|
2488
|
+
extra: number;
|
|
2489
|
+
grow: number;
|
|
2490
|
+
shrink: number;
|
|
2491
|
+
/** The size it is frozen at from the start, or NaN. */
|
|
2492
|
+
frozen: number;
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
/** An item's hypothetical main size: its flex base size within its limits
|
|
2496
|
+
* (CSS Flexbox 9.2, step 3), or where it is frozen, that size. */
|
|
2497
|
+
function hypotheticalOf(item: Flexing): number {
|
|
2498
|
+
return Number.isNaN(item.frozen)
|
|
2499
|
+
? Math.max(item.least, Math.min(item.most, item.base))
|
|
2500
|
+
: item.frozen;
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
/**
|
|
2504
|
+
* Resolve the flexible lengths of a line (CSS Flexbox 9.7): each item's
|
|
2505
|
+
* size along the main axis, sharing out the room its border boxes have
|
|
2506
|
+
* from their flex base sizes, and going round again with each item a limit
|
|
2507
|
+
* stops frozen at it until none is — the minimums where the line is short
|
|
2508
|
+
* of more than the maximums give back, and the other way round.
|
|
2509
|
+
*/
|
|
2510
|
+
function resolveLengths(line: readonly Flexing[], room: number): number[] {
|
|
2511
|
+
const count = line.length;
|
|
2512
|
+
const size = line.map(hypotheticalOf);
|
|
2513
|
+
let sum = 0;
|
|
2514
|
+
for (const s of size) sum += s;
|
|
2515
|
+
// step 1: grow where the line has room at the items' hypothetical sizes
|
|
2516
|
+
const grow = sum < room;
|
|
2517
|
+
// step 2: an item that does not flex, or would flex away from its limit,
|
|
2518
|
+
// is frozen at its hypothetical size — a flex base size Yoga worked out
|
|
2519
|
+
// being as near to that as its float32s come, and no further
|
|
2520
|
+
const frozen = line.map(
|
|
2521
|
+
(item, i) =>
|
|
2522
|
+
!Number.isNaN(item.frozen) ||
|
|
2523
|
+
(grow ? item.grow : item.shrink) === 0 ||
|
|
2524
|
+
(grow ? item.base > size[i] + NEAR : item.base < size[i] - NEAR),
|
|
2525
|
+
);
|
|
2526
|
+
const free = (): number => {
|
|
2527
|
+
let left = room;
|
|
2528
|
+
for (let i = 0; i < count; i++) left -= frozen[i] ? size[i] : line[i].base;
|
|
2529
|
+
return left;
|
|
2530
|
+
};
|
|
2531
|
+
// step 3
|
|
2532
|
+
const initial = free();
|
|
2533
|
+
const off = new Array<number>(count).fill(0);
|
|
2534
|
+
for (;;) {
|
|
2535
|
+
// step 4a
|
|
2536
|
+
let factors = 0;
|
|
2537
|
+
let open = false;
|
|
2538
|
+
for (let i = 0; i < count; i++) {
|
|
2539
|
+
if (frozen[i]) continue;
|
|
2540
|
+
open = true;
|
|
2541
|
+
factors += grow ? line[i].grow : line[i].shrink;
|
|
2542
|
+
}
|
|
2543
|
+
if (!open) break;
|
|
2544
|
+
// 4b: flex factors that come to less than 1 share out that part of the
|
|
2545
|
+
// free space the line started with
|
|
2546
|
+
let left = free();
|
|
2547
|
+
if (factors < 1) {
|
|
2548
|
+
const part = initial * factors;
|
|
2549
|
+
if (Math.abs(part) < Math.abs(left)) left = part;
|
|
2550
|
+
}
|
|
2551
|
+
// 4c: shared by the grow factors, or by the shrink factors each times
|
|
2552
|
+
// the item's inner flex base size
|
|
2553
|
+
let scaled = 0;
|
|
2554
|
+
if (!grow) {
|
|
2555
|
+
for (let i = 0; i < count; i++) {
|
|
2556
|
+
if (!frozen[i])
|
|
2557
|
+
scaled += line[i].shrink * (line[i].base - line[i].extra);
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
// 4d: each within its limits, and how far that took it
|
|
2561
|
+
let violation = 0;
|
|
2562
|
+
for (let i = 0; i < count; i++) {
|
|
2563
|
+
if (frozen[i]) continue;
|
|
2564
|
+
const item = line[i];
|
|
2565
|
+
let target = item.base;
|
|
2566
|
+
if (grow) target += (left * item.grow) / factors;
|
|
2567
|
+
else if (scaled > 0) {
|
|
2568
|
+
target -=
|
|
2569
|
+
(Math.abs(left) * item.shrink * (item.base - item.extra)) / scaled;
|
|
2570
|
+
}
|
|
2571
|
+
size[i] = Math.max(item.least, Math.min(item.most, target));
|
|
2572
|
+
off[i] = size[i] - target;
|
|
2573
|
+
violation += off[i];
|
|
1611
2574
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
2575
|
+
// 4e: freeze those stopped the way most of them were, or all of them
|
|
2576
|
+
for (let i = 0; i < count; i++) {
|
|
2577
|
+
if (frozen[i]) continue;
|
|
2578
|
+
if (violation === 0 || (violation > 0 ? off[i] > 0 : off[i] < 0)) {
|
|
2579
|
+
frozen[i] = true;
|
|
2580
|
+
}
|
|
1618
2581
|
}
|
|
1619
2582
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
2583
|
+
return size;
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
/** The least an item may be along the main axis, its border box: the
|
|
2587
|
+
* minimum of its own, and no less than its padding and borders. */
|
|
2588
|
+
function leastOf(box: Box, laid: Laid, row: boolean): number {
|
|
2589
|
+
const extra = row ? box.horizontalExtra : box.verticalExtra;
|
|
2590
|
+
return Number.isNaN(laid.least) ? extra : Math.max(laid.least, extra);
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
/**
|
|
2594
|
+
* Put right the lines of a flex box that wraps on which every item that
|
|
2595
|
+
* may grow is stopped by its maximum and Yoga made them smaller for it;
|
|
2596
|
+
* whether Yoga was asked again. The one line of a box that does not wrap
|
|
2597
|
+
* is shared out by 9.7 here where Yoga's answer may not be its
|
|
2598
|
+
* (`resolveLine`), which puts right this and more. Each such line is laid out with every item of it that
|
|
2599
|
+
* may grow frozen at its maximum, which is where CSS Flexbox leaves them
|
|
2600
|
+
* (9.7, step 4): the line has room left over with every one of them as
|
|
2601
|
+
* large as it goes. It is also where Yoga leaves them itself, where its
|
|
2602
|
+
* rounding is not below 0.
|
|
2603
|
+
*
|
|
2604
|
+
* It is a float's rounding of nothing. Yoga's first pass takes
|
|
2605
|
+
* each item its maximum stops out of the sum of the grow factors, and the
|
|
2606
|
+
* second divides the room still left by what is left of the sum: with
|
|
2607
|
+
* every item stopped, `(a + b + c) - a - b - c` in float32s. Where that is
|
|
2608
|
+
* 0, or a rounding above it, the quotient is vast and each item is held to
|
|
2609
|
+
* its maximum, which is right; where it is a rounding below, each is held
|
|
2610
|
+
* to its minimum. Three items of `flex-grow: 3.361`, `2.417` and `1.988`,
|
|
2611
|
+
* each `max-width: 10px`, in a row 100 wide came out nothing wide, as a
|
|
2612
|
+
* third of such lines of random factors do. Sums of whole numbers, and of
|
|
2613
|
+
* halves and quarters of them, are exact, so a box whose factors are those
|
|
2614
|
+
* is not looked at (`exactSums`).
|
|
2615
|
+
*
|
|
2616
|
+
* What makes a line one to put right is Yoga's answer. Every item of
|
|
2617
|
+
* the line that may grow has a
|
|
2618
|
+
* maximum, and the line has room to spare with each of them at it. And
|
|
2619
|
+
* one of them came out short of its maximum and no larger than its
|
|
2620
|
+
* hypothetical size (9.7, step 2), which Yoga, sharing out room, makes no
|
|
2621
|
+
* item: Yoga is asked for the line with nothing growing, where each item
|
|
2622
|
+
* is that size. No larger rather than smaller, because an item held to
|
|
2623
|
+
* its minimum is often that size: an empty one is nothing wide either
|
|
2624
|
+
* way. Any other line is put back as it was, and the two layouts that
|
|
2625
|
+
* took are the cost of a box with a factor that is not a whole number and
|
|
2626
|
+
* an item short of its maximum; any other box has cost a sum.
|
|
2627
|
+
*
|
|
2628
|
+
* A few of the lines put right are still not a browser's. Once
|
|
2629
|
+
* some of its items are frozen, 9.7 shares out to the rest only the part
|
|
2630
|
+
* of the room their factors come to, where that is less than 1 (step 4,
|
|
2631
|
+
* b), and Yoga has no such rule: an item of `flex-grow: 0.1` left alone
|
|
2632
|
+
* on a line is at its maximum here, as Yoga would have had it, and a
|
|
2633
|
+
* tenth of the room in Chrome.
|
|
2634
|
+
*
|
|
2635
|
+
* A box that wraps has lines like it: the last line of a grid of cards
|
|
2636
|
+
* with a `max-width` has room for every card it has. Yoga breaks lines by
|
|
2637
|
+
* its items' hypothetical sizes, which the layout with nothing growing
|
|
2638
|
+
* gives, and they are broken here as it breaks them (`linesOf`). Freezing
|
|
2639
|
+
* a line's items at their maximums moves no break: the line has room for
|
|
2640
|
+
* every item on it at that size, so it keeps them all, and the item after
|
|
2641
|
+
* it, which did not fit with them smaller, fits no better with them larger.
|
|
2642
|
+
* But Yoga holds a size to an item's maximum before its minimum, so where
|
|
2643
|
+
* the maximum is the smaller — or smaller than the item's padding and
|
|
2644
|
+
* borders — it breaks a line by one size and lays the item out at another:
|
|
2645
|
+
* `max-width: 2px; min-width: 10px` is 10 wide, on a line it was put on
|
|
2646
|
+
* as 2. A box that wraps with an item like that is left as Yoga had it.
|
|
2647
|
+
*/
|
|
2648
|
+
function cappedLines(
|
|
2649
|
+
items: readonly { box: Box; node: YogaNode; laid: Laid }[],
|
|
2650
|
+
row: boolean,
|
|
2651
|
+
/** The flex box's size along the main axis. */
|
|
2652
|
+
room: number,
|
|
2653
|
+
/** The size Yoga breaks lines at: the flex box's where it wraps and has
|
|
2654
|
+
* one, and else Infinity, for the one line. */
|
|
2655
|
+
breaks: number,
|
|
2656
|
+
/** The gap between two items. */
|
|
2657
|
+
gap: number,
|
|
2658
|
+
hold: Hold,
|
|
2659
|
+
/** Lay the flex box out, as its nodes now are. */
|
|
2660
|
+
ask: () => void,
|
|
2661
|
+
): boolean {
|
|
2662
|
+
// two at the least: what is left of a sum of one, less it, is 0 exactly
|
|
2663
|
+
const growing = items.filter(({ box, laid }) => grows(box, laid));
|
|
2664
|
+
if (growing.length < 2 || exactSums(growing)) return false;
|
|
2665
|
+
// one short of its maximum, as each on such a line is
|
|
2666
|
+
const short = growing.some(
|
|
2667
|
+
({ box, laid }) => laid.main < mostOf(box, laid, row) - 0.01,
|
|
2668
|
+
);
|
|
2669
|
+
if (!short) return false;
|
|
2670
|
+
if (breaks === Infinity) {
|
|
2671
|
+
// and on the one line, every one of them with a maximum, and room left
|
|
2672
|
+
// over as Yoga has it
|
|
2673
|
+
let used = gap * (items.length - 1);
|
|
2674
|
+
for (const { box, laid } of items) used += laid.main + marginsOf(box, row);
|
|
2675
|
+
const capped = growing.every(({ box, laid }) => !Number.isNaN(laid.most));
|
|
2676
|
+
if (!capped || !(used < room - SHORT)) return false;
|
|
2677
|
+
} else if (
|
|
2678
|
+
// and on lines broken where the layout says, with no item that Yoga
|
|
2679
|
+
// breaks a line by at another size
|
|
2680
|
+
items.some(({ box, laid }) => laid.most < leastOf(box, laid, row))
|
|
2681
|
+
) {
|
|
1624
2682
|
return false;
|
|
1625
2683
|
}
|
|
1626
2684
|
|
|
1627
|
-
const
|
|
1628
|
-
for (const { node } of
|
|
2685
|
+
const flexible = items.filter(({ laid }) => Number.isNaN(laid.held));
|
|
2686
|
+
for (const { node } of flexible) {
|
|
2687
|
+
node.setFlexGrow(0);
|
|
2688
|
+
node.setFlexShrink(0);
|
|
2689
|
+
}
|
|
1629
2690
|
ask();
|
|
1630
|
-
// each item its hypothetical size,
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
for (const
|
|
1636
|
-
const
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
2691
|
+
// each item its hypothetical size, on every line
|
|
2692
|
+
const hypothetical = items.map(({ node, laid }) =>
|
|
2693
|
+
row ? node.getComputedWidth() : heightOf(node, laid),
|
|
2694
|
+
);
|
|
2695
|
+
const frozen = new Set<Box>();
|
|
2696
|
+
for (const line of linesOf(items, hypothetical, row, breaks, gap)) {
|
|
2697
|
+
const growers = line.filter((i) => grows(items[i].box, items[i].laid));
|
|
2698
|
+
if (growers.length < 2) continue;
|
|
2699
|
+
// the line with each item that may grow at its maximum: Infinity where
|
|
2700
|
+
// one has none
|
|
2701
|
+
let size = gap * (line.length - 1);
|
|
2702
|
+
let smaller = false;
|
|
2703
|
+
for (const i of line) {
|
|
2704
|
+
const { box, laid } = items[i];
|
|
2705
|
+
size += marginsOf(box, row);
|
|
2706
|
+
if (!grows(box, laid)) {
|
|
2707
|
+
size += hypothetical[i];
|
|
2708
|
+
continue;
|
|
2709
|
+
}
|
|
2710
|
+
const most = mostOf(box, laid, row);
|
|
2711
|
+
size += most;
|
|
2712
|
+
if (
|
|
2713
|
+
laid.main < most - 0.01 &&
|
|
2714
|
+
(laid.main < hypothetical[i] || nearly(laid.main, hypothetical[i]))
|
|
2715
|
+
) {
|
|
2716
|
+
smaller = true;
|
|
1644
2717
|
}
|
|
1645
2718
|
}
|
|
2719
|
+
if (!smaller || !(size < room - SHORT)) continue;
|
|
2720
|
+
for (const i of growers) frozen.add(items[i].box);
|
|
1646
2721
|
}
|
|
1647
|
-
const
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
else
|
|
2722
|
+
for (const { box, node, laid } of flexible) {
|
|
2723
|
+
if (frozen.has(box)) {
|
|
2724
|
+
freezeAt(node, laid, row, mostOf(box, laid, row), hold);
|
|
2725
|
+
} else {
|
|
2726
|
+
node.setFlexGrow(box.style.flexGrow);
|
|
2727
|
+
node.setFlexShrink(box.style.flexShrink);
|
|
2728
|
+
}
|
|
1651
2729
|
}
|
|
1652
2730
|
ask();
|
|
1653
2731
|
return true;
|
|
1654
2732
|
}
|
|
1655
2733
|
|
|
1656
|
-
/**
|
|
1657
|
-
*
|
|
1658
|
-
|
|
1659
|
-
|
|
2734
|
+
/**
|
|
2735
|
+
* The lines Yoga breaks a flex box's items into, as indices of them. A line
|
|
2736
|
+
* takes items while their hypothetical sizes, their margins and the gaps
|
|
2737
|
+
* between them come to no more than the size it breaks at, and always
|
|
2738
|
+
* takes one — added up in float32s, in the order Yoga adds them, so that an
|
|
2739
|
+
* item that fits exactly is on the line it is on in Yoga's.
|
|
2740
|
+
*/
|
|
2741
|
+
function linesOf(
|
|
2742
|
+
items: readonly { box: Box }[],
|
|
2743
|
+
/** Each item's hypothetical size along the main axis. */
|
|
2744
|
+
sizes: readonly number[],
|
|
2745
|
+
row: boolean,
|
|
2746
|
+
breaks: number,
|
|
2747
|
+
gap: number,
|
|
2748
|
+
): number[][] {
|
|
2749
|
+
if (breaks === Infinity) return [items.map((_, i) => i)];
|
|
2750
|
+
const f = Math.fround;
|
|
2751
|
+
const limit = f(breaks);
|
|
2752
|
+
const lines: number[][] = [];
|
|
2753
|
+
let line: number[] = [];
|
|
2754
|
+
let used = 0;
|
|
2755
|
+
items.forEach(({ box }, i) => {
|
|
2756
|
+
const size = f(sizes[i]);
|
|
2757
|
+
const style = box.style;
|
|
2758
|
+
const [start, end] = row
|
|
2759
|
+
? [style.marginLeft, style.marginRight]
|
|
2760
|
+
: [style.marginTop, style.marginBottom];
|
|
2761
|
+
const margin = f(
|
|
2762
|
+
(start === AUTO ? 0 : f(row ? box.marginLeft : box.marginTop)) +
|
|
2763
|
+
(end === AUTO ? 0 : f(row ? box.marginRight : box.marginBottom)),
|
|
2764
|
+
);
|
|
2765
|
+
let lead = line.length ? f(gap) : 0;
|
|
2766
|
+
if (line.length && f(f(f(used + size) + margin) + lead) > limit) {
|
|
2767
|
+
lines.push(line);
|
|
2768
|
+
line = [];
|
|
2769
|
+
used = 0;
|
|
2770
|
+
lead = 0;
|
|
2771
|
+
}
|
|
2772
|
+
used = f(used + f(f(size + margin) + lead));
|
|
2773
|
+
line.push(i);
|
|
2774
|
+
});
|
|
2775
|
+
lines.push(line);
|
|
2776
|
+
return lines;
|
|
1660
2777
|
}
|
|
1661
2778
|
|
|
1662
|
-
/**
|
|
1663
|
-
|
|
2779
|
+
/** Whether Yoga may grow an item: it has a grow factor, and is not frozen
|
|
2780
|
+
* (`freezeAt`). */
|
|
2781
|
+
function grows(box: Box, laid: Laid): boolean {
|
|
2782
|
+
return box.style.flexGrow > 0 && Number.isNaN(laid.held);
|
|
2783
|
+
}
|
|
1664
2784
|
|
|
1665
|
-
/** The
|
|
1666
|
-
*
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
2785
|
+
/** The most an item may be along the main axis, its border box: the
|
|
2786
|
+
* maximum of its own, where it has one, and no less than its least size;
|
|
2787
|
+
* Infinity where it has none. */
|
|
2788
|
+
function mostOf(box: Box, laid: Laid, row: boolean): number {
|
|
2789
|
+
if (Number.isNaN(laid.most)) return Infinity;
|
|
2790
|
+
return Math.max(laid.most, leastOf(box, laid, row));
|
|
1670
2791
|
}
|
|
1671
2792
|
|
|
2793
|
+
/** An item's margins along the main axis, where they are not `auto`: an
|
|
2794
|
+
* `auto` margin is none to the size of its line (CSS Flexbox 8.1). */
|
|
2795
|
+
function marginsOf(box: Box, row: boolean): number {
|
|
2796
|
+
const style = box.style;
|
|
2797
|
+
if (row) {
|
|
2798
|
+
return (
|
|
2799
|
+
(style.marginLeft === AUTO ? 0 : box.marginLeft) +
|
|
2800
|
+
(style.marginRight === AUTO ? 0 : box.marginRight)
|
|
2801
|
+
);
|
|
2802
|
+
}
|
|
2803
|
+
return (
|
|
2804
|
+
(style.marginTop === AUTO ? 0 : box.marginTop) +
|
|
2805
|
+
(style.marginBottom === AUTO ? 0 : box.marginBottom)
|
|
2806
|
+
);
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
/**
|
|
2810
|
+
* Whether Yoga's sums of these items' grow factors are exact: in float32s,
|
|
2811
|
+
* as it adds them, a sum of whole numbers is, and so is one of halves and
|
|
2812
|
+
* quarters of them, and what is left of it less each of its terms is then
|
|
2813
|
+
* 0 exactly (`cappedLines`).
|
|
2814
|
+
*/
|
|
2815
|
+
function exactSums(items: readonly { box: Box }[]): boolean {
|
|
2816
|
+
let sum = 0;
|
|
2817
|
+
for (const { box } of items) {
|
|
2818
|
+
const factor = box.style.flexGrow * EXACT;
|
|
2819
|
+
if (!Number.isInteger(factor)) return false;
|
|
2820
|
+
sum += factor;
|
|
2821
|
+
}
|
|
2822
|
+
// every sum along the way a whole number of the parts under a float32's
|
|
2823
|
+
// 24 bits of significand
|
|
2824
|
+
return sum < 2 ** 24;
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
/** The parts of one a grow factor may be a whole number of and have its
|
|
2828
|
+
* sums exact (`exactSums`). */
|
|
2829
|
+
const EXACT = 1024;
|
|
2830
|
+
|
|
1672
2831
|
/** An item's min-content width, its border box's, taken the once: its
|
|
1673
2832
|
* content's, whatever width it has of its own (`keywordWidth`'s too). */
|
|
1674
2833
|
function minContentOf(box: Box, ctx: LayoutContext, laid: Laid): number {
|
|
@@ -1747,22 +2906,133 @@ function baselineLines<T extends { box: Box }>(
|
|
|
1747
2906
|
line = [];
|
|
1748
2907
|
}
|
|
1749
2908
|
last = box.x;
|
|
1750
|
-
|
|
1751
|
-
const aligned = own.alignSelf === AUTO ? style.alignItems : own.alignSelf;
|
|
1752
|
-
// an `auto` margin across the line takes it over alignment
|
|
1753
|
-
if (
|
|
1754
|
-
aligned === 'baseline' &&
|
|
1755
|
-
own.marginTop !== AUTO &&
|
|
1756
|
-
own.marginBottom !== AUTO
|
|
1757
|
-
) {
|
|
1758
|
-
line.push(item);
|
|
1759
|
-
}
|
|
2909
|
+
if (byBaseline(style, box.style)) line.push(item);
|
|
1760
2910
|
}
|
|
1761
2911
|
close();
|
|
1762
2912
|
// one alone on its line is at its start already
|
|
1763
2913
|
return several ? out : null;
|
|
1764
2914
|
}
|
|
1765
2915
|
|
|
2916
|
+
/** Whether an item takes part in aligning its line by baselines (CSS
|
|
2917
|
+
* Flexbox 8.3, 9.4 step 8): aligned by its baseline, with no `auto`
|
|
2918
|
+
* margin across the line, which takes it over its alignment. */
|
|
2919
|
+
function byBaseline(container: ComputedStyle, item: ComputedStyle): boolean {
|
|
2920
|
+
const aligned =
|
|
2921
|
+
item.alignSelf === AUTO ? container.alignItems : item.alignSelf;
|
|
2922
|
+
return (
|
|
2923
|
+
aligned === 'baseline' &&
|
|
2924
|
+
item.marginTop !== AUTO &&
|
|
2925
|
+
item.marginBottom !== AUTO
|
|
2926
|
+
);
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
/**
|
|
2930
|
+
* Whether Yoga made a line of a row that wraps the row's height, where its
|
|
2931
|
+
* items make it another (CSS Flexbox 9.4, steps 7 and 8; `layoutFlex`).
|
|
2932
|
+
* Where `align-content` stretches the lines, that is an item stretched
|
|
2933
|
+
* across one that is shorter than its content at the width it was given:
|
|
2934
|
+
* its line is as tall as that, past the box. Where it centres them or ends
|
|
2935
|
+
* them, it is the one line of the row not as tall as the row, which moves
|
|
2936
|
+
* it from the row's start — and so where it spaces them out around or
|
|
2937
|
+
* evenly, which centres one line (CSS Box Alignment 3, 5.1). Lines it
|
|
2938
|
+
* starts at the row's start are there in Yoga's layout as well, and so is
|
|
2939
|
+
* the one line `space-between` starts there.
|
|
2940
|
+
*/
|
|
2941
|
+
function heldLine(
|
|
2942
|
+
container: Box,
|
|
2943
|
+
ctx: LayoutContext,
|
|
2944
|
+
items: readonly { box: Box; node: YogaNode; laid: Laid }[],
|
|
2945
|
+
/** The row's content height. */
|
|
2946
|
+
height: number,
|
|
2947
|
+
/** The row's content width, the items' containing block's. */
|
|
2948
|
+
width: number,
|
|
2949
|
+
/** The gap between two items along the row. */
|
|
2950
|
+
gap: number,
|
|
2951
|
+
): boolean {
|
|
2952
|
+
const style = container.style;
|
|
2953
|
+
const lines = crossContent(style);
|
|
2954
|
+
if (
|
|
2955
|
+
lines !== 'stretch' &&
|
|
2956
|
+
lines !== 'center' &&
|
|
2957
|
+
lines !== 'flex-end' &&
|
|
2958
|
+
lines !== 'space-around' &&
|
|
2959
|
+
lines !== 'space-evenly'
|
|
2960
|
+
) {
|
|
2961
|
+
return false;
|
|
2962
|
+
}
|
|
2963
|
+
const stretched = items.filter(({ box }) => stretches(box, style));
|
|
2964
|
+
if (stretched.length === 0) return false;
|
|
2965
|
+
if (lines === 'stretch') {
|
|
2966
|
+
return stretched.some(
|
|
2967
|
+
({ box, node, laid }) =>
|
|
2968
|
+
naturalAcross(box, ctx, laid, width) > heightOf(node, laid) + 0.01,
|
|
2969
|
+
);
|
|
2970
|
+
}
|
|
2971
|
+
// one line, which the items come to no more than the row's width along:
|
|
2972
|
+
// Yoga made a line each of them only where they come to more
|
|
2973
|
+
let along = gap * (items.length - 1);
|
|
2974
|
+
for (const { box, laid } of items) along += laid.main + marginsOf(box, true);
|
|
2975
|
+
if (along > width + 0.01) return false;
|
|
2976
|
+
// as tall as its tallest item, as Yoga has them: it lays them out at
|
|
2977
|
+
// their content's height where `align-content` does not stretch lines
|
|
2978
|
+
let line = 0;
|
|
2979
|
+
for (const { box, node, laid } of items) {
|
|
2980
|
+
line = Math.max(line, heightOf(node, laid) + marginsOf(box, false));
|
|
2981
|
+
}
|
|
2982
|
+
return Math.abs(line - height) > 0.01;
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
/**
|
|
2986
|
+
* A row item's border-box height as its content makes it at the width the
|
|
2987
|
+
* flex layout gave it, within its limits: the layout `placeItems` gives it
|
|
2988
|
+
* there, made now where it has none (`layoutItemAt`), and a replaced one's
|
|
2989
|
+
* through its ratio or its natural height.
|
|
2990
|
+
*/
|
|
2991
|
+
function naturalAcross(
|
|
2992
|
+
box: Box,
|
|
2993
|
+
ctx: LayoutContext,
|
|
2994
|
+
laid: Laid,
|
|
2995
|
+
/** The flex box's content width, the item's containing block's. */
|
|
2996
|
+
containing: number,
|
|
2997
|
+
): number {
|
|
2998
|
+
const width = meant(laid.main, laid.set, laid.stretch, laid.width, laid.held);
|
|
2999
|
+
if (box.kind === 'replaced') {
|
|
3000
|
+
const own = box.intrinsic;
|
|
3001
|
+
const natural =
|
|
3002
|
+
heightThroughRatio(box, width) ??
|
|
3003
|
+
(own && !(own.missing & 2) ? own.height + box.verticalExtra : null);
|
|
3004
|
+
return natural === null ? 0 : clampHeight(box, natural);
|
|
3005
|
+
}
|
|
3006
|
+
if (box.kind === 'text' || box.kind === 'break') return 0;
|
|
3007
|
+
const kept = naturalHeight(box, ctx, width, containing, laid);
|
|
3008
|
+
if (!Number.isNaN(kept)) return kept;
|
|
3009
|
+
layoutNaturally(box, ctx, width, containing, laid);
|
|
3010
|
+
return box.height;
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
/**
|
|
3014
|
+
* The alignment an item with an `auto` margin across a row that wraps comes
|
|
3015
|
+
* to in its line (CSS Flexbox 8.1, 9.6 step 13), as Yoga is told it
|
|
3016
|
+
* (`layoutFlex`): at the end where the margin at the line's cross start is
|
|
3017
|
+
* `auto`, at the start where the one at its end is, and centred where both
|
|
3018
|
+
* are; null where neither is. Its line's cross start is the box's bottom
|
|
3019
|
+
* where its lines wrap in reverse.
|
|
3020
|
+
*
|
|
3021
|
+
* The line is as tall as its tallest item, margins and all, so the item
|
|
3022
|
+
* comes to no more than it, and the margins take a share of no less than
|
|
3023
|
+
* nothing: the alignment puts it where they would.
|
|
3024
|
+
*/
|
|
3025
|
+
function autoAcross(
|
|
3026
|
+
item: ComputedStyle,
|
|
3027
|
+
reverse: boolean,
|
|
3028
|
+
): 'flex-start' | 'flex-end' | 'center' | null {
|
|
3029
|
+
const top = item.marginTop === AUTO;
|
|
3030
|
+
const bottom = item.marginBottom === AUTO;
|
|
3031
|
+
if (top && bottom) return 'center';
|
|
3032
|
+
if (!top && !bottom) return null;
|
|
3033
|
+
return top !== reverse ? 'flex-end' : 'flex-start';
|
|
3034
|
+
}
|
|
3035
|
+
|
|
1766
3036
|
/**
|
|
1767
3037
|
* How far an item is from where it belongs across its line, where Yoga
|
|
1768
3038
|
* (3.2.1) aligned the lines in the pass it takes for a box that wraps or
|
|
@@ -1771,47 +3041,349 @@ function baselineLines<T extends { box: Box }>(
|
|
|
1771
3041
|
* none at either — `items-start` in a wrapping row put every card's top
|
|
1772
3042
|
* margin under it. By the margin at the start of the line's cross axis, and
|
|
1773
3043
|
* by half what it exceeds the one at the end by, as Yoga holds them: for an
|
|
1774
|
-
* item set at its line's start (`started`), its baseline's.
|
|
1775
|
-
*
|
|
1776
|
-
*
|
|
3044
|
+
* item set at its line's start (`started`), its baseline's.
|
|
3045
|
+
*
|
|
3046
|
+
* A row whose lines wrap in reverse Yoga lays out as though they wrapped
|
|
3047
|
+
* forward, and then turns each item over across the box, its margins with
|
|
3048
|
+
* it: each is where the other margin would have put it. One at its line's
|
|
3049
|
+
* start, the bottom, stands on nothing; one at its end, the top, or
|
|
3050
|
+
* stretched, is its bottom margin down from its line's top where it is its
|
|
3051
|
+
* top one; and one centred, half the difference. A column's, whose cross
|
|
3052
|
+
* 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 in a row that does not
|
|
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`).
|
|
1777
3056
|
*/
|
|
1778
3057
|
function lineMarginFix(
|
|
1779
3058
|
container: ComputedStyle,
|
|
1780
3059
|
item: Box,
|
|
1781
3060
|
node: YogaNode,
|
|
1782
3061
|
row: boolean,
|
|
3062
|
+
/** Whether the flex box has a definite height. */
|
|
3063
|
+
own: boolean,
|
|
1783
3064
|
started = false,
|
|
1784
3065
|
): number {
|
|
1785
|
-
|
|
1786
|
-
if (!row && container.direction === 'rtl') return 0;
|
|
3066
|
+
const reverse = container.flexWrap === 'wrap-reverse';
|
|
3067
|
+
if (!row && (reverse || container.direction === 'rtl')) return 0;
|
|
1787
3068
|
const style = item.style;
|
|
1788
|
-
// a margin that is `auto` takes the item over its alignment
|
|
3069
|
+
// a margin that is `auto` takes the item over its alignment, which Yoga
|
|
3070
|
+
// is told it comes to in a row that wraps (`autoAcross`)
|
|
1789
3071
|
const lead = row ? style.marginTop : style.marginLeft;
|
|
1790
3072
|
const trail = row ? style.marginBottom : style.marginRight;
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
3073
|
+
let align: string | null;
|
|
3074
|
+
if (started) align = 'flex-start';
|
|
3075
|
+
else if (lead === AUTO || trail === AUTO) {
|
|
3076
|
+
align =
|
|
3077
|
+
row && container.flexWrap !== 'nowrap'
|
|
3078
|
+
? autoAcross(style, reverse)
|
|
3079
|
+
: null;
|
|
3080
|
+
} else {
|
|
3081
|
+
align = style.alignSelf === AUTO ? container.alignItems : style.alignSelf;
|
|
3082
|
+
// which sets one whose height is a percentage of none at its line's
|
|
3083
|
+
// start, as Yoga is told it does (`applyItem`)
|
|
3084
|
+
if (align === 'stretch' && row && heightOfNone(style, own)) {
|
|
3085
|
+
align = 'flex-start';
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
1798
3088
|
const leading = node.getComputedMargin(row ? Y.EDGE_TOP : Y.EDGE_LEFT);
|
|
1799
|
-
if (align === 'flex-start') return leading;
|
|
1800
3089
|
const trailing = node.getComputedMargin(row ? Y.EDGE_BOTTOM : Y.EDGE_RIGHT);
|
|
1801
|
-
|
|
3090
|
+
switch (align) {
|
|
3091
|
+
case 'flex-start':
|
|
3092
|
+
return reverse ? -trailing : leading;
|
|
3093
|
+
case 'center':
|
|
3094
|
+
return (leading - trailing) / 2;
|
|
3095
|
+
case 'flex-end':
|
|
3096
|
+
case 'stretch':
|
|
3097
|
+
return reverse ? leading - trailing : 0;
|
|
3098
|
+
}
|
|
3099
|
+
return 0;
|
|
1802
3100
|
}
|
|
1803
3101
|
|
|
1804
3102
|
/** An item's first baseline, down from its border edge: its content's
|
|
1805
3103
|
* first line's, or where it has none, or is replaced, one synthesized
|
|
1806
|
-
* from its border box — its bottom edge.
|
|
3104
|
+
* from its border box — its bottom edge. Past that edge where the line
|
|
3105
|
+
* is: only a scroll container's is held to its box (`firstBaselineIn`),
|
|
3106
|
+
* and an item shorter than its text, or whose padding puts the text
|
|
3107
|
+
* under it, sits on its text where it had sat on its bottom. */
|
|
1807
3108
|
function itemBaseline(item: Box): number {
|
|
1808
3109
|
if (item.kind !== 'replaced') {
|
|
1809
3110
|
const found = firstBaselineIn(item);
|
|
1810
|
-
if (found !== null) return
|
|
3111
|
+
if (found !== null) return found - item.y;
|
|
1811
3112
|
}
|
|
1812
3113
|
return item.height;
|
|
1813
3114
|
}
|
|
1814
3115
|
|
|
3116
|
+
/**
|
|
3117
|
+
* The items of a column that wraps that Yoga (3.2.1) lays out at another
|
|
3118
|
+
* height than it sized them at, as it stretches them across their lines:
|
|
3119
|
+
* each it aligns `stretch`, with no width of its own, whose left and right
|
|
3120
|
+
* margins come to another sum than its top and bottom ones.
|
|
3121
|
+
*
|
|
3122
|
+
* Yoga aligns the lines of a box that wraps in a pass of its own, after it
|
|
3123
|
+
* has sized and placed every item down them, and lays each item it
|
|
3124
|
+
* stretches out again there at its line's width — and at its height plus
|
|
3125
|
+
* the margins across the line, where it means the ones down it
|
|
3126
|
+
* (CalculateLayout.cpp's multi-line `Align::Stretch`, still so on Yoga's
|
|
3127
|
+
* main). A leaf laid out at a width and a height is that size, so the item
|
|
3128
|
+
* came out its side margins taller and its top and bottom ones shorter
|
|
3129
|
+
* than the height its line was shared out to, which its place and the
|
|
3130
|
+
* next item's had been worked out from. The pass lays an item out again
|
|
3131
|
+
* wherever its side margins are not nothing, or its line is another width
|
|
3132
|
+
* than the one it was measured at, so `margin: 0 5px` on a line of words
|
|
3133
|
+
* was 28 tall where Chrome's is 18, and `margin: 4px 0 2px` in a column
|
|
3134
|
+
* with two lines 6 short; where the two sums are the same, the pass gives
|
|
3135
|
+
* the height back unchanged.
|
|
3136
|
+
*/
|
|
3137
|
+
function stretchedDown(
|
|
3138
|
+
root: YogaNode,
|
|
3139
|
+
items: readonly { box: Box; node: YogaNode; laid: Laid }[],
|
|
3140
|
+
): { box: Box; node: YogaNode; laid: Laid }[] {
|
|
3141
|
+
const lines = root.getAlignItems();
|
|
3142
|
+
return items.filter(({ box, node }) => {
|
|
3143
|
+
const own = node.getAlignSelf();
|
|
3144
|
+
if ((own === Y.ALIGN_AUTO ? lines : own) !== Y.ALIGN_STRETCH) return false;
|
|
3145
|
+
const width = node.getWidth().unit;
|
|
3146
|
+
if (width !== Y.UNIT_AUTO && width !== Y.UNIT_UNDEFINED) return false;
|
|
3147
|
+
// as Yoga adds them up, an `auto` margin as nothing
|
|
3148
|
+
const style = box.style;
|
|
3149
|
+
const of = (len: unknown, px: number) => (len === AUTO ? 0 : px);
|
|
3150
|
+
const across =
|
|
3151
|
+
of(style.marginLeft, box.marginLeft) +
|
|
3152
|
+
of(style.marginRight, box.marginRight);
|
|
3153
|
+
const down =
|
|
3154
|
+
of(style.marginTop, box.marginTop) +
|
|
3155
|
+
of(style.marginBottom, box.marginBottom);
|
|
3156
|
+
return Math.abs(across - down) >= 0.0001;
|
|
3157
|
+
});
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
/**
|
|
3161
|
+
* Lay a column that wraps out, keeping for each item Yoga stretches across
|
|
3162
|
+
* its line the height it sized it at (`stretchedDown`, `Laid.unstretched`).
|
|
3163
|
+
*
|
|
3164
|
+
* Yoga is asked again with each such item set at its line's start, which
|
|
3165
|
+
* its multi-line pass does not lay out again: each is as tall as its line
|
|
3166
|
+
* made it. Its flex base size has to come out the same for that, so its
|
|
3167
|
+
* lines break and share out the same — and where Yoga hands the column's
|
|
3168
|
+
* width over as a size, it measures an item it stretches at that width, and
|
|
3169
|
+
* one it does not at its content's, so there the item is handed the width
|
|
3170
|
+
* as its own too. Then Yoga is asked once more as it was, so the lines and
|
|
3171
|
+
* the widths are its own, and only the heights are taken from the second
|
|
3172
|
+
* layout. Where an item's place down its line moved between the two, a
|
|
3173
|
+
* base moved too, and Yoga's height is kept, as it was.
|
|
3174
|
+
*/
|
|
3175
|
+
function unstretch(
|
|
3176
|
+
items: readonly { box: Box; node: YogaNode; laid: Laid }[],
|
|
3177
|
+
stretched: readonly { box: Box; node: YogaNode; laid: Laid }[],
|
|
3178
|
+
/** The column's content width, where Yoga is handed it as a size, and
|
|
3179
|
+
* else null. */
|
|
3180
|
+
width: number | null,
|
|
3181
|
+
layout: () => void,
|
|
3182
|
+
): void {
|
|
3183
|
+
layout();
|
|
3184
|
+
const tops = items.map(({ node }) => node.getComputedTop());
|
|
3185
|
+
const aligned = stretched.map(({ box, node }) => {
|
|
3186
|
+
const own = node.getAlignSelf();
|
|
3187
|
+
node.setAlignSelf(Y.ALIGN_FLEX_START);
|
|
3188
|
+
if (width !== null) node.setWidth(measuredAcross(box, node, width));
|
|
3189
|
+
return own;
|
|
3190
|
+
});
|
|
3191
|
+
layout();
|
|
3192
|
+
const kept = items.every(
|
|
3193
|
+
({ node }, i) => Math.abs(node.getComputedTop() - tops[i]) < 0.01,
|
|
3194
|
+
);
|
|
3195
|
+
const heights = stretched.map(({ node }) => node.getComputedHeight());
|
|
3196
|
+
stretched.forEach(({ node }, i) => {
|
|
3197
|
+
node.setAlignSelf(aligned[i]);
|
|
3198
|
+
if (width !== null) node.setWidthAuto();
|
|
3199
|
+
});
|
|
3200
|
+
layout();
|
|
3201
|
+
stretched.forEach(({ laid }, i) => {
|
|
3202
|
+
laid.unstretched = kept ? heights[i] : NaN;
|
|
3203
|
+
});
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
/** The border-box width Yoga measures a column's item at for its flex base
|
|
3207
|
+
* size where it stretches it across a column of a width: the column's,
|
|
3208
|
+
* less its margins across, within its maximum. */
|
|
3209
|
+
function measuredAcross(box: Box, node: YogaNode, width: number): number {
|
|
3210
|
+
const style = box.style;
|
|
3211
|
+
const of = (len: unknown, px: number) => (len === AUTO ? 0 : px);
|
|
3212
|
+
const across =
|
|
3213
|
+
of(style.marginLeft, box.marginLeft) +
|
|
3214
|
+
of(style.marginRight, box.marginRight);
|
|
3215
|
+
const most = node.getMaxWidth();
|
|
3216
|
+
const limit =
|
|
3217
|
+
most.unit === Y.UNIT_POINT
|
|
3218
|
+
? most.value
|
|
3219
|
+
: most.unit === Y.UNIT_PERCENT
|
|
3220
|
+
? (most.value / 100) * width
|
|
3221
|
+
: Infinity;
|
|
3222
|
+
return Math.min(width - across, limit);
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
/** An item's border-box height, as Yoga sized it: the one it had before its
|
|
3226
|
+
* line stretched it, where that laid it out at another (`unstretch`,
|
|
3227
|
+
* `unspace`). */
|
|
3228
|
+
function heightOf(node: YogaNode, laid: Laid): number {
|
|
3229
|
+
return Number.isNaN(laid.unstretched)
|
|
3230
|
+
? node.getComputedHeight()
|
|
3231
|
+
: laid.unstretched;
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
/** Whether `align-content` spaces a flex box's lines out, putting what
|
|
3235
|
+
* room it has past them between them and around them. */
|
|
3236
|
+
function spacesLines(style: ComputedStyle): boolean {
|
|
3237
|
+
const lines = crossContent(style);
|
|
3238
|
+
return (
|
|
3239
|
+
lines === 'space-between' ||
|
|
3240
|
+
lines === 'space-around' ||
|
|
3241
|
+
lines === 'space-evenly'
|
|
3242
|
+
);
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
/**
|
|
3246
|
+
* Lay a row that wraps out, its lines spaced out by `align-content`,
|
|
3247
|
+
* keeping each item Yoga stretches across its line as tall as the line
|
|
3248
|
+
* (`Laid.unstretched`; CSS Flexbox 9.4, step 11).
|
|
3249
|
+
*
|
|
3250
|
+
* Yoga (3.2.1) sets the lines of a box that wraps in a pass of its own,
|
|
3251
|
+
* after it has sized them, and lays each item it stretches out again there
|
|
3252
|
+
* as tall as its line and the room it puts after the line as well
|
|
3253
|
+
* (CalculateLayout.cpp's multi-line `Align::Stretch`, still so on Yoga's
|
|
3254
|
+
* main). Each line was where it belongs, and every item on it that is not
|
|
3255
|
+
* stretched, but a stretched item ran down to the next line: two lines of
|
|
3256
|
+
* 18px items in a row 100 tall came to 82 apiece with `space-between`. So
|
|
3257
|
+
* Yoga is asked with the lines set at the row's start, where it puts no
|
|
3258
|
+
* room between them and each item is as tall as its line. Where the lines
|
|
3259
|
+
* leave the row no room (`linesReach`), that is the layout the spacing
|
|
3260
|
+
* makes too, and is kept. Where they do, Yoga is asked again as the row
|
|
3261
|
+
* is, for where the lines and the items are, and the heights are kept from
|
|
3262
|
+
* the first layout.
|
|
3263
|
+
*
|
|
3264
|
+
* And where the lines are taller than the row, `space-around` and
|
|
3265
|
+
* `space-evenly` fall back to `safe center`, which sets them at the row's
|
|
3266
|
+
* own start, its top (CSS Box Alignment 3, 4.3 and 5.1; CSS Flexbox 8.4).
|
|
3267
|
+
* Yoga falls back to `flex-start` for both, which is the bottom where the
|
|
3268
|
+
* lines run bottom to top, and ran them out past the row's top. It sets
|
|
3269
|
+
* the lines of a row that wraps in reverse top to bottom and turns them
|
|
3270
|
+
* over once they are set, so it is asked for them at `flex-end` there,
|
|
3271
|
+
* which comes out at the top — where the lines it set at their start, or
|
|
3272
|
+
* spaced, reach past the row's bottom.
|
|
3273
|
+
*/
|
|
3274
|
+
function unspace(
|
|
3275
|
+
root: YogaNode,
|
|
3276
|
+
items: readonly { box: Box; node: YogaNode; laid: Laid }[],
|
|
3277
|
+
/** Whether the lines run bottom to top. */
|
|
3278
|
+
reverse: boolean,
|
|
3279
|
+
layout: () => void,
|
|
3280
|
+
): void {
|
|
3281
|
+
for (const { laid } of items) laid.unstretched = NaN;
|
|
3282
|
+
const lines = root.getAlignContent();
|
|
3283
|
+
// where the lines start at the row's bottom, its start is their end
|
|
3284
|
+
const safe =
|
|
3285
|
+
reverse &&
|
|
3286
|
+
(lines === Y.ALIGN_SPACE_AROUND || lines === Y.ALIGN_SPACE_EVENLY);
|
|
3287
|
+
const atEnd = (): void => {
|
|
3288
|
+
root.setAlignContent(Y.ALIGN_FLEX_END);
|
|
3289
|
+
layout();
|
|
3290
|
+
root.setAlignContent(lines);
|
|
3291
|
+
};
|
|
3292
|
+
// each item Yoga stretches: `stretch`, with no height of its own
|
|
3293
|
+
const align = root.getAlignItems();
|
|
3294
|
+
const stretched = items.filter(({ node }) => {
|
|
3295
|
+
const own = node.getAlignSelf();
|
|
3296
|
+
if ((own === Y.ALIGN_AUTO ? align : own) !== Y.ALIGN_STRETCH) return false;
|
|
3297
|
+
const height = node.getHeight().unit;
|
|
3298
|
+
return height === Y.UNIT_AUTO || height === Y.UNIT_UNDEFINED;
|
|
3299
|
+
});
|
|
3300
|
+
if (stretched.length === 0) {
|
|
3301
|
+
layout();
|
|
3302
|
+
// spaced where there is room, and else at their start, past the bottom
|
|
3303
|
+
if (
|
|
3304
|
+
safe &&
|
|
3305
|
+
linesReach(root, items, reverse) - root.getComputedHeight() > NEAR
|
|
3306
|
+
) {
|
|
3307
|
+
atEnd();
|
|
3308
|
+
}
|
|
3309
|
+
return;
|
|
3310
|
+
}
|
|
3311
|
+
root.setAlignContent(Y.ALIGN_FLEX_START);
|
|
3312
|
+
layout();
|
|
3313
|
+
root.setAlignContent(lines);
|
|
3314
|
+
const room = root.getComputedHeight() - linesReach(root, items, reverse);
|
|
3315
|
+
if (safe && room < -NEAR) {
|
|
3316
|
+
atEnd();
|
|
3317
|
+
return;
|
|
3318
|
+
}
|
|
3319
|
+
if (!(room > NEAR)) return;
|
|
3320
|
+
const heights = stretched.map(({ node }) => node.getComputedHeight());
|
|
3321
|
+
layout();
|
|
3322
|
+
stretched.forEach(({ node, laid }, i) => {
|
|
3323
|
+
if (Math.abs(node.getComputedHeight() - heights[i]) >= NEAR) {
|
|
3324
|
+
laid.unstretched = heights[i];
|
|
3325
|
+
}
|
|
3326
|
+
});
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
/**
|
|
3330
|
+
* How far down a row that wraps its lines reach, at the least, in the
|
|
3331
|
+
* layout Yoga made of it with the lines at its start (`unspace`): the most,
|
|
3332
|
+
* over its items, of where the item's line ends at the least. An item is as
|
|
3333
|
+
* tall as its line with its margins where it is stretched across it, and
|
|
3334
|
+
* no taller where it is not, and Yoga ends it at its line's end, or with
|
|
3335
|
+
* its margins at the line's start, or centres it with neither
|
|
3336
|
+
* (`lineMarginFix`). Read with the lines turned back the right way up where
|
|
3337
|
+
* they run bottom to top.
|
|
3338
|
+
*/
|
|
3339
|
+
function linesReach(
|
|
3340
|
+
root: YogaNode,
|
|
3341
|
+
items: readonly { box: Box; node: YogaNode }[],
|
|
3342
|
+
reverse: boolean,
|
|
3343
|
+
): number {
|
|
3344
|
+
const height = root.getComputedHeight();
|
|
3345
|
+
const lines = root.getAlignItems();
|
|
3346
|
+
let reach = 0;
|
|
3347
|
+
for (const { box, node } of items) {
|
|
3348
|
+
const tall = node.getComputedHeight();
|
|
3349
|
+
const top = reverse
|
|
3350
|
+
? height - node.getComputedTop() - tall
|
|
3351
|
+
: node.getComputedTop();
|
|
3352
|
+
const style = box.style;
|
|
3353
|
+
const above = style.marginTop === AUTO ? 0 : box.marginTop;
|
|
3354
|
+
const below = style.marginBottom === AUTO ? 0 : box.marginBottom;
|
|
3355
|
+
const own = node.getAlignSelf();
|
|
3356
|
+
const align = own === Y.ALIGN_AUTO ? lines : own;
|
|
3357
|
+
// where Yoga set it at its line's start with no margin above it, the
|
|
3358
|
+
// line runs on past it by both its margins at the least, and where it
|
|
3359
|
+
// centred it, by half of them
|
|
3360
|
+
const lead =
|
|
3361
|
+
align === Y.ALIGN_FLEX_START
|
|
3362
|
+
? above
|
|
3363
|
+
: align === Y.ALIGN_CENTER
|
|
3364
|
+
? (above - below) / 2
|
|
3365
|
+
: 0;
|
|
3366
|
+
reach = Math.max(reach, top + tall + below + lead);
|
|
3367
|
+
}
|
|
3368
|
+
return reach;
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
/**
|
|
3372
|
+
* Whether a row item's height is a percentage of a height its flex box has
|
|
3373
|
+
* none of. That is `auto` to the item's size (CSS 2.1 10.5), and is not
|
|
3374
|
+
* handed to Yoga (`applyItem`), but it is not `auto` to its alignment:
|
|
3375
|
+
* `stretch` stretches an item whose height computes to `auto` (CSS Flexbox
|
|
3376
|
+
* 8.3), and this one's computes to the percentage. Such an item is as tall
|
|
3377
|
+
* as its content, set at its line's start, which is what Yoga is told.
|
|
3378
|
+
*/
|
|
3379
|
+
function heightOfNone(
|
|
3380
|
+
style: ComputedStyle,
|
|
3381
|
+
/** Whether the flex box has a definite height. */
|
|
3382
|
+
own: boolean,
|
|
3383
|
+
): boolean {
|
|
3384
|
+
return !own && isPct(style.height);
|
|
3385
|
+
}
|
|
3386
|
+
|
|
1815
3387
|
/** Whether an item is stretched across its line: `stretch`, its own or its
|
|
1816
3388
|
* container's, with no height of its own and no `auto` margin across. */
|
|
1817
3389
|
function stretches(
|
|
@@ -1834,6 +3406,163 @@ function stretches(
|
|
|
1834
3406
|
style.marginRight !== AUTO;
|
|
1835
3407
|
}
|
|
1836
3408
|
|
|
3409
|
+
/** Whether a flex box laid out at a width is a column that wraps, whose
|
|
3410
|
+
* lines are as wide as their items and not as the box, and are placed
|
|
3411
|
+
* across it here (`crossLines`). */
|
|
3412
|
+
function wrapsColumn(style: ComputedStyle, width: number): boolean {
|
|
3413
|
+
return (
|
|
3414
|
+
Number.isFinite(width) &&
|
|
3415
|
+
!style.flexDirection.startsWith('row') &&
|
|
3416
|
+
style.flexWrap !== 'nowrap'
|
|
3417
|
+
);
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
/** Where an item is across a column that wraps (`crossLines`): its border
|
|
3421
|
+
* box's left edge from the flex box's content box's, and its width. */
|
|
3422
|
+
interface Across {
|
|
3423
|
+
x: number;
|
|
3424
|
+
width: number;
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
/**
|
|
3428
|
+
* Place a column that wraps across: its lines across the box, and each
|
|
3429
|
+
* item across its line (CSS Flexbox 9.4, steps 8 to 11, and 9.6, steps 13
|
|
3430
|
+
* to 16). From the layout Yoga made of it with every item at its line's
|
|
3431
|
+
* start (`layoutFlex`), in which each item is as wide as it is before it is
|
|
3432
|
+
* stretched, `fit-content` or its own width within its limits, and each
|
|
3433
|
+
* line starts its gap and a pixel past the one before, which is how the
|
|
3434
|
+
* items of one line are told from the next one's.
|
|
3435
|
+
*
|
|
3436
|
+
* A line is as wide as its widest item, margins and all. `align-content`
|
|
3437
|
+
* (its `start` and `end` the box's own sides, `crossContent`) shares out
|
|
3438
|
+
* what the box has past its lines: `stretch` to each line alike,
|
|
3439
|
+
* where they are short of the box; `center` and `flex-end` whether they are
|
|
3440
|
+
* or not, so that lines wider than the box run past both its sides, or its
|
|
3441
|
+
* start; and the spacing values only where there is room, setting the lines
|
|
3442
|
+
* at their start where there is none, or at the box's (CSS Box Alignment 3,
|
|
3443
|
+
* 4.3: `space-between` falls back to `flex-start`, `space-around` and
|
|
3444
|
+
* `space-evenly` to `safe center`, the box's own start where the lines
|
|
3445
|
+
* overflow, and not theirs where they wrap in reverse). An
|
|
3446
|
+
* item with an `auto` margin across its line takes the room the line has
|
|
3447
|
+
* past it on that side (8.1), and none where there is none; a stretched
|
|
3448
|
+
* 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; and the rest are where
|
|
3450
|
+
* `align-self` says, a baseline at the line's start, as a column has none
|
|
3451
|
+
* across it to align by.
|
|
3452
|
+
*/
|
|
3453
|
+
function crossLines(
|
|
3454
|
+
style: ComputedStyle,
|
|
3455
|
+
items: readonly { box: Box; node: YogaNode; laid: Laid }[],
|
|
3456
|
+
/** The flex box's content width. */
|
|
3457
|
+
width: number,
|
|
3458
|
+
/** The gap between two lines. */
|
|
3459
|
+
gap: number,
|
|
3460
|
+
): Map<Box, Across> {
|
|
3461
|
+
// the lines start at the right where the box runs right to left, or
|
|
3462
|
+
// where they wrap in reverse, and at the left where both are so
|
|
3463
|
+
const fromRight =
|
|
3464
|
+
(style.direction === 'rtl') !== (style.flexWrap === 'wrap-reverse');
|
|
3465
|
+
const lines: number[][] = [];
|
|
3466
|
+
const sizes: number[] = [];
|
|
3467
|
+
// each item's margins across, from its line's start, an `auto` one none
|
|
3468
|
+
const lead: number[] = [];
|
|
3469
|
+
const trail: number[] = [];
|
|
3470
|
+
let last = NaN;
|
|
3471
|
+
items.forEach(({ box, node, laid }, i) => {
|
|
3472
|
+
const yoga = node.getComputedWidth();
|
|
3473
|
+
const left = node.getComputedLeft();
|
|
3474
|
+
const start = fromRight ? width - left - yoga : left;
|
|
3475
|
+
if (!(Math.abs(start - last) < 0.5)) lines.push([]);
|
|
3476
|
+
last = start;
|
|
3477
|
+
lines[lines.length - 1].push(i);
|
|
3478
|
+
sizes.push(meant(yoga, laid.set, laid.stretch, laid.width));
|
|
3479
|
+
const own = box.style;
|
|
3480
|
+
const l = own.marginLeft === AUTO ? 0 : box.marginLeft;
|
|
3481
|
+
const r = own.marginRight === AUTO ? 0 : box.marginRight;
|
|
3482
|
+
lead.push(fromRight ? r : l);
|
|
3483
|
+
trail.push(fromRight ? l : r);
|
|
3484
|
+
});
|
|
3485
|
+
const across = lines.map((line) =>
|
|
3486
|
+
line.reduce((most, i) => Math.max(most, sizes[i] + lead[i] + trail[i]), 0),
|
|
3487
|
+
);
|
|
3488
|
+
const count = lines.length;
|
|
3489
|
+
let room = width - gap * (count - 1);
|
|
3490
|
+
for (const size of across) room -= size;
|
|
3491
|
+
let at = 0;
|
|
3492
|
+
let between = gap;
|
|
3493
|
+
// Where they are wider than the box, `space-around` and `space-evenly`
|
|
3494
|
+
// set the lines at the box's own start (`safe center`), which is where
|
|
3495
|
+
// their last one ends where they wrap in reverse: they run past its end
|
|
3496
|
+
const reverse = style.flexWrap === 'wrap-reverse';
|
|
3497
|
+
switch (crossContent(style)) {
|
|
3498
|
+
case 'flex-end':
|
|
3499
|
+
at = room;
|
|
3500
|
+
break;
|
|
3501
|
+
case 'center':
|
|
3502
|
+
at = room / 2;
|
|
3503
|
+
break;
|
|
3504
|
+
case 'stretch':
|
|
3505
|
+
if (room > 0) across.forEach((_, k) => (across[k] += room / count));
|
|
3506
|
+
break;
|
|
3507
|
+
case 'space-between':
|
|
3508
|
+
if (room > 0 && count > 1) between += room / (count - 1);
|
|
3509
|
+
break;
|
|
3510
|
+
case 'space-around':
|
|
3511
|
+
if (room > 0) {
|
|
3512
|
+
at = room / count / 2;
|
|
3513
|
+
between += room / count;
|
|
3514
|
+
} else if (reverse) at = room;
|
|
3515
|
+
break;
|
|
3516
|
+
case 'space-evenly':
|
|
3517
|
+
if (room > 0) {
|
|
3518
|
+
at = room / (count + 1);
|
|
3519
|
+
between += room / (count + 1);
|
|
3520
|
+
} else if (reverse) at = room;
|
|
3521
|
+
break;
|
|
3522
|
+
}
|
|
3523
|
+
const placed = new Map<Box, Across>();
|
|
3524
|
+
lines.forEach((line, k) => {
|
|
3525
|
+
const size = across[k];
|
|
3526
|
+
for (const i of line) {
|
|
3527
|
+
const { box, node } = items[i];
|
|
3528
|
+
const own = box.style;
|
|
3529
|
+
let itemWidth = sizes[i];
|
|
3530
|
+
let offset = lead[i];
|
|
3531
|
+
const free = size - itemWidth - lead[i] - trail[i];
|
|
3532
|
+
const before = fromRight ? own.marginRight : own.marginLeft;
|
|
3533
|
+
const after = fromRight ? own.marginLeft : own.marginRight;
|
|
3534
|
+
if (before === AUTO || after === AUTO) {
|
|
3535
|
+
if (free > 0 && before === AUTO) {
|
|
3536
|
+
offset += after === AUTO ? free / 2 : free;
|
|
3537
|
+
}
|
|
3538
|
+
} else if (stretches(box, style, false) && !own.widthKeyword) {
|
|
3539
|
+
const most = pointsOf(node.getMaxWidth());
|
|
3540
|
+
const least = pointsOf(node.getMinWidth());
|
|
3541
|
+
itemWidth = size - lead[i] - trail[i];
|
|
3542
|
+
if (itemWidth > most) itemWidth = most;
|
|
3543
|
+
if (itemWidth < least) itemWidth = least;
|
|
3544
|
+
itemWidth = Math.max(itemWidth, box.horizontalExtra);
|
|
3545
|
+
} else {
|
|
3546
|
+
const align = own.alignSelf === AUTO ? style.alignItems : own.alignSelf;
|
|
3547
|
+
if (align === 'center') offset += free / 2;
|
|
3548
|
+
else if (align === 'flex-end') offset += free;
|
|
3549
|
+
}
|
|
3550
|
+
const start = at + offset;
|
|
3551
|
+
placed.set(box, {
|
|
3552
|
+
x: fromRight ? width - start - itemWidth : start,
|
|
3553
|
+
width: itemWidth,
|
|
3554
|
+
});
|
|
3555
|
+
}
|
|
3556
|
+
at += size + between;
|
|
3557
|
+
});
|
|
3558
|
+
return placed;
|
|
3559
|
+
}
|
|
3560
|
+
|
|
3561
|
+
/** A length Yoga holds in points, or NaN where it holds none. */
|
|
3562
|
+
function pointsOf(value: { unit: number; value: number }): number {
|
|
3563
|
+
return value.unit === Y.UNIT_POINT ? value.value : NaN;
|
|
3564
|
+
}
|
|
3565
|
+
|
|
1837
3566
|
/** How deep flex boxes are laid out by Yoga, one inside another's measure
|
|
1838
3567
|
* (`layoutFlex`). */
|
|
1839
3568
|
const FLEX_DEPTH = 64;
|
|
@@ -1896,6 +3625,20 @@ function mainJustify(style: ComputedStyle): string {
|
|
|
1896
3625
|
return start !== reverse ? 'flex-start' : 'flex-end';
|
|
1897
3626
|
}
|
|
1898
3627
|
|
|
3628
|
+
/**
|
|
3629
|
+
* An `align-content` as the cross axis takes it (CSS Box Alignment 3,
|
|
3630
|
+
* 5.1): `start` and `end` are the box's own edges, which are its lines'
|
|
3631
|
+
* cross ends turned round where the box wraps in reverse — `end` in a row
|
|
3632
|
+
* `wrap-reverse` is the bottom, where its lines start.
|
|
3633
|
+
*/
|
|
3634
|
+
function crossContent(style: ComputedStyle): string {
|
|
3635
|
+
const lines = style.alignContent;
|
|
3636
|
+
if (lines !== 'start' && lines !== 'end') return lines;
|
|
3637
|
+
return (lines === 'start') === (style.flexWrap !== 'wrap-reverse')
|
|
3638
|
+
? 'flex-start'
|
|
3639
|
+
: 'flex-end';
|
|
3640
|
+
}
|
|
3641
|
+
|
|
1899
3642
|
const JUSTIFY: Record<string, number> = {
|
|
1900
3643
|
'flex-start': Y?.JUSTIFY_FLEX_START,
|
|
1901
3644
|
'flex-end': Y?.JUSTIFY_FLEX_END,
|
|
@@ -1913,4 +3656,5 @@ const ALIGN: Record<string, number> = {
|
|
|
1913
3656
|
baseline: Y?.ALIGN_BASELINE,
|
|
1914
3657
|
'space-between': Y?.ALIGN_SPACE_BETWEEN,
|
|
1915
3658
|
'space-around': Y?.ALIGN_SPACE_AROUND,
|
|
3659
|
+
'space-evenly': Y?.ALIGN_SPACE_EVENLY,
|
|
1916
3660
|
};
|