extend CSS value normalization to cover more properties

Add missing properties to isLengthProperty (0→0px) and
isTwoValueShorthand (duplicate value collapse) maps based
on WPT test failures in css/css-sizing, css/css-align,
css/css-scroll-snap, css/css-logical, and others.

New length properties: scroll-margin/padding-*, column-width,
column-rule-width, grid-column/row-gap, outline, shape-margin,
offset-distance, translate, animation-range-*, block-step-size,
text-decoration-inset, and *-rule-*-inset (CSS Gaps).

New two-value shorthands: scroll-padding-block/inline,
scroll-snap-align, background-size, border-image-repeat,
mask-repeat/size, contain-intrinsic-size, scale, text-box-edge,
animation-range, grid-gap.
This commit is contained in:
egrs
2026-03-10 13:53:27 +01:00
parent dfd90bd564
commit 6a7f7fdf15
2 changed files with 140 additions and 0 deletions

View File

@@ -293,6 +293,28 @@
div.style.top = '0'; div.style.top = '0';
testing.expectEqual('0px', div.style.top); testing.expectEqual('0px', div.style.top);
// Scroll properties
div.style.scrollMarginTop = '0';
testing.expectEqual('0px', div.style.scrollMarginTop);
div.style.scrollPaddingBottom = '0';
testing.expectEqual('0px', div.style.scrollPaddingBottom);
// Multi-column
div.style.columnWidth = '0';
testing.expectEqual('0px', div.style.columnWidth);
div.style.columnRuleWidth = '0';
testing.expectEqual('0px', div.style.columnRuleWidth);
// Outline shorthand
div.style.outline = '0';
testing.expectEqual('0px', div.style.outline);
// Shapes
div.style.shapeMargin = '0';
testing.expectEqual('0px', div.style.shapeMargin);
// Non-length properties should not be affected // Non-length properties should not be affected
div.style.opacity = '0'; div.style.opacity = '0';
testing.expectEqual('0', div.style.opacity); testing.expectEqual('0', div.style.opacity);
@@ -313,6 +335,12 @@
div.style.alignContent = 'first baseline'; div.style.alignContent = 'first baseline';
testing.expectEqual('baseline', div.style.alignContent); testing.expectEqual('baseline', div.style.alignContent);
div.style.alignSelf = 'first baseline';
testing.expectEqual('baseline', div.style.alignSelf);
div.style.justifySelf = 'first baseline';
testing.expectEqual('baseline', div.style.justifySelf);
// "last baseline" should remain unchanged // "last baseline" should remain unchanged
div.style.alignItems = 'last baseline'; div.style.alignItems = 'last baseline';
testing.expectEqual('last baseline', div.style.alignItems); testing.expectEqual('last baseline', div.style.alignItems);
@@ -339,6 +367,16 @@
div.style.gap = '10px 20px'; div.style.gap = '10px 20px';
testing.expectEqual('10px 20px', div.style.gap); testing.expectEqual('10px 20px', div.style.gap);
// New shorthands
div.style.overflow = 'hidden hidden';
testing.expectEqual('hidden', div.style.overflow);
div.style.scrollSnapAlign = 'start start';
testing.expectEqual('start', div.style.scrollSnapAlign);
div.style.overscrollBehavior = 'auto auto';
testing.expectEqual('auto', div.style.overscrollBehavior);
} }
</script> </script>

View File

