@react-x11/components 0.16.0 → 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/html/css/ua.js +12 -8
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +13 -0
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +28 -0
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/flex.d.ts.map +1 -1
- package/dist/html/layout/flex.js +139 -28
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +41 -11
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +4 -0
- package/dist/html/node.js.map +1 -1
- package/package.json +1 -1
- package/src/html/css/ua.ts +12 -8
- package/src/html/layout/boxes.ts +40 -0
- package/src/html/layout/flex.ts +146 -33
- package/src/html/layout/inline.ts +43 -9
- package/src/html/node.ts +4 -0
package/src/html/layout/boxes.ts
CHANGED
|
@@ -562,6 +562,40 @@ export type ReplacedKind =
|
|
|
562
562
|
/** An inline `<svg>`: a drawing, sized by what it says of its size. */
|
|
563
563
|
| 'svg';
|
|
564
564
|
|
|
565
|
+
/**
|
|
566
|
+
* Where a control sits on the line, as Chrome sets it: a field, a select
|
|
567
|
+
* or an input button on the baseline of the text it shows, its line in its
|
|
568
|
+
* face centred in its content box — that face's ascent and descent — and a
|
|
569
|
+
* checkbox, a radio button or a range on its border box's bottom, its
|
|
570
|
+
* bottom margin under the line. A `<textarea>` is none of these, and sits
|
|
571
|
+
* on its bottom margin edge as a box that scrolls does.
|
|
572
|
+
*/
|
|
573
|
+
export const CONTROL_BASELINES = new WeakMap<
|
|
574
|
+
Box,
|
|
575
|
+
InlineDecoration | 'border'
|
|
576
|
+
>();
|
|
577
|
+
|
|
578
|
+
function controlBaseline(
|
|
579
|
+
el: Element,
|
|
580
|
+
kind: ReplacedKind,
|
|
581
|
+
style: ComputedStyle,
|
|
582
|
+
options: BuildOptions,
|
|
583
|
+
): InlineDecoration | 'border' | null {
|
|
584
|
+
if (kind === 'checkbox' || kind === 'radio') return 'border';
|
|
585
|
+
if (kind === 'input' && (attr(el, 'type') ?? '').toLowerCase() === 'range') {
|
|
586
|
+
return 'border';
|
|
587
|
+
}
|
|
588
|
+
if (kind !== 'input' && kind !== 'select' && kind !== 'button') return null;
|
|
589
|
+
// a list box shows rows, and sits on its bottom edge
|
|
590
|
+
if (kind === 'select' && Number(attr(el, 'size') ?? 1) > 1) return null;
|
|
591
|
+
return (
|
|
592
|
+
options.faceExtent?.(style) ?? {
|
|
593
|
+
ascent: style.fontSize * 0.8,
|
|
594
|
+
descent: style.fontSize * 0.2,
|
|
595
|
+
}
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
565
599
|
/** A replaced box's intrinsic dimensions, in device pixels. */
|
|
566
600
|
export interface Intrinsic {
|
|
567
601
|
/** Its size — or, on an axis `missing` names, the default object size of
|
|
@@ -853,6 +887,10 @@ export interface BuildOptions {
|
|
|
853
887
|
* pixels: what a list marker's image with no size of its own is sized
|
|
854
888
|
* by (`_markerImageSize`). Where it is not known, 0.8em. */
|
|
855
889
|
faceAscent?(style: ComputedStyle): number | undefined;
|
|
890
|
+
/** How far a style's first face reaches above and below its baseline,
|
|
891
|
+
* in device pixels: where a control sets its text, which is where its
|
|
892
|
+
* baseline is (`CONTROL_BASELINES`). */
|
|
893
|
+
faceExtent?(style: ComputedStyle): InlineDecoration | undefined;
|
|
856
894
|
/** The size a real widget wants, so the box in the flow is the size the
|
|
857
895
|
* control will be drawn at. */
|
|
858
896
|
controlSize(
|
|
@@ -1486,6 +1524,8 @@ class Builder {
|
|
|
1486
1524
|
ratio: 0,
|
|
1487
1525
|
};
|
|
1488
1526
|
this._controls.push(box);
|
|
1527
|
+
const baseline = controlBaseline(el, replaced, style, this._options);
|
|
1528
|
+
if (baseline) CONTROL_BASELINES.set(box, baseline);
|
|
1489
1529
|
// A control's value is the widget's, not the document's: putting it in
|
|
1490
1530
|
// the selection index would make Ctrl+A copy the contents of every text
|
|
1491
1531
|
// field, which no document viewer does.
|
package/src/html/layout/flex.ts
CHANGED
|
@@ -252,6 +252,14 @@ export function layoutFlex(
|
|
|
252
252
|
const direction =
|
|
253
253
|
box.style.direction === 'rtl' ? Y.DIRECTION_RTL : Y.DIRECTION_LTR;
|
|
254
254
|
const row = box.style.flexDirection.startsWith('row');
|
|
255
|
+
// 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'));
|
|
255
263
|
// An `auto` margin across a row that wraps takes the room its line has
|
|
256
264
|
// past the item (CSS Flexbox 8.1, 9.6), where Yoga's pass that sets the
|
|
257
265
|
// items across their lines reads an item's alignment and not its
|
|
@@ -264,6 +272,37 @@ export function layoutFlex(
|
|
|
264
272
|
const aligned = autoAcross(child.style, reverse);
|
|
265
273
|
if (aligned) node.setAlignSelf(ALIGN[aligned]);
|
|
266
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
|
+
}
|
|
267
306
|
}
|
|
268
307
|
// frozen at its least size on the one line of a box that does not
|
|
269
308
|
// wrap, by a basis where Yoga takes one along the main axis
|
|
@@ -288,12 +327,13 @@ export function layoutFlex(
|
|
|
288
327
|
// `align-content` spaces out come out of its layout as tall as their
|
|
289
328
|
// line and the room after it as well (`unspace`): laid out so that each
|
|
290
329
|
// is as tall as its line, where the box has room to space them out in —
|
|
291
|
-
// a height, or a minimum one
|
|
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
|
|
292
332
|
const spaced =
|
|
293
333
|
row &&
|
|
294
334
|
bounded &&
|
|
295
335
|
!hold.frozen &&
|
|
296
|
-
(height !== null || floor > 0) &&
|
|
336
|
+
(height !== null || floor > 0 || ceiling < Infinity) &&
|
|
297
337
|
spacesLines(box.style);
|
|
298
338
|
const ask =
|
|
299
339
|
restretched.length > 0
|
|
@@ -409,18 +449,15 @@ export function layoutFlex(
|
|
|
409
449
|
}
|
|
410
450
|
}
|
|
411
451
|
|
|
412
|
-
// Yoga
|
|
413
|
-
|
|
414
|
-
const baselines =
|
|
415
|
-
box.style.alignItems === 'baseline' ||
|
|
416
|
-
items.some(({ box: child }) => child.style.alignSelf === 'baseline');
|
|
452
|
+
// whether Yoga's last layout took its pass for lines (`placeItems`)
|
|
453
|
+
let lined = box.style.flexWrap !== 'nowrap' || baselines;
|
|
417
454
|
let bottom = placeItems(
|
|
418
455
|
box,
|
|
419
456
|
ctx,
|
|
420
457
|
items,
|
|
421
458
|
row,
|
|
422
459
|
own,
|
|
423
|
-
|
|
460
|
+
lined,
|
|
424
461
|
contentWidth,
|
|
425
462
|
undefined,
|
|
426
463
|
crossed ? crossLines(box.style, items, contentWidth, columnGap) : undefined,
|
|
@@ -430,11 +467,11 @@ export function layoutFlex(
|
|
|
430
467
|
// by the flex layout again, set at their lines' starts with the margins
|
|
431
468
|
// that put their baselines together, which makes each line as tall as
|
|
432
469
|
// that does
|
|
433
|
-
const lines =
|
|
470
|
+
const lines = baselines ? baselineLines(box.style, items) : null;
|
|
471
|
+
const started = new Set<Box>();
|
|
434
472
|
if (lines) {
|
|
435
473
|
// every one of them, and the box's own `baseline` too, which is what
|
|
436
474
|
// takes Yoga down that pass
|
|
437
|
-
const started = new Set<Box>();
|
|
438
475
|
for (const line of lines) {
|
|
439
476
|
line.forEach(({ box: child, node }, i) => {
|
|
440
477
|
node.setAlignSelf(Y.ALIGN_FLEX_START);
|
|
@@ -445,6 +482,8 @@ export function layoutFlex(
|
|
|
445
482
|
if (box.style.alignItems === 'baseline') {
|
|
446
483
|
root.setAlignItems(Y.ALIGN_FLEX_START);
|
|
447
484
|
}
|
|
485
|
+
// so it takes it only where the row wraps
|
|
486
|
+
lined = box.style.flexWrap !== 'nowrap';
|
|
448
487
|
ctx.flexDepth = depth + 1;
|
|
449
488
|
try {
|
|
450
489
|
calculate();
|
|
@@ -457,7 +496,7 @@ export function layoutFlex(
|
|
|
457
496
|
items,
|
|
458
497
|
row,
|
|
459
498
|
own,
|
|
460
|
-
|
|
499
|
+
lined,
|
|
461
500
|
contentWidth,
|
|
462
501
|
started,
|
|
463
502
|
);
|
|
@@ -485,8 +524,9 @@ export function layoutFlex(
|
|
|
485
524
|
items,
|
|
486
525
|
row,
|
|
487
526
|
own,
|
|
488
|
-
|
|
527
|
+
lined,
|
|
489
528
|
contentWidth,
|
|
529
|
+
started,
|
|
490
530
|
);
|
|
491
531
|
}
|
|
492
532
|
|
|
@@ -680,7 +720,7 @@ function placeItems(
|
|
|
680
720
|
const placed = crossed?.get(child);
|
|
681
721
|
const across =
|
|
682
722
|
lined && !placed
|
|
683
|
-
? lineMarginFix(box.style, child, node, row, started?.has(child))
|
|
723
|
+
? lineMarginFix(box.style, child, node, row, own, started?.has(child))
|
|
684
724
|
: 0;
|
|
685
725
|
const itemHeight = row ? heightOf(node, laid) : laid.main;
|
|
686
726
|
const left =
|
|
@@ -896,6 +936,14 @@ function applyItem(
|
|
|
896
936
|
if (style.flexShrink) node.setFlexShrink(style.flexShrink);
|
|
897
937
|
if (style.alignSelf !== AUTO)
|
|
898
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
|
+
}
|
|
899
947
|
|
|
900
948
|
// Yoga's sizes are border boxes, as `box-sizing: border-box` has them;
|
|
901
949
|
// a `content-box` length is the content's, and the item's padding and
|
|
@@ -2858,22 +2906,26 @@ function baselineLines<T extends { box: Box }>(
|
|
|
2858
2906
|
line = [];
|
|
2859
2907
|
}
|
|
2860
2908
|
last = box.x;
|
|
2861
|
-
|
|
2862
|
-
const aligned = own.alignSelf === AUTO ? style.alignItems : own.alignSelf;
|
|
2863
|
-
// an `auto` margin across the line takes it over alignment
|
|
2864
|
-
if (
|
|
2865
|
-
aligned === 'baseline' &&
|
|
2866
|
-
own.marginTop !== AUTO &&
|
|
2867
|
-
own.marginBottom !== AUTO
|
|
2868
|
-
) {
|
|
2869
|
-
line.push(item);
|
|
2870
|
-
}
|
|
2909
|
+
if (byBaseline(style, box.style)) line.push(item);
|
|
2871
2910
|
}
|
|
2872
2911
|
close();
|
|
2873
2912
|
// one alone on its line is at its start already
|
|
2874
2913
|
return several ? out : null;
|
|
2875
2914
|
}
|
|
2876
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
|
+
|
|
2877
2929
|
/**
|
|
2878
2930
|
* Whether Yoga made a line of a row that wraps the row's height, where its
|
|
2879
2931
|
* items make it another (CSS Flexbox 9.4, steps 7 and 8; `layoutFlex`).
|
|
@@ -2999,13 +3051,16 @@ function autoAcross(
|
|
|
2999
3051
|
* top one; and one centred, half the difference. A column's, whose cross
|
|
3000
3052
|
* axis runs right to left, or that wraps in reverse, is left as Yoga has
|
|
3001
3053
|
* it, and so is an item an `auto` margin aligns in a row that does not
|
|
3002
|
-
* wrap
|
|
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`).
|
|
3003
3056
|
*/
|
|
3004
3057
|
function lineMarginFix(
|
|
3005
3058
|
container: ComputedStyle,
|
|
3006
3059
|
item: Box,
|
|
3007
3060
|
node: YogaNode,
|
|
3008
3061
|
row: boolean,
|
|
3062
|
+
/** Whether the flex box has a definite height. */
|
|
3063
|
+
own: boolean,
|
|
3009
3064
|
started = false,
|
|
3010
3065
|
): number {
|
|
3011
3066
|
const reverse = container.flexWrap === 'wrap-reverse';
|
|
@@ -3024,6 +3079,11 @@ function lineMarginFix(
|
|
|
3024
3079
|
: null;
|
|
3025
3080
|
} else {
|
|
3026
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
|
+
}
|
|
3027
3087
|
}
|
|
3028
3088
|
const leading = node.getComputedMargin(row ? Y.EDGE_TOP : Y.EDGE_LEFT);
|
|
3029
3089
|
const trailing = node.getComputedMargin(row ? Y.EDGE_BOTTOM : Y.EDGE_RIGHT);
|
|
@@ -3041,11 +3101,14 @@ function lineMarginFix(
|
|
|
3041
3101
|
|
|
3042
3102
|
/** An item's first baseline, down from its border edge: its content's
|
|
3043
3103
|
* first line's, or where it has none, or is replaced, one synthesized
|
|
3044
|
-
* 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. */
|
|
3045
3108
|
function itemBaseline(item: Box): number {
|
|
3046
3109
|
if (item.kind !== 'replaced') {
|
|
3047
3110
|
const found = firstBaselineIn(item);
|
|
3048
|
-
if (found !== null) return
|
|
3111
|
+
if (found !== null) return found - item.y;
|
|
3049
3112
|
}
|
|
3050
3113
|
return item.height;
|
|
3051
3114
|
}
|
|
@@ -3197,6 +3260,16 @@ function spacesLines(style: ComputedStyle): boolean {
|
|
|
3197
3260
|
* makes too, and is kept. Where they do, Yoga is asked again as the row
|
|
3198
3261
|
* is, for where the lines and the items are, and the heights are kept from
|
|
3199
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.
|
|
3200
3273
|
*/
|
|
3201
3274
|
function unspace(
|
|
3202
3275
|
root: YogaNode,
|
|
@@ -3206,6 +3279,16 @@ function unspace(
|
|
|
3206
3279
|
layout: () => void,
|
|
3207
3280
|
): void {
|
|
3208
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
|
+
};
|
|
3209
3292
|
// each item Yoga stretches: `stretch`, with no height of its own
|
|
3210
3293
|
const align = root.getAlignItems();
|
|
3211
3294
|
const stretched = items.filter(({ node }) => {
|
|
@@ -3216,15 +3299,24 @@ function unspace(
|
|
|
3216
3299
|
});
|
|
3217
3300
|
if (stretched.length === 0) {
|
|
3218
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
|
+
}
|
|
3219
3309
|
return;
|
|
3220
3310
|
}
|
|
3221
|
-
const lines = root.getAlignContent();
|
|
3222
3311
|
root.setAlignContent(Y.ALIGN_FLEX_START);
|
|
3223
3312
|
layout();
|
|
3224
3313
|
root.setAlignContent(lines);
|
|
3225
|
-
|
|
3314
|
+
const room = root.getComputedHeight() - linesReach(root, items, reverse);
|
|
3315
|
+
if (safe && room < -NEAR) {
|
|
3316
|
+
atEnd();
|
|
3226
3317
|
return;
|
|
3227
3318
|
}
|
|
3319
|
+
if (!(room > NEAR)) return;
|
|
3228
3320
|
const heights = stretched.map(({ node }) => node.getComputedHeight());
|
|
3229
3321
|
layout();
|
|
3230
3322
|
stretched.forEach(({ node, laid }, i) => {
|
|
@@ -3276,6 +3368,22 @@ function linesReach(
|
|
|
3276
3368
|
return reach;
|
|
3277
3369
|
}
|
|
3278
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
|
+
|
|
3279
3387
|
/** Whether an item is stretched across its line: `stretch`, its own or its
|
|
3280
3388
|
* container's, with no height of its own and no `auto` margin across. */
|
|
3281
3389
|
function stretches(
|
|
@@ -3331,9 +3439,10 @@ interface Across {
|
|
|
3331
3439
|
* where they are short of the box; `center` and `flex-end` whether they are
|
|
3332
3440
|
* or not, so that lines wider than the box run past both its sides, or its
|
|
3333
3441
|
* start; and the spacing values only where there is room, setting the lines
|
|
3334
|
-
* at
|
|
3335
|
-
* `space-between` falls back to `flex-start`, `space-around` and
|
|
3336
|
-
* `space-evenly` to `safe center`, the start where 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
|
|
3337
3446
|
* item with an `auto` margin across its line takes the room the line has
|
|
3338
3447
|
* past it on that side (8.1), and none where there is none; a stretched
|
|
3339
3448
|
* item is as wide as its line less its margins, within its limits (9.4,
|
|
@@ -3381,6 +3490,10 @@ function crossLines(
|
|
|
3381
3490
|
for (const size of across) room -= size;
|
|
3382
3491
|
let at = 0;
|
|
3383
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';
|
|
3384
3497
|
switch (crossContent(style)) {
|
|
3385
3498
|
case 'flex-end':
|
|
3386
3499
|
at = room;
|
|
@@ -3398,13 +3511,13 @@ function crossLines(
|
|
|
3398
3511
|
if (room > 0) {
|
|
3399
3512
|
at = room / count / 2;
|
|
3400
3513
|
between += room / count;
|
|
3401
|
-
}
|
|
3514
|
+
} else if (reverse) at = room;
|
|
3402
3515
|
break;
|
|
3403
3516
|
case 'space-evenly':
|
|
3404
3517
|
if (room > 0) {
|
|
3405
3518
|
at = room / (count + 1);
|
|
3406
3519
|
between += room / (count + 1);
|
|
3407
|
-
}
|
|
3520
|
+
} else if (reverse) at = room;
|
|
3408
3521
|
break;
|
|
3409
3522
|
}
|
|
3410
3523
|
const placed = new Map<Box, Across>();
|
|
@@ -45,6 +45,7 @@ import { inkColor, isTransparent, resolve } from '../css/values.js';
|
|
|
45
45
|
import { tagOf } from '../dom.js';
|
|
46
46
|
import {
|
|
47
47
|
BOX_RAISES,
|
|
48
|
+
CONTROL_BASELINES,
|
|
48
49
|
GRID_TRACKS,
|
|
49
50
|
LINE_BOX_RAISES,
|
|
50
51
|
PADDED_FACES,
|
|
@@ -3329,6 +3330,22 @@ function atomicRaise(
|
|
|
3329
3330
|
*/
|
|
3330
3331
|
function atomicBaseline(box: Box): number {
|
|
3331
3332
|
const margin = box.height + box.marginTop + box.marginBottom;
|
|
3333
|
+
// a control on its text's baseline, or on its border box's bottom,
|
|
3334
|
+
// where layout containment does not keep its baseline in
|
|
3335
|
+
const control =
|
|
3336
|
+
box.style.contain & CONTAIN_LAYOUT ? undefined : CONTROL_BASELINES.get(box);
|
|
3337
|
+
if (control === 'border') return box.marginTop + box.height;
|
|
3338
|
+
if (control) {
|
|
3339
|
+
const content = box.height - box.verticalExtra;
|
|
3340
|
+
const text = control.ascent + control.descent;
|
|
3341
|
+
return (
|
|
3342
|
+
box.marginTop +
|
|
3343
|
+
box.borderTop +
|
|
3344
|
+
box.padTop +
|
|
3345
|
+
(content - text) / 2 +
|
|
3346
|
+
control.ascent
|
|
3347
|
+
);
|
|
3348
|
+
}
|
|
3332
3349
|
const button = isButton(box);
|
|
3333
3350
|
// A `<button>` with no line in it sits on the bottom of its content box,
|
|
3334
3351
|
// not of its margin box: where its label would have sat. No specification
|
|
@@ -3468,14 +3485,14 @@ function tableBaseline(table: Box): number | null {
|
|
|
3468
3485
|
* chevron, and the line it was on came out shorter than a browser's.
|
|
3469
3486
|
*/
|
|
3470
3487
|
function lastBaselineIn(box: Box): number | null {
|
|
3471
|
-
if (box.kind === 'flex') return flexBaseline(box);
|
|
3488
|
+
if (box.kind === 'flex') return heldIn(box, flexBaseline(box));
|
|
3472
3489
|
if (box.lines?.length) {
|
|
3473
3490
|
const line = box.lines[box.lines.length - 1];
|
|
3474
|
-
return line.y + line.baseline;
|
|
3491
|
+
return heldIn(box, line.y + line.baseline);
|
|
3475
3492
|
}
|
|
3476
3493
|
for (let i = box.children.length - 1; i >= 0; i -= 1) {
|
|
3477
3494
|
const found = childBaseline(box.children[i], lastBaselineIn);
|
|
3478
|
-
if (found !== null) return found;
|
|
3495
|
+
if (found !== null) return heldIn(box, found);
|
|
3479
3496
|
}
|
|
3480
3497
|
return null;
|
|
3481
3498
|
}
|
|
@@ -3483,15 +3500,32 @@ function lastBaselineIn(box: Box): number | null {
|
|
|
3483
3500
|
/** The same, from the first line box or child: a table's baseline is its
|
|
3484
3501
|
* first row's, and so is a table cell's. */
|
|
3485
3502
|
export function firstBaselineIn(box: Box): number | null {
|
|
3486
|
-
if (box.kind === 'flex') return flexBaseline(box);
|
|
3487
|
-
if (box.lines?.length)
|
|
3503
|
+
if (box.kind === 'flex') return heldIn(box, flexBaseline(box));
|
|
3504
|
+
if (box.lines?.length) {
|
|
3505
|
+
return heldIn(box, box.lines[0].y + box.lines[0].baseline);
|
|
3506
|
+
}
|
|
3488
3507
|
for (const child of box.children) {
|
|
3489
3508
|
const found = childBaseline(child, firstBaselineIn);
|
|
3490
|
-
if (found !== null) return found;
|
|
3509
|
+
if (found !== null) return heldIn(box, found);
|
|
3491
3510
|
}
|
|
3492
3511
|
return null;
|
|
3493
3512
|
}
|
|
3494
3513
|
|
|
3514
|
+
/**
|
|
3515
|
+
* A box's baseline as its content gives it, held to its border box where
|
|
3516
|
+
* the box is a scroll container (CSS Box Alignment 3, 9.1), at either edge
|
|
3517
|
+
* — and only there: a box whose text overflows it otherwise, or clips it
|
|
3518
|
+
* (`overflow: clip` is no scroll container), has the baseline its text
|
|
3519
|
+
* has, as Chrome has it. Held at every box, so a scroll container deep in
|
|
3520
|
+
* an item, or in an inline block, holds the baseline it gives to its box.
|
|
3521
|
+
* The flex layout held every item to its height, and an item shorter
|
|
3522
|
+
* than its text sat its text's descent below the items beside it.
|
|
3523
|
+
*/
|
|
3524
|
+
function heldIn(box: Box, found: number | null): number | null {
|
|
3525
|
+
if (found === null || !scrolls(box.style)) return found;
|
|
3526
|
+
return Math.min(Math.max(found, box.y), box.y + box.height);
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3495
3529
|
function childBaseline(
|
|
3496
3530
|
child: Box,
|
|
3497
3531
|
inside: (box: Box) => number | null,
|
|
@@ -3502,9 +3536,9 @@ function childBaseline(
|
|
|
3502
3536
|
// a table is passed over for a block's last baseline (`lastBaselineIn`)
|
|
3503
3537
|
if (inside === lastBaselineIn && child.kind === 'table') return null;
|
|
3504
3538
|
// the legacy rule is a block container's last baseline alone (CSS Box
|
|
3505
|
-
// Alignment 3, 9.2): its first is its first line's, clipped or not
|
|
3506
|
-
// and a button that clips keeps its
|
|
3507
|
-
// own (`atomicBaseline`)
|
|
3539
|
+
// Alignment 3, 9.2): its first is its first line's, clipped or not, held
|
|
3540
|
+
// to its border box (`heldIn`) — and a button that clips keeps its
|
|
3541
|
+
// label's, here as on a line of its own (`atomicBaseline`)
|
|
3508
3542
|
if (
|
|
3509
3543
|
inside === lastBaselineIn &&
|
|
3510
3544
|
child.kind !== 'flex' &&
|
package/src/html/node.ts
CHANGED
|
@@ -1051,6 +1051,10 @@ export class HtmlViewNode extends Node {
|
|
|
1051
1051
|
const fonts = this._fonts();
|
|
1052
1052
|
return fonts ? faceExtentOf(fonts, style).ascent : undefined;
|
|
1053
1053
|
},
|
|
1054
|
+
faceExtent: (style) => {
|
|
1055
|
+
const fonts = this._fonts();
|
|
1056
|
+
return fonts ? faceExtentOf(fonts, style) : undefined;
|
|
1057
|
+
},
|
|
1054
1058
|
controlSize: (el, kind, style) =>
|
|
1055
1059
|
measureControl(el, kind, style, this._fonts(), look),
|
|
1056
1060
|
});
|