diff --git a/resources/android/ContainerRenderers.kt b/resources/android/ContainerRenderers.kt index 2071009..089e460 100644 --- a/resources/android/ContainerRenderers.kt +++ b/resources/android/ContainerRenderers.kt @@ -78,24 +78,29 @@ object StackRenderer { // Absolute children pin to the stack's edges by inset — // the docs-blessed "layer a badge over an icon" pattern. - // Same anchor convention as ComposeFlexLayout / the iOS - // FlexContainer: a NON-ZERO right/bottom inset anchors to - // that edge; otherwise offset from top-left. Non-zero (not - // positive) so NEGATIVE insets overhang the edge, matching - // Tailwind's `-right-8` bleed. + // Same anchor convention as the iOS stack renderer: +0.0 + // means unset, non-zero anchors (negatives overhang — + // `-right-8` bleed), and IEEE -0.0 is an authored explicit + // zero (`bottom-0`), which anchors to that edge too. When + // both edges are authored, left/top win (CSS precedence). if (layout.positionType == PositionType.ABSOLUTE) { val left = layout.positionLeft val top = layout.positionTop val right = layout.positionRight val bottom = layout.positionBottom + // -0.0f == 0f in Kotlin, so authored zeros need the + // raw sign bit. + fun isSet(v: Float) = v != 0f || v.toRawBits() != 0 + val anchorEnd = isSet(right) && !isSet(left) + val anchorBottom = isSet(bottom) && !isSet(top) val anchor = when { - right != 0f && bottom != 0f -> Alignment.BottomEnd - right != 0f -> Alignment.TopEnd - bottom != 0f -> Alignment.BottomStart - else -> Alignment.TopStart + anchorEnd && anchorBottom -> Alignment.BottomEnd + anchorEnd -> Alignment.TopEnd + anchorBottom -> Alignment.BottomStart + else -> Alignment.TopStart } - val offsetX = if (right != 0f) (-right).dp else left.dp - val offsetY = if (bottom != 0f) (-bottom).dp else top.dp + val offsetX = if (anchorEnd) (-right).dp else left.dp + val offsetY = if (anchorBottom) (-bottom).dp else top.dp childMod = childMod.align(anchor).offset(x = offsetX, y = offsetY) } } diff --git a/resources/ios/NativeUIStackRenderer.swift b/resources/ios/NativeUIStackRenderer.swift index 61dd576..317d06d 100644 --- a/resources/ios/NativeUIStackRenderer.swift +++ b/resources/ios/NativeUIStackRenderer.swift @@ -86,21 +86,23 @@ struct NativeUIStackLayout: Layout { // Absolute children pin to the stack's edges by inset — the // docs-blessed "layer a badge over an icon" pattern. Same anchor - // convention as FlexContainer.placeAbsolute: a NON-ZERO right / - // bottom inset (with zero left/top) anchors to that edge, and a - // negative one deliberately overhangs it (`-right-8` bleed). + // convention as FlexContainer.placeAbsolute: +0.0 means unset, + // non-zero anchors (negatives overhang — `-right-8` bleed), and + // IEEE -0.0 is an authored explicit zero (`bottom-0`), which + // anchors to that edge too. if layout?.positionType == PositionType.absolute { let top = CGFloat(layout?.positionTop ?? 0) let right = CGFloat(layout?.positionRight ?? 0) let bottom = CGFloat(layout?.positionBottom ?? 0) let left = CGFloat(layout?.positionLeft ?? 0) + let isSet: (CGFloat) -> Bool = { $0 != 0 || $0.sign == .minus } - var x = bounds.minX + left - if right != 0 && left == 0 { + var x = bounds.minX + (isSet(left) ? left : 0) + if isSet(right) && !isSet(left) { x = bounds.maxX - width - right } - var y = bounds.minY + top - if bottom != 0 && top == 0 { + var y = bounds.minY + (isSet(top) ? top : 0) + if isSet(bottom) && !isSet(top) { y = bounds.maxY - height - bottom }