@@ -483,6 +483,21 @@ fn isTwoValueShorthand(name: []const u8) bool {
.{ "overflow", {} }, .{ "overflow", {} },
.{ "overscroll-behavior", {} }, .{ "overscroll-behavior", {} },
.{ "gap", {} }, .{ "gap", {} },
.{ "grid-gap", {} },
// Scroll
.{ "scroll-padding-block", {} },
.{ "scroll-padding-inline", {} },
.{ "scroll-snap-align", {} },
// Background/Mask
.{ "background-size", {} },
.{ "border-image-repeat", {} },
.{ "mask-repeat", {} },
.{ "mask-size", {} },
// Other
.{ "contain-intrinsic-size", {} },
.{ "scale", {} },
.{ "text-box-edge", {} },
.{ "animation-range", {} },
}); });
return shorthands.has(name); return shorthands.has(name);
} }
@@ -561,17 +576,52 @@ fn isLengthProperty(name: []const u8) bool {
.{ "row-gap", {} }, .{ "row-gap", {} },
.{ "column-gap", {} }, .{ "column-gap", {} },
.{ "flex-basis", {} }, .{ "flex-basis", {} },
// Legacy grid aliases
.{ "grid-column-gap", {} },
.{ "grid-row-gap", {} },
// Outline // Outline
.{ "outline", {} },
.{ "outline-width", {} }, .{ "outline-width", {} },
.{ "outline-offset", {} }, .{ "outline-offset", {} },
// Multi-column
.{ "column-rule-width", {} },
.{ "column-width", {} },
// Scroll
.{ "scroll-margin", {} },
.{ "scroll-margin-top", {} },
.{ "scroll-margin-right", {} },
.{ "scroll-margin-bottom", {} },
.{ "scroll-margin-left", {} },
.{ "scroll-padding", {} },
.{ "scroll-padding-top", {} },
.{ "scroll-padding-right", {} },
.{ "scroll-padding-bottom", {} },
.{ "scroll-padding-left", {} },
// Shapes
.{ "shape-margin", {} },
// Motion path
.{ "offset-distance", {} },
// Transforms
.{ "translate", {} },
// Animations
.{ "animation-range-end", {} },
.{ "animation-range-start", {} },
// Other // Other
.{ "border-spacing", {} }, .{ "border-spacing", {} },
.{ "text-shadow", {} }, .{ "text-shadow", {} },
.{ "box-shadow", {} }, .{ "box-shadow", {} },
.{ "baseline-shift", {} }, .{ "baseline-shift", {} },
.{ "vertical-align", {} }, .{ "vertical-align", {} },
.{ "text-decoration-inset", {} },
.{ "block-step-size", {} },
// Grid lanes // Grid lanes
.{ "flow-tolerance", {} }, .{ "flow-tolerance", {} },
.{ "column-rule-edge-inset", {} },
.{ "column-rule-interior-inset", {} },
.{ "row-rule-edge-inset", {} },
.{ "row-rule-interior-inset", {} },
.{ "rule-edge-inset", {} },
.{ "rule-interior-inset", {} },
}); });
return length_properties.has(name); return length_properties.has(name);
@@ -693,3 +743,55 @@ pub const JsApi = struct {
pub const removeProperty = bridge.function(CSSStyleDeclaration.removeProperty, .{}); pub const removeProperty = bridge.function(CSSStyleDeclaration.removeProperty, .{});
pub const cssFloat = bridge.accessor(CSSStyleDeclaration.getFloat, CSSStyleDeclaration.setFloat, .{}); pub const cssFloat = bridge.accessor(CSSStyleDeclaration.getFloat, CSSStyleDeclaration.setFloat, .{});
}; };
const testing = @import("std").testing;
test "normalizePropertyValue: unitless zero to 0px" {
const cases = .{
.{ "width", "0", "0px" },
.{ "height", "0", "0px" },
.{ "scroll-margin-top", "0", "0px" },
.{ "scroll-padding-bottom", "0", "0px" },
.{ "column-width", "0", "0px" },
.{ "column-rule-width", "0", "0px" },
.{ "outline", "0", "0px" },
.{ "shape-margin", "0", "0px" },
.{ "offset-distance", "0", "0px" },
.{ "translate", "0", "0px" },
.{ "grid-column-gap", "0", "0px" },
.{ "grid-row-gap", "0", "0px" },
// Non-length properties should NOT normalize
.{ "opacity", "0", "0" },
.{ "z-index", "0", "0" },
};
inline for (cases) |case| {
const result = try normalizePropertyValue(testing.allocator, case[0], case[1]);
try testing.expectEqualStrings(case[2], result);
}
}
test "normalizePropertyValue: first baseline to baseline" {
const result = try normalizePropertyValue(testing.allocator, "align-items", "first baseline");
try testing.expectEqualStrings("baseline", result);
const result2 = try normalizePropertyValue(testing.allocator, "align-self", "last baseline");
try testing.expectEqualStrings("last baseline", result2);
}
test "normalizePropertyValue: collapse duplicate two-value shorthands" {
const cases = .{
.{ "overflow", "hidden hidden", "hidden" },
.{ "gap", "10px 10px", "10px" },
.{ "scroll-snap-align", "start start", "start" },
.{ "scroll-padding-block", "5px 5px", "5px" },
.{ "background-size", "cover cover", "cover" },
.{ "overscroll-behavior", "auto auto", "auto" },
// Different values should NOT collapse
.{ "overflow", "hidden scroll", "hidden scroll" },
.{ "gap", "10px 20px", "10px 20px" },
};
inline for (cases) |case| {
const result = try normalizePropertyValue(testing.allocator, case[0], case[1]);
try testing.expectEqualStrings(case[2], result);
}
}