flexily 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -16
- package/dist/classic/layout.d.ts.map +1 -1
- package/dist/classic/layout.js +10 -1
- package/dist/classic/layout.js.map +1 -1
- package/dist/classic/node.js +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -1
- package/dist/constants.js.map +1 -1
- package/dist/index-classic.d.ts +1 -1
- package/dist/index-classic.d.ts.map +1 -1
- package/dist/index-classic.js +5 -5
- package/dist/index-classic.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/layout-helpers.d.ts +1 -4
- package/dist/layout-helpers.d.ts.map +1 -1
- package/dist/layout-helpers.js +2 -7
- package/dist/layout-helpers.js.map +1 -1
- package/dist/layout-zero.js +195 -39
- package/dist/layout-zero.js.map +1 -1
- package/dist/logger.js +2 -2
- package/dist/logger.js.map +1 -1
- package/dist/node-zero.js +1 -1
- package/dist/testing.js +4 -4
- package/dist/trace.js +1 -1
- package/dist/types.js +2 -2
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +11 -3
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +46 -21
- package/dist/utils.js.map +1 -1
- package/package.json +11 -3
- package/src/CLAUDE.md +36 -21
- package/src/classic/layout.ts +105 -45
- package/src/classic/node.ts +60 -0
- package/src/constants.ts +2 -1
- package/src/index-classic.ts +1 -1
- package/src/index.ts +1 -1
- package/src/layout-flex-lines.ts +70 -3
- package/src/layout-helpers.ts +27 -7
- package/src/layout-stats.ts +0 -2
- package/src/layout-zero.ts +587 -160
- package/src/node-zero.ts +98 -2
- package/src/testing.ts +20 -14
- package/src/types.ts +22 -15
- package/src/utils.ts +47 -21
package/dist/layout-helpers.js
CHANGED
|
@@ -6,13 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as C from "./constants.js";
|
|
8
8
|
import { resolveValue } from "./utils.js";
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
// ============================================================================
|
|
12
|
-
export const EDGE_LEFT = 0;
|
|
13
|
-
export const EDGE_TOP = 1;
|
|
14
|
-
export const EDGE_RIGHT = 2;
|
|
15
|
-
export const EDGE_BOTTOM = 3;
|
|
9
|
+
// Re-export edge constants (canonical definitions in constants.ts)
|
|
10
|
+
export { EDGE_LEFT, EDGE_TOP, EDGE_RIGHT, EDGE_BOTTOM } from "./constants.js";
|
|
16
11
|
// ============================================================================
|
|
17
12
|
// Helper Functions
|
|
18
13
|
// ============================================================================
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout-helpers.js","sourceRoot":"","sources":["../src/layout-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAA;AAEnC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAEzC
|
|
1
|
+
{"version":3,"file":"layout-helpers.js","sourceRoot":"","sources":["../src/layout-helpers.ts"],"names":[],"mappings":"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"}
|
package/dist/layout-zero.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Flexily Layout Algorithm — Main Entry Point
|
|
3
3
|
*
|
|
4
4
|
* Core flexbox layout computation. This file contains:
|
|
5
5
|
* - computeLayout(): top-level entry point
|
|
@@ -606,8 +606,14 @@ function layoutNode(node, availableWidth, availableHeight, offsetX, offsetY, abs
|
|
|
606
606
|
// For ALIGN_BASELINE in row direction, we need to know the max baseline first
|
|
607
607
|
// Zero-alloc: store baseline in child.flex.baseline, not a temporary array
|
|
608
608
|
let maxBaseline = 0;
|
|
609
|
+
// baselineZoneHeight: the effective cross-axis size that non-baseline children
|
|
610
|
+
// align within when baseline alignment is present. This is max(maxBaseline, maxChildHeight).
|
|
611
|
+
// Only meaningful when alignItems != ALIGN_BASELINE but some children have alignSelf=baseline.
|
|
612
|
+
let baselineZoneHeight = 0;
|
|
613
|
+
const alignItemsIsBaseline = style.alignItems === C.ALIGN_BASELINE;
|
|
609
614
|
if (hasBaselineAlignment && isRow) {
|
|
610
615
|
// First pass: compute each child's baseline and find the maximum
|
|
616
|
+
let maxChildHeight = 0;
|
|
611
617
|
for (const child of node.children) {
|
|
612
618
|
if (child.flex.relativeIndex < 0)
|
|
613
619
|
continue;
|
|
@@ -674,8 +680,20 @@ function layoutNode(node, availableWidth, availableHeight, offsetX, offsetY, abs
|
|
|
674
680
|
// This is a simplification from CSS spec but acceptable for TUI use cases
|
|
675
681
|
child.flex.baseline = topMargin + childHeight;
|
|
676
682
|
}
|
|
677
|
-
|
|
683
|
+
// Track max child height (including margin) for baseline zone calculation
|
|
684
|
+
maxChildHeight = Math.max(maxChildHeight, topMargin + childHeight + child.flex.marginB);
|
|
685
|
+
// When alignItems is baseline, ALL children participate in baseline computation.
|
|
686
|
+
// When alignItems is NOT baseline, only children with alignSelf=baseline participate.
|
|
687
|
+
// This matches Yoga's behavior: non-baseline children are positioned within the
|
|
688
|
+
// "baseline zone" (the effective height determined by baseline-aligned children),
|
|
689
|
+
// not the full container cross-axis.
|
|
690
|
+
if (alignItemsIsBaseline || childStyle.alignSelf === C.ALIGN_BASELINE) {
|
|
691
|
+
maxBaseline = Math.max(maxBaseline, child.flex.baseline);
|
|
692
|
+
}
|
|
678
693
|
}
|
|
694
|
+
// Baseline zone height: the max of maxBaseline and the tallest child.
|
|
695
|
+
// Non-baseline children are aligned within this zone, not the full container.
|
|
696
|
+
baselineZoneHeight = Math.max(maxBaseline, maxChildHeight);
|
|
679
697
|
}
|
|
680
698
|
// -----------------------------------------------------------------------
|
|
681
699
|
// PHASE 7a: Estimate Flex Line Cross-Axis Sizes (Tentative)
|
|
@@ -709,16 +727,26 @@ function layoutNode(node, availableWidth, availableHeight, offsetX, offsetY, abs
|
|
|
709
727
|
else if (crossDim.unit === C.UNIT_PERCENT && !Number.isNaN(crossAxisSize)) {
|
|
710
728
|
childCross = crossAxisSize * (crossDim.value / 100);
|
|
711
729
|
}
|
|
712
|
-
else {
|
|
713
|
-
// Auto
|
|
714
|
-
|
|
730
|
+
else if (child.hasMeasureFunc()) {
|
|
731
|
+
// Auto-sized with measureFunc: get tentative cross size from cached measure.
|
|
732
|
+
// Phase 5 already called cachedMeasure, so this is typically a cache hit (no alloc).
|
|
733
|
+
const crossMargin = crossMarginStart + crossMarginEnd;
|
|
734
|
+
const availCross = Number.isNaN(crossAxisSize) ? Infinity : crossAxisSize - crossMargin;
|
|
735
|
+
const mW = isRow ? mainAxisSize : availCross;
|
|
736
|
+
const mH = isRow ? availCross : mainAxisSize;
|
|
737
|
+
const mWMode = Number.isNaN(mW) ? C.MEASURE_MODE_UNDEFINED : C.MEASURE_MODE_AT_MOST;
|
|
738
|
+
const mHMode = Number.isNaN(mH) ? C.MEASURE_MODE_UNDEFINED : C.MEASURE_MODE_AT_MOST;
|
|
739
|
+
const measured = child.cachedMeasure(Number.isNaN(mW) ? Infinity : mW, mWMode, Number.isNaN(mH) ? Infinity : mH, mHMode);
|
|
740
|
+
if (measured) {
|
|
741
|
+
childCross = isRow ? measured.height : measured.width;
|
|
742
|
+
}
|
|
715
743
|
}
|
|
716
744
|
maxLineCross = Math.max(maxLineCross, childCross + crossMarginStart + crossMarginEnd);
|
|
717
745
|
}
|
|
718
|
-
//
|
|
719
|
-
//
|
|
720
|
-
|
|
721
|
-
const lineCrossSize = maxLineCross
|
|
746
|
+
// Use measured max cross size. If all children are auto-sized (maxLineCross === 0),
|
|
747
|
+
// use 0 — NOT crossAxisSize/numLines, which would consume all free space and
|
|
748
|
+
// prevent alignContent from distributing it. Actual sizes are computed in Phase 8.
|
|
749
|
+
const lineCrossSize = maxLineCross;
|
|
722
750
|
_lineCrossSizes[lineIdx] = lineCrossSize;
|
|
723
751
|
cumulativeCrossOffset += lineCrossSize + crossGap;
|
|
724
752
|
}
|
|
@@ -770,6 +798,15 @@ function layoutNode(node, availableWidth, availableHeight, offsetX, offsetY, abs
|
|
|
770
798
|
}
|
|
771
799
|
}
|
|
772
800
|
break;
|
|
801
|
+
case C.ALIGN_SPACE_EVENLY:
|
|
802
|
+
// Equal spacing between lines and at edges
|
|
803
|
+
if (numLines > 0) {
|
|
804
|
+
const gap = freeSpace / (numLines + 1);
|
|
805
|
+
for (let i = 0; i < numLines; i++) {
|
|
806
|
+
_lineCrossOffsets[i] += gap * (i + 1);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
break;
|
|
773
810
|
case C.ALIGN_STRETCH:
|
|
774
811
|
// Distribute extra space evenly among lines
|
|
775
812
|
if (freeSpace > 0 && numLines > 0) {
|
|
@@ -906,6 +943,20 @@ function layoutNode(node, availableWidth, availableHeight, offsetX, offsetY, abs
|
|
|
906
943
|
if (childStyle.alignSelf !== C.ALIGN_AUTO) {
|
|
907
944
|
alignment = childStyle.alignSelf;
|
|
908
945
|
}
|
|
946
|
+
// CSS Alignment spec: aspect-ratio fallback alignment
|
|
947
|
+
// When a flex item has aspect-ratio and auto cross-axis dimension,
|
|
948
|
+
// the fallback alignment is flex-start (not stretch). This prevents
|
|
949
|
+
// stretch from overriding the AR-derived dimension.
|
|
950
|
+
// Only applies when stretch is inherited (align-self: auto), not explicit.
|
|
951
|
+
const childCrossDimForAR = isRow ? childStyle.height : childStyle.width;
|
|
952
|
+
const childCrossIsAutoForAR = childCrossDimForAR.unit === C.UNIT_AUTO || childCrossDimForAR.unit === C.UNIT_UNDEFINED;
|
|
953
|
+
if (alignment === C.ALIGN_STRETCH &&
|
|
954
|
+
childStyle.alignSelf === C.ALIGN_AUTO &&
|
|
955
|
+
!Number.isNaN(childStyle.aspectRatio) &&
|
|
956
|
+
childStyle.aspectRatio > 0 &&
|
|
957
|
+
childCrossIsAutoForAR) {
|
|
958
|
+
alignment = C.ALIGN_FLEX_START;
|
|
959
|
+
}
|
|
909
960
|
// Cross axis size depends on alignment and child's explicit dimensions
|
|
910
961
|
// IMPORTANT: Resolve percent against parent's cross axis, not child's available
|
|
911
962
|
let childCrossSize;
|
|
@@ -928,8 +979,10 @@ function layoutNode(node, availableWidth, availableHeight, offsetX, offsetY, abs
|
|
|
928
979
|
childCrossSize = resolveValue(crossDim, crossAxisSize);
|
|
929
980
|
}
|
|
930
981
|
else if (parentHasDefiniteCross && alignment === C.ALIGN_STRETCH) {
|
|
931
|
-
// Stretch alignment with definite parent cross size - fill the cross axis
|
|
932
|
-
|
|
982
|
+
// Stretch alignment with definite parent cross size - fill the line's cross axis
|
|
983
|
+
// For wrapping layouts, stretch to line cross size, not full container cross size
|
|
984
|
+
const lineCross = numLines > 1 && childLineIdx < MAX_FLEX_LINES ? _lineCrossSizes[childLineIdx] : crossAxisSize;
|
|
985
|
+
childCrossSize = lineCross - crossMargin;
|
|
933
986
|
}
|
|
934
987
|
else {
|
|
935
988
|
// Non-stretch alignment or no definite cross size - shrink-wrap to content
|
|
@@ -954,7 +1007,9 @@ function layoutNode(node, availableWidth, availableHeight, offsetX, offsetY, abs
|
|
|
954
1007
|
// For auto main size children, use flex-computed size if flexGrow > 0,
|
|
955
1008
|
// otherwise pass remaining available space for shrink-wrap behavior
|
|
956
1009
|
const mainDim = isRow ? childStyle.width : childStyle.height;
|
|
957
|
-
|
|
1010
|
+
// A child has definite main size if it has explicit width/height OR non-auto flexBasis
|
|
1011
|
+
const hasDefiniteFlexBasis = childStyle.flexBasis.unit === C.UNIT_POINT || childStyle.flexBasis.unit === C.UNIT_PERCENT;
|
|
1012
|
+
const mainIsAutoChild = (mainDim.unit === C.UNIT_AUTO || mainDim.unit === C.UNIT_UNDEFINED) && !hasDefiniteFlexBasis;
|
|
958
1013
|
const hasFlexGrow = cflex.flexGrow > 0;
|
|
959
1014
|
// Use flex-computed mainSize for all cases - it includes padding/border as minimum
|
|
960
1015
|
// The flex algorithm already computed the proper size based on content/padding/border
|
|
@@ -1094,8 +1149,12 @@ function layoutNode(node, availableWidth, availableHeight, offsetX, offsetY, abs
|
|
|
1094
1149
|
// Calculate child's RELATIVE position (stored in layout)
|
|
1095
1150
|
// Yoga behavior: position is rounded locally, size uses absolute edge rounding
|
|
1096
1151
|
// This ensures sizes are pixel-perfect at document level while positions remain intuitive
|
|
1097
|
-
|
|
1098
|
-
|
|
1152
|
+
// Yoga 3.x quirk: measureFunc leaf nodes use Math.floor for position rounding,
|
|
1153
|
+
// while explicit-sized children use Math.round. This affects any justify/align mode
|
|
1154
|
+
// that produces fractional offsets (center, space-around, space-evenly).
|
|
1155
|
+
const posRound = shouldMeasure ? Math.floor : Math.round;
|
|
1156
|
+
const childLeft = posRound(fractionalLeft + posOffsetX);
|
|
1157
|
+
const childTop = posRound(fractionalTop + posOffsetY);
|
|
1099
1158
|
// Check if cross axis is auto-sized (needed for deciding what to pass to layoutNode)
|
|
1100
1159
|
const crossDimForLayoutCall = isRow ? childStyle.height : childStyle.width;
|
|
1101
1160
|
const crossIsAutoForLayoutCall = crossDimForLayoutCall.unit === C.UNIT_AUTO || crossDimForLayoutCall.unit === C.UNIT_UNDEFINED;
|
|
@@ -1217,7 +1276,17 @@ function layoutNode(node, availableWidth, availableHeight, offsetX, offsetY, abs
|
|
|
1217
1276
|
const crossEndIndex = isRow ? 3 : 2; // bottom for row, right for column
|
|
1218
1277
|
const hasAutoStartMargin = isEdgeAuto(childStyle.margin, crossStartIndex, style.flexDirection, direction);
|
|
1219
1278
|
const hasAutoEndMargin = isEdgeAuto(childStyle.margin, crossEndIndex, style.flexDirection, direction);
|
|
1220
|
-
|
|
1279
|
+
// When baseline alignment is present (hasBaselineAlignment) and this child is NOT using
|
|
1280
|
+
// baseline alignment, align within the baseline zone instead of the full container.
|
|
1281
|
+
// Yoga behavior: non-baseline children are positioned relative to the effective height
|
|
1282
|
+
// of the baseline group (max of maxBaseline and tallest child), not the container.
|
|
1283
|
+
const useBaselineZone = hasBaselineAlignment &&
|
|
1284
|
+
isRow &&
|
|
1285
|
+
!alignItemsIsBaseline &&
|
|
1286
|
+
alignment !== C.ALIGN_BASELINE &&
|
|
1287
|
+
baselineZoneHeight > 0;
|
|
1288
|
+
const effectiveCrossSize = useBaselineZone ? baselineZoneHeight : crossAxisSize;
|
|
1289
|
+
const availableCrossSpace = effectiveCrossSize - finalCrossSize - crossMargin;
|
|
1221
1290
|
if (hasAutoStartMargin && hasAutoEndMargin) {
|
|
1222
1291
|
// Both auto: center the item
|
|
1223
1292
|
// CSS spec: auto margins don't absorb negative free space (clamp to 0)
|
|
@@ -1252,11 +1321,14 @@ function layoutNode(node, availableWidth, availableHeight, offsetX, offsetY, abs
|
|
|
1252
1321
|
}
|
|
1253
1322
|
}
|
|
1254
1323
|
if (crossOffset > 0) {
|
|
1324
|
+
// Yoga 3.x quirk: measureFunc leaf nodes use Math.floor for cross-axis alignment
|
|
1325
|
+
// offset, matching the main-axis floor rounding behavior
|
|
1326
|
+
const crossRound = shouldMeasure ? Math.floor : Math.round;
|
|
1255
1327
|
if (isRow) {
|
|
1256
|
-
child.layout.top +=
|
|
1328
|
+
child.layout.top += crossRound(crossOffset);
|
|
1257
1329
|
}
|
|
1258
1330
|
else {
|
|
1259
|
-
child.layout.left +=
|
|
1331
|
+
child.layout.left += crossRound(crossOffset);
|
|
1260
1332
|
}
|
|
1261
1333
|
}
|
|
1262
1334
|
// Position advancement: use the right size depending on Phase 8 behavior.
|
|
@@ -1307,45 +1379,62 @@ function layoutNode(node, availableWidth, availableHeight, offsetX, offsetY, abs
|
|
|
1307
1379
|
actualUsedMain += childMainSize + totalMainMargin;
|
|
1308
1380
|
}
|
|
1309
1381
|
actualUsedMain += totalGaps;
|
|
1310
|
-
|
|
1382
|
+
// Skip main-axis shrink-wrap when aspect ratio determined this dimension
|
|
1383
|
+
const hasAR = !Number.isNaN(aspectRatio) && aspectRatio > 0;
|
|
1384
|
+
if (isRow && style.width.unit !== C.UNIT_POINT && style.width.unit !== C.UNIT_PERCENT && !hasAR) {
|
|
1311
1385
|
// Auto-width row: shrink-wrap to content
|
|
1312
1386
|
nodeWidth = actualUsedMain + innerLeft + innerRight;
|
|
1313
1387
|
}
|
|
1314
|
-
if (!isRow && style.height.unit !== C.UNIT_POINT && style.height.unit !== C.UNIT_PERCENT) {
|
|
1388
|
+
if (!isRow && style.height.unit !== C.UNIT_POINT && style.height.unit !== C.UNIT_PERCENT && !hasAR) {
|
|
1315
1389
|
// Auto-height column: shrink-wrap to content
|
|
1316
1390
|
nodeHeight = actualUsedMain + innerTop + innerBottom;
|
|
1317
1391
|
}
|
|
1318
|
-
// For cross axis,
|
|
1319
|
-
//
|
|
1320
|
-
//
|
|
1321
|
-
let
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1392
|
+
// For cross axis, compute shrink-wrap size
|
|
1393
|
+
// For multi-line (flex-wrap), sum line cross sizes + cross gaps
|
|
1394
|
+
// For single line, use max child cross size (existing behavior)
|
|
1395
|
+
let totalCrossSize = 0;
|
|
1396
|
+
if (numLines > 1) {
|
|
1397
|
+
// Multi-line: sum line cross sizes + cross gaps between lines
|
|
1398
|
+
for (let i = 0; i < numLines; i++) {
|
|
1399
|
+
totalCrossSize += _lineCrossSizes[i];
|
|
1400
|
+
}
|
|
1401
|
+
totalCrossSize += crossGap * (numLines - 1);
|
|
1402
|
+
}
|
|
1403
|
+
else {
|
|
1404
|
+
// Single line: max child cross size
|
|
1405
|
+
// CSS spec: percentage margins resolve against containing block's WIDTH only
|
|
1406
|
+
// Use resolveEdgeValue to respect logical EDGE_START/END
|
|
1407
|
+
for (const child of node.children) {
|
|
1408
|
+
if (child.flex.relativeIndex < 0)
|
|
1409
|
+
continue;
|
|
1410
|
+
const childCross = isRow ? child.layout.height : child.layout.width;
|
|
1411
|
+
const childMargin = isRow
|
|
1412
|
+
? resolveEdgeValue(child.style.margin, 1, style.flexDirection, contentWidth, direction) +
|
|
1413
|
+
resolveEdgeValue(child.style.margin, 3, style.flexDirection, contentWidth, direction)
|
|
1414
|
+
: resolveEdgeValue(child.style.margin, 0, style.flexDirection, contentWidth, direction) +
|
|
1415
|
+
resolveEdgeValue(child.style.margin, 2, style.flexDirection, contentWidth, direction);
|
|
1416
|
+
totalCrossSize = Math.max(totalCrossSize, childCross + childMargin);
|
|
1417
|
+
}
|
|
1332
1418
|
}
|
|
1333
1419
|
// Cross-axis shrink-wrap for auto-sized dimension
|
|
1334
1420
|
// Only shrink-wrap when the available dimension is NaN (unconstrained)
|
|
1335
1421
|
// When availableHeight/Width is defined, Yoga uses it for AUTO-sized root nodes
|
|
1422
|
+
// Skip if aspect ratio already determined this dimension (aspect ratio > shrink-wrap)
|
|
1336
1423
|
if (isRow &&
|
|
1337
1424
|
style.height.unit !== C.UNIT_POINT &&
|
|
1338
1425
|
style.height.unit !== C.UNIT_PERCENT &&
|
|
1339
|
-
Number.isNaN(availableHeight)
|
|
1340
|
-
|
|
1341
|
-
|
|
1426
|
+
Number.isNaN(availableHeight) &&
|
|
1427
|
+
!hasAR) {
|
|
1428
|
+
// Auto-height row: shrink-wrap to total cross size (accounts for multi-line)
|
|
1429
|
+
nodeHeight = totalCrossSize + innerTop + innerBottom;
|
|
1342
1430
|
}
|
|
1343
1431
|
if (!isRow &&
|
|
1344
1432
|
style.width.unit !== C.UNIT_POINT &&
|
|
1345
1433
|
style.width.unit !== C.UNIT_PERCENT &&
|
|
1346
|
-
Number.isNaN(availableWidth)
|
|
1347
|
-
|
|
1348
|
-
|
|
1434
|
+
Number.isNaN(availableWidth) &&
|
|
1435
|
+
!hasAR) {
|
|
1436
|
+
// Auto-width column: shrink-wrap to total cross size (accounts for multi-line)
|
|
1437
|
+
nodeWidth = totalCrossSize + innerLeft + innerRight;
|
|
1349
1438
|
}
|
|
1350
1439
|
}
|
|
1351
1440
|
// Re-apply min/max constraints after any shrink-wrap adjustments
|
|
@@ -1360,6 +1449,73 @@ function layoutNode(node, availableWidth, availableHeight, offsetX, offsetY, abs
|
|
|
1360
1449
|
if (!Number.isNaN(nodeHeight) && nodeHeight < minInnerHeight) {
|
|
1361
1450
|
nodeHeight = minInnerHeight;
|
|
1362
1451
|
}
|
|
1452
|
+
// -----------------------------------------------------------------------
|
|
1453
|
+
// PHASE 9b: Re-stretch children after shrink-wrap (Yoga compat)
|
|
1454
|
+
// -----------------------------------------------------------------------
|
|
1455
|
+
// When the parent's cross axis was auto (NaN during Phase 8), children with
|
|
1456
|
+
// stretch alignment were shrink-wrapped to content. Now that the cross size
|
|
1457
|
+
// is known from shrink-wrap, re-layout those children with the definite size.
|
|
1458
|
+
// This matches Yoga's two-pass approach for auto-sized containers.
|
|
1459
|
+
if (Number.isNaN(crossAxisSize) && relativeCount > 0) {
|
|
1460
|
+
const finalCross = isRow ? nodeHeight - innerTop - innerBottom : nodeWidth - innerLeft - innerRight;
|
|
1461
|
+
if (!Number.isNaN(finalCross) && finalCross > 0) {
|
|
1462
|
+
for (const child of node.children) {
|
|
1463
|
+
if (child.flex.relativeIndex < 0)
|
|
1464
|
+
continue;
|
|
1465
|
+
const cstyle = child.style;
|
|
1466
|
+
// Determine alignment for this child
|
|
1467
|
+
let childAlign = style.alignItems;
|
|
1468
|
+
if (cstyle.alignSelf !== C.ALIGN_AUTO) {
|
|
1469
|
+
childAlign = cstyle.alignSelf;
|
|
1470
|
+
}
|
|
1471
|
+
// AR fallback: aspect-ratio prevents implicit stretch
|
|
1472
|
+
const cCrossDim = isRow ? cstyle.height : cstyle.width;
|
|
1473
|
+
const cCrossIsAuto = cCrossDim.unit === C.UNIT_AUTO || cCrossDim.unit === C.UNIT_UNDEFINED;
|
|
1474
|
+
if (childAlign === C.ALIGN_STRETCH &&
|
|
1475
|
+
cstyle.alignSelf === C.ALIGN_AUTO &&
|
|
1476
|
+
!Number.isNaN(cstyle.aspectRatio) &&
|
|
1477
|
+
cstyle.aspectRatio > 0 &&
|
|
1478
|
+
cCrossIsAuto) {
|
|
1479
|
+
childAlign = C.ALIGN_FLEX_START;
|
|
1480
|
+
}
|
|
1481
|
+
if (childAlign !== C.ALIGN_STRETCH)
|
|
1482
|
+
continue;
|
|
1483
|
+
if (!cCrossIsAuto)
|
|
1484
|
+
continue;
|
|
1485
|
+
// Compute child's cross margin
|
|
1486
|
+
const cCrossMargin = isRow
|
|
1487
|
+
? resolveEdgeValue(cstyle.margin, 1, style.flexDirection, contentWidth, direction) +
|
|
1488
|
+
resolveEdgeValue(cstyle.margin, 3, style.flexDirection, contentWidth, direction)
|
|
1489
|
+
: resolveEdgeValue(cstyle.margin, 0, style.flexDirection, contentWidth, direction) +
|
|
1490
|
+
resolveEdgeValue(cstyle.margin, 2, style.flexDirection, contentWidth, direction);
|
|
1491
|
+
const stretchedCross = finalCross - cCrossMargin;
|
|
1492
|
+
// Only re-layout if the cross size actually changed
|
|
1493
|
+
const currentCross = isRow ? child.layout.height : child.layout.width;
|
|
1494
|
+
if (Math.round(stretchedCross) <= currentCross)
|
|
1495
|
+
continue;
|
|
1496
|
+
// Re-layout child with the definite cross size
|
|
1497
|
+
// Save position — layoutNode overwrites layout.left/top
|
|
1498
|
+
const savedLeft = child.layout.left;
|
|
1499
|
+
const savedTop = child.layout.top;
|
|
1500
|
+
const cMarginL = resolveEdgeValue(cstyle.margin, 0, style.flexDirection, contentWidth, direction);
|
|
1501
|
+
const cMarginT = resolveEdgeValue(cstyle.margin, 1, style.flexDirection, contentWidth, direction);
|
|
1502
|
+
const cAbsX = absX + innerLeft + savedLeft - cMarginL;
|
|
1503
|
+
const cAbsY = absY + innerTop + savedTop - cMarginT;
|
|
1504
|
+
const passW = isRow ? child.layout.width : stretchedCross;
|
|
1505
|
+
const passH = isRow ? stretchedCross : child.layout.height;
|
|
1506
|
+
layoutNode(child, passW, passH, savedLeft, savedTop, cAbsX, cAbsY, direction);
|
|
1507
|
+
// Restore position and override cross dimension to stretched size
|
|
1508
|
+
child.layout.left = savedLeft;
|
|
1509
|
+
child.layout.top = savedTop;
|
|
1510
|
+
if (isRow) {
|
|
1511
|
+
child.layout.height = Math.round(stretchedCross);
|
|
1512
|
+
}
|
|
1513
|
+
else {
|
|
1514
|
+
child.layout.width = Math.round(stretchedCross);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1363
1519
|
// =========================================================================
|
|
1364
1520
|
// PHASE 10: Final Output - Set Node Layout
|
|
1365
1521
|
// =========================================================================
|