diff --git a/crates/jsbindings/bindings.layout.hpp b/crates/jsbindings/bindings.layout.hpp index 22152a469..1b33c746a 100644 --- a/crates/jsbindings/bindings.layout.hpp +++ b/crates/jsbindings/bindings.layout.hpp @@ -170,6 +170,7 @@ namespace crates::layout2 XX(Clip, "clip") #define POSITION_MAP(XX) \ + XX(Static, "static") \ XX(Relative, "relative") \ XX(Absolute, "absolute") @@ -375,11 +376,15 @@ namespace crates::layout2 }; class Position : public CSSKeyword + holocron::layout::Position::Static> { using CSSKeyword::CSSKeyword; public: + static Position Static() + { + return Position(holocron::layout::Position::Static); + } static Position Relative() { return Position(holocron::layout::Position::Relative); @@ -392,9 +397,13 @@ namespace crates::layout2 public: Position(const std::string &input) { - handle_ = parse(input).value_or(holocron::layout::Position::Relative); + handle_ = parse(input).value_or(holocron::layout::Position::Static); } + inline bool isStatic() const + { + return handle_ == holocron::layout::Position::Static; + } inline bool isRelative() const { return handle_ == holocron::layout::Position::Relative; @@ -1035,7 +1044,7 @@ namespace crates::layout2 // scrollbar_width 2.0f, // position - styles::Position::Relative(), + styles::Position::Static(), // inset holocron::layout::LengthPercentageAutoRect{ .top = styles::LengthPercentageAuto::Auto(), diff --git a/crates/jsbindings/layout.rs b/crates/jsbindings/layout.rs index 2f10aba5e..78a9d2cea 100644 --- a/crates/jsbindings/layout.rs +++ b/crates/jsbindings/layout.rs @@ -262,7 +262,8 @@ mod ffi { #[derive(Clone, Copy, Debug)] enum Position { - Relative = 0, + Static = 0, + Relative, Absolute, } @@ -667,12 +668,36 @@ impl From> for ffi::NumberRect { impl_type_casting_simple!(Display, { Block, Flex, Grid, None }, Block); impl_type_casting_simple!(BoxSizing, { ContentBox, BorderBox }, ContentBox); impl_type_casting_simple!(Overflow, { Visible, Clip, Hidden, Scroll }, Visible); -impl_type_casting_simple!(Position, { Relative, Absolute }, Relative); +#[allow(unreachable_patterns)] +impl From for taffy::Position { + fn from(value: ffi::Position) -> Self { + match value { + // todo:support position: static + ffi::Position::Static => Self::Relative, // Static position maps to Relative in taffy + ffi::Position::Relative => Self::Relative, + ffi::Position::Absolute => Self::Absolute, + _ => Self::Relative, + } + } +} + +#[allow(unreachable_patterns)] +impl From for ffi::Position { + fn from(value: taffy::Position) -> Self { + match value { + // Note: Taffy does not have a Static position, so we map Relative to Static here. + // taffy roadmap:https://github.com/DioxusLabs/taffy/issues/345 indicates that Static may be added in the future. + taffy::Position::Relative => Self::Static, // Note: Loss of Static vs Relative distinction + taffy::Position::Absolute => Self::Absolute, + _ => Self::Static, + } + } +} impl_default_for!(Display, Block); impl_default_for!(BoxSizing, ContentBox); impl_default_for!(Overflow, Visible); -impl_default_for!(Position, Relative); +impl_default_for!(Position, Static); macro_rules! impl_xy_casting { ($name:ident, $container:ty) => { @@ -1346,13 +1371,27 @@ fn grid_line(input_str: &str) -> taffy::GridPlacement { impl From for taffy::Style { fn from(value: ffi::Style) -> Self { + // According to CSS specification, inset properties (top, right, bottom, left) + // should be ignored when position is static + let inset = if matches!(value.position, ffi::Position::Static) { + // For static positioning, force all inset values to auto + taffy::Rect { + top: taffy::LengthPercentageAuto::auto(), + right: taffy::LengthPercentageAuto::auto(), + bottom: taffy::LengthPercentageAuto::auto(), + left: taffy::LengthPercentageAuto::auto(), + } + } else { + value.inset.into() + }; + taffy::Style { display: value.display.into(), box_sizing: value.box_sizing.into(), overflow: value.overflow.into(), scrollbar_width: value.scrollbar_width, position: value.position.into(), - inset: value.inset.into(), + inset, size: taffy::Size { width: value.width.into(), height: value.height.into(), diff --git a/fixtures/html/css-position-static-test.html b/fixtures/html/css-position-static-test.html new file mode 100644 index 000000000..157649dcd --- /dev/null +++ b/fixtures/html/css-position-static-test.html @@ -0,0 +1,258 @@ + + + + + CSS Position Static Test - Inset Properties Ignored + + + + +

