diff --git a/src/browser/tests/css/stylesheet.html b/src/browser/tests/css/stylesheet.html index e717e806..ec7928af 100644 --- a/src/browser/tests/css/stylesheet.html +++ b/src/browser/tests/css/stylesheet.html @@ -293,6 +293,28 @@ div.style.top = '0'; 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 div.style.opacity = '0'; testing.expectEqual('0', div.style.opacity); @@ -313,6 +335,12 @@ div.style.alignContent = 'first baseline'; 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 div.style.alignItems = 'last baseline'; testing.expectEqual('last baseline', div.style.alignItems); @@ -339,6 +367,16 @@ div.style.gap = '10px 20px'; 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); } diff --git a/src/browser/webapi/css/CSSStyleDeclaration.zig b/src/browser/webapi/css/CSSStyleDeclaration.zig index adc8982a..ebaafbe0 100644 --- a/src/browser/webapi/css/CSSStyleDeclaration.zig +++ b/src/browser/webapi/css/CSSStyleDeclaration.zig @@ -483,6 +483,16 @@ fn isTwoValueShorthand(name: []const u8) bool { .{ "overflow", {} }, .{ "overscroll-behavior", {} }, .{ "gap", {} }, + .{ "grid-gap", {} }, + // Scroll + .{ "scroll-padding-block", {} }, + .{ "scroll-padding-inline", {} }, + .{ "scroll-snap-align", {} }, + // Background/Mask + .{ "background-size", {} }, + .{ "border-image-repeat", {} }, + .{ "mask-repeat", {} }, + .{ "mask-size", {} }, }); return shorthands.has(name); } @@ -552,7 +562,6 @@ fn isLengthProperty(name: []const u8) bool { .{ "border-bottom-right-radius", {} }, // Text .{ "font-size", {} }, - .{ "line-height", {} }, .{ "letter-spacing", {} }, .{ "word-spacing", {} }, .{ "text-indent", {} }, @@ -561,17 +570,52 @@ fn isLengthProperty(name: []const u8) bool { .{ "row-gap", {} }, .{ "column-gap", {} }, .{ "flex-basis", {} }, + // Legacy grid aliases + .{ "grid-column-gap", {} }, + .{ "grid-row-gap", {} }, // Outline + .{ "outline", {} }, .{ "outline-width", {} }, .{ "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 .{ "border-spacing", {} }, .{ "text-shadow", {} }, .{ "box-shadow", {} }, .{ "baseline-shift", {} }, .{ "vertical-align", {} }, + .{ "text-decoration-inset", {} }, + .{ "block-step-size", {} }, // Grid lanes .{ "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); @@ -693,3 +737,55 @@ pub const JsApi = struct { pub const removeProperty = bridge.function(CSSStyleDeclaration.removeProperty, .{}); 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", "auto auto", "auto" }, + .{ "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); + } +}