@@ -595,10 +595,13 @@ public void CloseElement()
595595 openLayoutElement . MinDimensions . Width = Math . Clamp ( openLayoutElement . MinDimensions . Width ,
596596 layoutConfig . Sizing . Width . MinMax . Min , maxWidth ) ;
597597 }
598- else
599- {
600- openLayoutElement . Dimensions . Width = 0 ;
601- }
598+ // NOTE: a Percent element keeps the content-derived width computed from
599+ // its children here (it is NOT reset to 0). The width pass overwrites it
600+ // with the resolved percent against the real parent, so the only effect
601+ // of keeping it is that a FIT parent can size to this element's content.
602+ // Resetting to 0 collapsed a fit parent whose sole child is percent/grow
603+ // width — e.g. a tooltip wrapper around flowing text got 0 width and thus
604+ // 0 hit area (no hover). Content sizing matches CSS min-content behaviour.
602605
603606 if ( layoutConfig . Sizing . Height . Type != SizingType . Percent )
604607 {
@@ -873,10 +876,63 @@ private void UpdateAspectRatioBox(ref LayoutElement layoutElement)
873876 private void ComputeLayout ( )
874877 {
875878 SizeContainersAlongAxis ( true ) ;
879+ // Width pass reflows overflowing text leaves to multiple lines; their FIT
880+ // ancestors were height-summed at CloseElement against the pre-wrap single
881+ // line, so re-sum them before the height pass / positioning runs.
882+ RecomputeFitHeights ( ) ;
876883 SizeContainersAlongAxis ( false ) ;
877884 PositionElements ( ) ;
878885 }
879886
887+ // Bottom-up (post-order) re-accumulation of FIT element heights. Only FIT
888+ // containers are touched — Fixed/Percent/Grow heights are owned by other
889+ // passes. Mirrors the CloseElement FIT rule: a column sums child heights +
890+ // gaps + padding, a row takes the tallest child + padding.
891+ private void RecomputeFitHeights ( )
892+ {
893+ for ( int r = 0 ; r < LayoutElementTreeRoots . Length ; r ++ )
894+ RecomputeFitHeightsRecursive ( LayoutElementTreeRoots [ r ] . LayoutElementIndex ) ;
895+ }
896+
897+ private float RecomputeFitHeightsRecursive ( int elementIndex )
898+ {
899+ // Re-fetch by index after recursing (the elements array never resizes
900+ // here, but avoid holding a ref across the child calls regardless).
901+ if ( LayoutElements [ elementIndex ] . IsTextElement )
902+ return LayoutElements [ elementIndex ] . Dimensions . Height ;
903+
904+ int childStart = LayoutElements [ elementIndex ] . Children . StartIndex ;
905+ int childCount = LayoutElements [ elementIndex ] . Children . Length ;
906+ for ( int i = 0 ; i < childCount ; i ++ )
907+ RecomputeFitHeightsRecursive ( LayoutElementChildren [ childStart + i ] ) ;
908+
909+ ref var el = ref LayoutElements [ elementIndex ] ;
910+ ref var cfg = ref LayoutConfigs [ el . LayoutConfigIndex ] ;
911+ if ( cfg . Sizing . Height . Type != SizingType . Fit )
912+ return el . Dimensions . Height ;
913+
914+ float padV = cfg . Padding . Top + cfg . Padding . Bottom ;
915+ float height ;
916+ if ( cfg . Direction == LayoutDirection . TopToBottom )
917+ {
918+ height = padV ;
919+ for ( int i = 0 ; i < childCount ; i ++ )
920+ height += LayoutElements [ LayoutElementChildren [ childStart + i ] ] . Dimensions . Height ;
921+ height += Math . Max ( childCount - 1 , 0 ) * cfg . ChildGap ;
922+ }
923+ else
924+ {
925+ float maxChild = 0 ;
926+ for ( int i = 0 ; i < childCount ; i ++ )
927+ maxChild = Math . Max ( maxChild , LayoutElements [ LayoutElementChildren [ childStart + i ] ] . Dimensions . Height ) ;
928+ height = maxChild + padV ;
929+ }
930+
931+ float maxHeight = cfg . Sizing . Height . MinMax . Max > 0 ? cfg . Sizing . Height . MinMax . Max : float . MaxValue ;
932+ el . Dimensions . Height = Math . Clamp ( height , cfg . Sizing . Height . MinMax . Min , maxHeight ) ;
933+ return el . Dimensions . Height ;
934+ }
935+
880936 private void SizeContainersAlongAxis ( bool xAxis )
881937 {
882938 for ( int rootIndex = 0 ; rootIndex < LayoutElementTreeRoots . Length ; rootIndex ++ )
@@ -945,6 +1001,33 @@ private void SizeContainersAlongAxis(bool xAxis)
9451001 queue . Add ( childIndex ) ;
9461002 }
9471003
1004+ // Text reflow: a text leaf is measured single-line at AddText
1005+ // time (FIT), so it overflows a narrower container. Now that the
1006+ // parent's width is resolved (top-down BFS), re-wrap any text
1007+ // child to the available content width — width shrinks to the
1008+ // widest wrapped line, height grows with the line count.
1009+ // RecomputeFitHeights (run after this pass) propagates the new
1010+ // height up through FIT ancestors. Height-only effect here;
1011+ // widths of FIT ancestors were already framed by non-text
1012+ // siblings / fixed sizing in CloseElement.
1013+ if ( xAxis && child . IsTextElement && TextMeasurer != null
1014+ && availableSpace > 0 && child . Dimensions . Width > availableSpace )
1015+ {
1016+ int tcIndex = FindConfigIndex ( ref child , ElementConfigType . Text ) ;
1017+ if ( tcIndex >= 0 )
1018+ {
1019+ ref var td = ref TextElementData [ child . TextData . Index ] ;
1020+ ref var tc = ref TextElementConfigs [ tcIndex ] ;
1021+ var wrapped = TextMeasurer . MeasureTextWrapped (
1022+ td . Text . AsSpan ( ) , tc . FontId , tc . FontSize , tc . LetterSpacing , availableSpace ) ;
1023+ if ( tc . LineHeight > 0 && tc . FontSize > 0 )
1024+ wrapped . Height *= tc . LineHeight / ( float ) tc . FontSize ;
1025+ child . Dimensions . Width = Math . Min ( wrapped . Width , availableSpace ) ;
1026+ child . Dimensions . Height = wrapped . Height ;
1027+ td . PreferredDimensions = child . Dimensions ;
1028+ }
1029+ }
1030+
9481031 ref var childLayoutConfig = ref LayoutConfigs [ child . LayoutConfigIndex ] ;
9491032 var childSizing = xAxis ? childLayoutConfig . Sizing . Width : childLayoutConfig . Sizing . Height ;
9501033
0 commit comments