CSS Position: Static Test - Inset Properties Should Be Ignored

+ +
+ Issue: This test validates the fix for + Issue #378 + - CSS position: static should ignore top/left/right/bottom properties per web standards. +
+ +
+ CSS Specification: According to the + CSS Positioning Module Level 3, + when an element has position: static, the properties top, left, right, + and bottom have no effect and should be ignored by the layout engine. +
+ +

Test 1: Static Position with Inset Properties

+
+
Reference position
+
+ Static: top:100px left:100px +
+
+ ✅ Expected: The blue box should appear directly below the reference box, + ignoring the top: 100px; left: 100px; properties. +
+
+ +

Test 2: Relative Position with Same Inset Properties (Comparison)

+
+
Reference position
+
+ Relative: top:20px left:50px +
+
+ 📝 Comparison: The green box should be offset by the specified + top: 20px; left: 50px; values, demonstrating the difference from static positioning. +
+
+ +

Test 3: Static Position with Large Offset Values

+
+
Reference position
+
+ Static: top:500px left:300px +
+
+ ✅ Expected: The orange box should appear in normal document flow, + completely ignoring the large top: 500px; left: 300px; offset values. +
+
+ +

Test 4: Nested Static Positioning

+
+
+
Nested reference
+
+ Nested Static: top:80px left:120px +
+
+ ✅ Expected: Even within a positioned container, the brown box + should ignore top: 80px; left: 120px; and appear in normal flow. +
+
+
+ +
+ How to verify: +
    +
  1. All colored boxes with position: static should appear in normal document flow
  2. +
  3. None of the static positioned elements should be offset by their top/left/right/bottom values
  4. +
  5. Compare with the relative positioned element (Test 2) which should be visibly offset
  6. +
  7. This behavior should match standard browsers like Chrome, Firefox, Safari
  8. +
+
+ +
+ Implementation Details: The fix operates at the Rust layout layer where inset properties + are actively ignored (set to auto) when position: static is detected, ensuring compliance + with CSS specifications regardless of the computed style values. +
+ + + + \ No newline at end of file diff --git a/src/client/cssom/values/generics/position.hpp b/src/client/cssom/values/generics/position.hpp index 446e31f94..3fcd163cc 100644 --- a/src/client/cssom/values/generics/position.hpp +++ b/src/client/cssom/values/generics/position.hpp @@ -81,13 +81,14 @@ namespace client_cssom::values::generics switch (tag_) { case kStatic: + return crates::layout2::styles::Position::Static(); case kRelative: return crates::layout2::styles::Position::Relative(); case kAbsolute: return crates::layout2::styles::Position::Absolute(); default: // TODO(yorkie): support fixed and sticky. - return crates::layout2::styles::Position::Relative(); + return crates::layout2::styles::Position::Static(); } } diff --git a/tests/client/layout_position_tests.cpp b/tests/client/layout_position_tests.cpp index 08151ed2d..559133a1d 100644 --- a/tests/client/layout_position_tests.cpp +++ b/tests/client/layout_position_tests.cpp @@ -58,4 +58,5 @@ TEST_CASE("Layout Position Tests", "[layout-position]") REQUIRE(true); // Placeholder - actual testing requires DOM setup } + } \ No newline at end of file