From f6ec0da125bae0fe217851cc1a25ba0cd71680ad Mon Sep 17 00:00:00 2001 From: Zack Radisic <56137411+zackradisic@users.noreply.github.com> Date: Fri, 31 Jan 2025 03:47:49 -0800 Subject: [PATCH] Various CSS fixing and stability stuff (#16889) --- bun.lock | 10 +- package.json | 4 +- src/bitflags.zig | 2 + src/css/build-prefixes.js | 18 +- src/css/compat.zig | 56 +- src/css/css_parser.zig | 276 +-- src/css/declaration.zig | 13 + src/css/generics.zig | 221 +- src/css/prefixes.zig | 70 +- src/css/printer.zig | 12 + src/css/properties/align.zig | 436 +++- src/css/properties/animation.zig | 307 ++- src/css/properties/border.zig | 37 +- src/css/properties/border_image.zig | 4 +- src/css/properties/flex.zig | 77 + src/css/properties/generate_properties.ts | 148 +- src/css/properties/grid.zig | 561 +++++ src/css/properties/masking.zig | 16 + src/css/properties/properties.zig | 1 + src/css/properties/properties_generated.zig | 1018 +++++++- src/css/properties/properties_impl.zig | 2 +- src/css/properties/transform.zig | 429 +++- src/css/properties/transition.zig | 512 +++- src/css/selectors/parser.zig | 2 +- src/css/small_list.zig | 8 +- src/css/targets.zig | 52 +- src/css/values/angle.zig | 26 +- src/css/values/calc.zig | 30 +- src/css/values/easing.zig | 138 +- src/css/values/gradient.zig | 102 +- src/css/values/image.zig | 28 +- src/css/values/length.zig | 18 +- src/css/values/number.zig | 14 +- src/css/values/percentage.zig | 33 +- src/css/values/position.zig | 19 +- src/css/values/resolution.zig | 15 +- src/css/values/time.zig | 60 +- src/string_immutable.zig | 5 +- test/js/bun/css/css.test.ts | 2385 +++++++++++++------ test/js/bun/http/bun-serve-html.test.ts | 3 +- 40 files changed, 5687 insertions(+), 1481 deletions(-) create mode 100644 src/css/properties/grid.zig diff --git a/bun.lock b/bun.lock index 42a4352e71..4729944dcf 100644 --- a/bun.lock +++ b/bun.lock @@ -10,8 +10,8 @@ "@typescript-eslint/eslint-plugin": "^7.11.0", "@typescript-eslint/parser": "^7.11.0", "@vscode/debugadapter": "^1.65.0", - "autoprefixer": "^10.4.19", - "caniuse-lite": "^1.0.30001620", + "autoprefixer": "^10.4.20", + "caniuse-lite": "^1.0.30001660", "esbuild": "^0.21.4", "eslint": "^9.4.0", "eslint-config-prettier": "^9.1.0", @@ -265,7 +265,7 @@ "camel-case": ["camel-case@4.1.2", "", { "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" } }, "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw=="], - "caniuse-lite": ["caniuse-lite@1.0.30001653", "", {}, "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001695", "", {}, "sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw=="], "capital-case": ["capital-case@1.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case-first": "^2.0.2" } }, "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A=="], @@ -923,10 +923,14 @@ "are-we-there-yet/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "autoprefixer/caniuse-lite": ["caniuse-lite@1.0.30001653", "", {}, "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw=="], + "babel-code-frame/chalk": ["chalk@1.1.3", "", { "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" } }, "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A=="], "babel-code-frame/js-tokens": ["js-tokens@3.0.2", "", {}, "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg=="], + "browserslist/caniuse-lite": ["caniuse-lite@1.0.30001653", "", {}, "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw=="], + "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], diff --git a/package.json b/package.json index 1055954b3b..6364104b40 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "react-dom": "^18.3.1", "source-map-js": "^1.2.0", "typescript": "^5.7.2", - "caniuse-lite": "^1.0.30001620", - "autoprefixer": "^10.4.19", + "caniuse-lite": "^1.0.30001660", + "autoprefixer": "^10.4.20", "@mdn/browser-compat-data": "~5.5.28" }, "resolutions": { diff --git a/src/bitflags.zig b/src/bitflags.zig index 90fdbfc737..f851a69490 100644 --- a/src/bitflags.zig +++ b/src/bitflags.zig @@ -7,6 +7,8 @@ pub fn Bitflags(comptime T: type) type { const IntRepresentingNumOfBits = std.math.IntFittingRange(0, IntTypeInfo.Int.bits); return struct { + pub const IMPL_BITFLAGS: u0 = 0; + pub inline fn empty() T { return @bitCast(@as(IntType, 0)); } diff --git a/src/css/build-prefixes.js b/src/css/build-prefixes.js index bb36e78669..bd799194c2 100644 --- a/src/css/build-prefixes.js +++ b/src/css/build-prefixes.js @@ -520,16 +520,26 @@ let browsersZig = `pub const Browsers = struct { ${allBrowsers.join(": ?u32 = null,\n")}: ?u32 = null, pub usingnamespace BrowsersImpl(@This()); }`; +let field_len = 0; let flagsZig = `pub const Features = packed struct(u32) { ${flags .map((flag, i) => { if (Array.isArray(flag)) { // return `const ${flag[0]} = ${flag[1].map(f => `Self::${f}.bits()`).join(" | ")};`; - return `const ${flag[0]} = Features.fromNames(${flag[1].map(f => `"${f}"`).join(", ")});`; + return `pub const ${flag[0]} = Features.fromNames(&.{${flag[1].map(f => `"${f}"`).join(", ")}});`; } else { - return `${flag}: bool = 1 << ${i},`; + field_len++; + return `${flag}: bool = false,`; } }) + .concat(`__unused: u${32 - field_len} = 0,`) + .sort((a, b) => { + const a_is_const = a.startsWith("const"); + const b_is_const = b.startsWith("const"); + if (a_is_const && !b_is_const) return 1; + if (!a_is_const && b_is_const) return -1; + return 0; + }) .join("\n ")} pub usingnamespace css.Bitflags(@This()); @@ -593,7 +603,7 @@ pub const Feature = enum { pub fn prefixesFor(this: *const Feature, browsers: Browsers) VendorPrefix { var prefixes = VendorPrefix{ .none = true }; - switch (this.*) { + switch (this) { ${[...p] .map(([features, versions]) => { return `${features.map(name => `.${enumify(name)}`).join(" ,\n ")} => { @@ -673,7 +683,7 @@ const Browsers = @import("./targets.zig").Browsers; pub const Feature = enum { ${[...compat.keys()].flat().map(enumify).sort().join(",\n ")}, - pub fn isCompatible(this: *const Feature, browsers: Browsers) bool { + pub fn isCompatible(this: Feature, browsers: Browsers) bool { switch (this.*) { ${[...compat] .map( diff --git a/src/css/compat.zig b/src/css/compat.zig index 7d94f023d8..3dad0d4067 100644 --- a/src/css/compat.zig +++ b/src/css/compat.zig @@ -441,7 +441,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -533,7 +533,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -578,7 +578,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -623,7 +623,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -668,7 +668,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -713,7 +713,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -758,7 +758,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -803,7 +803,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -895,7 +895,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -940,7 +940,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -1004,23 +1004,13 @@ pub const Feature = enum { return false; } } - if (browsers.safari) |version| { - if (version < 721152) { - return false; - } - } if (browsers.opera) |version| { if (version < 4718592) { return false; } } - if (browsers.ios_saf) |version| { - if (version < 721664) { - return false; - } - } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -1029,7 +1019,7 @@ pub const Feature = enum { return false; } } - if (browsers.ie != null) { + if (browsers.ie != null or browsers.ios_saf != null or browsers.safari != null) { return false; } }, @@ -1065,7 +1055,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -1155,7 +1145,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -1200,7 +1190,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -1250,7 +1240,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -1337,7 +1327,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -1382,7 +1372,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -1427,7 +1417,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -1467,7 +1457,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -1512,7 +1502,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -1557,7 +1547,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } @@ -1617,7 +1607,7 @@ pub const Feature = enum { } } if (browsers.android) |version| { - if (version < 8323072) { + if (version < 8585216) { return false; } } diff --git a/src/css/css_parser.zig b/src/css/css_parser.zig index a73ff1d3fe..48a52dd51d 100644 --- a/src/css/css_parser.zig +++ b/src/css/css_parser.zig @@ -780,7 +780,7 @@ pub fn DeriveToCss(comptime T: type) type { if (comptime is_enum_or_union_enum) { inline for (std.meta.fields(T), 0..) |field, i| { if (@intFromEnum(this.*) == enum_fields[i].value) { - if (comptime field.type == void) { + if (comptime tyinfo == .Enum or field.type == void) { return dest.writeStr(enum_fields[i].name); } else if (comptime generic.hasToCss(field.type)) { return generic.toCss(field.type, &@field(this, field.name), W, dest); @@ -3812,6 +3812,18 @@ pub const Parser = struct { } } + pub fn expectSquareBracketBlock(this: *Parser) Result(void) { + const start_location = this.currentSourceLocation(); + const tok = switch (this.next()) { + .err => |e| return .{ .err = e }, + .result => |v| v, + }; + switch (tok.*) { + .open_square => return .{ .result = {} }, + else => return .{ .err = start_location.newUnexpectedTokenError(tok.*) }, + } + } + /// Parse a and return the unescaped value. pub fn expectUrl(this: *Parser) Result([]const u8) { const start_location = this.currentSourceLocation(); @@ -6393,23 +6405,10 @@ pub const serializer = struct { }; } else notation: { var buf: [129]u8 = undefined; - // We must pass finite numbers to dtoa_short - if (std.math.isPositiveInf(value)) { - const output = "1e999"; - try writer.writeAll(output); - return; - } else if (std.math.isNegativeInf(value)) { - const output = "-1e999"; - try writer.writeAll(output); - return; - } - // We shouldn't receive NaN here. - // NaN is not a valid CSS token and any inlined calculations from `calc()` we ensure - // are not NaN. - bun.debugAssert(!std.math.isNan(value)); - const str, const notation = dtoa_short(&buf, value, 6); + const str, const maybe_notaton = try dtoa_short(&buf, value, 6); try writer.writeAll(str); - break :notation notation; + if (maybe_notaton) |notation| break :notation notation; + return; }; if (int_value == null and fract(value) == 0) { @@ -6480,215 +6479,9 @@ pub const serializer = struct { } }; -pub inline fn implementDeepClone(comptime T: type, this: *const T, allocator: Allocator) T { - const tyinfo = @typeInfo(T); - - if (comptime bun.meta.isSimpleCopyType(T)) { - return this.*; - } - - if (comptime bun.meta.looksLikeListContainerType(T)) |result| { - return switch (result) { - .array_list => deepClone(result.child, allocator, this), - .baby_list => @panic("Not implemented."), - .small_list => this.deepClone(allocator), - }; - } - - if (comptime T == []const u8) { - return this.*; - } - - if (comptime @typeInfo(T) == .Pointer) { - const TT = std.meta.Child(T); - return implementEql(TT, this.*); - } - - return switch (tyinfo) { - .Struct => { - var strct: T = undefined; - inline for (tyinfo.Struct.fields) |field| { - if (comptime generic.canTransitivelyImplementDeepClone(field.type) and @hasDecl(field.type, "__generateDeepClone")) { - @field(strct, field.name) = implementDeepClone(field.type, &field(this, field.name, allocator)); - } else { - @field(strct, field.name) = generic.deepClone(field.type, &@field(this, field.name), allocator); - } - } - return strct; - }, - .Union => { - inline for (bun.meta.EnumFields(T), tyinfo.Union.fields) |enum_field, union_field| { - if (@intFromEnum(this.*) == enum_field.value) { - if (comptime generic.canTransitivelyImplementDeepClone(union_field.type) and @hasDecl(union_field.type, "__generateDeepClone")) { - return @unionInit(T, enum_field.name, implementDeepClone(union_field.type, &@field(this, enum_field.name), allocator)); - } - return @unionInit(T, enum_field.name, generic.deepClone(union_field.type, &@field(this, enum_field.name), allocator)); - } - } - unreachable; - }, - else => @compileError("Unhandled type " ++ @typeName(T)), - }; -} - -/// A function to implement `lhs.eql(&rhs)` for the many types in the CSS parser that needs this. -/// -/// This is the equivalent of doing `#[derive(PartialEq])` in Rust. -/// -/// This function only works on simple types like: -/// - Simple equality types (e.g. integers, floats, strings, enums, etc.) -/// - Types which implement a `.eql(lhs: *const @This(), rhs: *const @This()) bool` function -/// -/// Or compound types composed of simple types such as: -/// - Pointers to simple types -/// - Optional simple types -/// - Structs, Arrays, and Unions -pub fn implementEql(comptime T: type, this: *const T, other: *const T) bool { - const tyinfo = @typeInfo(T); - if (comptime bun.meta.isSimpleEqlType(T)) { - return this.* == other.*; - } - if (comptime T == []const u8) { - return bun.strings.eql(this.*, other.*); - } - if (comptime @typeInfo(T) == .Pointer) { - const TT = std.meta.Child(T); - return implementEql(TT, this.*, other.*); - } - if (comptime @typeInfo(T) == .Optional) { - const TT = std.meta.Child(T); - if (this.* != null and other.* != null) return implementEql(TT, &this.*.?, &other.*.?); - return false; - } - return switch (tyinfo) { - .Optional => @compileError("Handled above, this means Zack wrote a bug."), - .Pointer => @compileError("Handled above, this means Zack wrote a bug."), - .Array => { - const Child = std.meta.Child(T); - if (comptime bun.meta.isSimpleEqlType(Child)) { - return std.mem.eql(Child, &this.*, &other.*); - } - if (this.len != other.len) return false; - if (comptime generic.canTransitivelyImplementEql(Child) and @hasDecl(Child, "__generateEql")) { - for (this.*, other.*) |*a, *b| { - if (!implementEql(Child, &a, &b)) return false; - } - } else { - for (this.*, other.*) |*a, *b| { - if (!generic.eql(Child, a, b)) return false; - } - } - return true; - }, - .Struct => { - inline for (tyinfo.Struct.fields) |field| { - if (!generic.eql(field.type, &@field(this, field.name), &@field(other, field.name))) return false; - } - return true; - }, - .Union => { - if (tyinfo.Union.tag_type == null) @compileError("Unions must have a tag type"); - if (@intFromEnum(this.*) != @intFromEnum(other.*)) return false; - const enum_fields = bun.meta.EnumFields(T); - inline for (enum_fields, std.meta.fields(T)) |enum_field, union_field| { - if (enum_field.value == @intFromEnum(this.*)) { - if (union_field.type != void) { - if (comptime generic.canTransitivelyImplementEql(union_field.type) and @hasDecl(union_field.type, "__generateEql")) { - return implementEql(union_field.type, &@field(this, enum_field.name), &@field(other, enum_field.name)); - } - return generic.eql(union_field.type, &@field(this, enum_field.name), &@field(other, enum_field.name)); - } else { - return true; - } - } - } - unreachable; - }, - else => @compileError("Unsupported type: " ++ @typeName(T)), - }; -} - -pub fn implementHash(comptime T: type, this: *const T, hasher: *std.hash.Wyhash) void { - const tyinfo = @typeInfo(T); - if (comptime T == void) return; - if (comptime bun.meta.isSimpleEqlType(T)) { - return hasher.update(std.mem.asBytes(&this)); - } - if (comptime bun.meta.looksLikeListContainerType(T)) |result| { - const list = switch (result) { - .array_list => this.items[0..], - .baby_list => this.sliceConst(), - .small_list => this.slice(), - }; - bun.writeAnyToHasher(hasher, list.len); - for (list) |*item| { - generic.hash(tyinfo.Array.child, item, hasher); - } - return; - } - if (comptime T == []const u8) { - return hasher.update(this.*); - } - if (comptime @typeInfo(T) == .Pointer) { - @compileError("Invalid type for implementHash(): " ++ @typeName(T)); - } - if (comptime @typeInfo(T) == .Optional) { - @compileError("Invalid type for implementHash(): " ++ @typeName(T)); - } - return switch (tyinfo) { - .Optional => { - if (this.* == null) { - bun.writeAnyToHasher(hasher, "null"); - } else { - bun.writeAnyToHasher(hasher, "some"); - generic.hash(tyinfo.Optional.child, &this.*.?, hasher); - } - }, - .Pointer => { - generic.hash(tyinfo.Pointer.child, &this.*, hasher); - }, - .Array => { - bun.writeAnyToHasher(hasher, this.len); - for (this.*[0..]) |*item| { - generic.hash(tyinfo.Array.child, item, hasher); - } - }, - .Struct => { - inline for (tyinfo.Struct.fields) |field| { - if (comptime generic.hasHash(field.type)) { - generic.hash(field.type, &@field(this, field.name), hasher); - } else if (@hasDecl(field.type, "__generateHash") and @typeInfo(field.type) == .Struct) { - implementHash(field.type, &@field(this, field.name), hasher); - } else { - @compileError("Can't hash these fields: " ++ @typeName(field.type) ++ ". On " ++ @typeName(T)); - } - } - return; - }, - .Enum => { - bun.writeAnyToHasher(hasher, @intFromEnum(this.*)); - }, - .Union => { - if (tyinfo.Union.tag_type == null) @compileError("Unions must have a tag type"); - bun.writeAnyToHasher(hasher, @intFromEnum(this.*)); - const enum_fields = bun.meta.EnumFields(T); - inline for (enum_fields, std.meta.fields(T)) |enum_field, union_field| { - if (enum_field.value == @intFromEnum(this.*)) { - const field = union_field; - if (comptime generic.hasHash(field.type)) { - generic.hash(field.type, &@field(this, field.name), hasher); - } else if (@hasDecl(field.type, "__generateHash") and @typeInfo(field.type) == .Struct) { - implementHash(field.type, &@field(this, field.name), hasher); - } else { - @compileError("Can't hash these fields: " ++ @typeName(field.type) ++ ". On " ++ @typeName(T)); - } - } - } - return; - }, - else => @compileError("Unsupported type: " ++ @typeName(T)), - }; -} +pub const implementEql = generic.implementEql; +pub const implementHash = generic.implementHash; +pub const implementDeepClone = generic.implementDeepClone; pub const parse_utility = struct { /// Parse a value from a string. @@ -6772,12 +6565,9 @@ pub const to_css = struct { } pub fn float32(this: f32, writer: anytype) !void { - var scratch: [64]u8 = undefined; - // PERF/TODO: Compare this to Rust dtoa-short crate - const floats = std.fmt.formatFloat(scratch[0..], this, .{ - .mode = .decimal, - }) catch unreachable; - return writer.writeAll(floats); + var scratch: [129]u8 = undefined; + const str, _ = try dtoa_short(&scratch, this, 6); + return writer.writeAll(str); } fn maxDigits(comptime T: type) usize { @@ -6869,7 +6659,27 @@ const Notation = struct { } }; -pub fn dtoa_short(buf: *[129]u8, value: f32, comptime precision: u8) struct { []u8, Notation } { +/// Writes float with precision. +/// +/// Returns null if value was an infinite number +pub fn dtoa_short(buf: *[129]u8, value: f32, comptime precision: u8) !struct { []u8, ?Notation } { + // We must pass finite numbers to dtoa_short_impl + if (std.math.isPositiveInf(value)) { + buf[0.."1e999".len].* = "1e999".*; + return .{ buf[0.."1e999".len], null }; + } else if (std.math.isNegativeInf(value)) { + buf[0.."-1e999".len].* = "-1e999".*; + return .{ buf[0.."-1e999".len], null }; + } + // We shouldn't receive NaN here. + // NaN is not a valid CSS token and any inlined calculations from `calc()` we ensure + // are not NaN. + bun.debugAssert(!std.math.isNan(value)); + const str, const notation = dtoa_short_impl(buf, value, precision); + return .{ str, notation }; +} + +pub fn dtoa_short_impl(buf: *[129]u8, value: f32, comptime precision: u8) struct { []u8, Notation } { buf[0] = '0'; bun.debugAssert(std.math.isFinite(value)); const buf_len = bun.fmt.FormatDouble.dtoa(@ptrCast(buf[1..].ptr), @floatCast(value)).len; diff --git a/src/css/declaration.zig b/src/css/declaration.zig index 2ce5b57cf9..6c09604cd3 100644 --- a/src/css/declaration.zig +++ b/src/css/declaration.zig @@ -24,6 +24,10 @@ const FontHandler = css.css_properties.font.FontHandler; const InsetHandler = css.css_properties.margin_padding.InsetHandler; const SizeHandler = css.css_properties.size.SizeHandler; const FlexHandler = css.css_properties.flex.FlexHandler; +const AlignHandler = css.css_properties.@"align".AlignHandler; +const TransitionHandler = css.css_properties.transition.TransitionHandler; +const TransformHandler = css.css_properties.transform.TransformHandler; +// const GridHandler = css.css_properties.g /// A CSS declaration block. /// @@ -334,12 +338,15 @@ pub const DeclarationHandler = struct { background: BackgroundHandler = .{}, border: BorderHandler = .{}, flex: FlexHandler = .{}, + @"align": AlignHandler = .{}, size: SizeHandler = .{}, margin: MarginHandler = .{}, padding: PaddingHandler = .{}, scroll_margin: ScrollMarginHandler = .{}, + transition: TransitionHandler = .{}, font: FontHandler = .{}, inset: InsetHandler = .{}, + transform: TransformHandler = .{}, fallback: FallbackHandler = .{}, direction: ?css.css_properties.text.Direction, decls: DeclarationList, @@ -359,12 +366,15 @@ pub const DeclarationHandler = struct { this.background.finalize(&this.decls, context); this.border.finalize(&this.decls, context); this.flex.finalize(&this.decls, context); + this.@"align".finalize(&this.decls, context); this.size.finalize(&this.decls, context); this.margin.finalize(&this.decls, context); this.padding.finalize(&this.decls, context); this.scroll_margin.finalize(&this.decls, context); + this.transition.finalize(&this.decls, context); this.font.finalize(&this.decls, context); this.inset.finalize(&this.decls, context); + this.transform.finalize(&this.decls, context); this.fallback.finalize(&this.decls, context); } @@ -373,12 +383,15 @@ pub const DeclarationHandler = struct { return this.background.handleProperty(property, &this.decls, context) or this.border.handleProperty(property, &this.decls, context) or this.flex.handleProperty(property, &this.decls, context) or + this.@"align".handleProperty(property, &this.decls, context) or this.size.handleProperty(property, &this.decls, context) or this.margin.handleProperty(property, &this.decls, context) or this.padding.handleProperty(property, &this.decls, context) or this.scroll_margin.handleProperty(property, &this.decls, context) or + this.transition.handleProperty(property, &this.decls, context) or this.font.handleProperty(property, &this.decls, context) or this.inset.handleProperty(property, &this.decls, context) or + this.transform.handleProperty(property, &this.decls, context) or this.fallback.handleProperty(property, &this.decls, context); } diff --git a/src/css/generics.zig b/src/css/generics.zig index 8d90ca5287..11cbcd50ce 100644 --- a/src/css/generics.zig +++ b/src/css/generics.zig @@ -24,6 +24,220 @@ const DashedIdent = css.DashedIdent; const DashedIdentFns = css.DashedIdentFns; const Ident = css.Ident; const IdentFns = css.IdentFns; +const VendorPrefix = css.VendorPrefix; + +pub inline fn implementDeepClone(comptime T: type, this: *const T, allocator: Allocator) T { + const tyinfo = @typeInfo(T); + + if (comptime bun.meta.isSimpleCopyType(T)) { + return this.*; + } + + if (comptime bun.meta.looksLikeListContainerType(T)) |result| { + return switch (result) { + .array_list => deepClone(result.child, allocator, this), + .baby_list => @panic("Not implemented."), + .small_list => this.deepClone(allocator), + }; + } + + if (comptime T == []const u8) { + return this.*; + } + + if (comptime @typeInfo(T) == .Pointer) { + const TT = std.meta.Child(T); + return implementEql(TT, this.*); + } + + return switch (tyinfo) { + .Struct => { + var strct: T = undefined; + inline for (tyinfo.Struct.fields) |field| { + if (comptime canTransitivelyImplementDeepClone(field.type) and @hasDecl(field.type, "__generateDeepClone")) { + @field(strct, field.name) = implementDeepClone(field.type, &field(this, field.name, allocator)); + } else { + @field(strct, field.name) = deepClone(field.type, &@field(this, field.name), allocator); + } + } + return strct; + }, + .Union => { + inline for (bun.meta.EnumFields(T), tyinfo.Union.fields) |enum_field, union_field| { + if (@intFromEnum(this.*) == enum_field.value) { + if (comptime canTransitivelyImplementDeepClone(union_field.type) and @hasDecl(union_field.type, "__generateDeepClone")) { + return @unionInit(T, enum_field.name, implementDeepClone(union_field.type, &@field(this, enum_field.name), allocator)); + } + return @unionInit(T, enum_field.name, deepClone(union_field.type, &@field(this, enum_field.name), allocator)); + } + } + unreachable; + }, + else => @compileError("Unhandled type " ++ @typeName(T)), + }; +} + +/// A function to implement `lhs.eql(&rhs)` for the many types in the CSS parser that needs this. +/// +/// This is the equivalent of doing `#[derive(PartialEq])` in Rust. +/// +/// This function only works on simple types like: +/// - Simple equality types (e.g. integers, floats, strings, enums, etc.) +/// - Types which implement a `.eql(lhs: *const @This(), rhs: *const @This()) bool` function +/// +/// Or compound types composed of simple types such as: +/// - Pointers to simple types +/// - Optional simple types +/// - Structs, Arrays, and Unions +pub fn implementEql(comptime T: type, this: *const T, other: *const T) bool { + const tyinfo = @typeInfo(T); + if (comptime bun.meta.isSimpleEqlType(T)) { + return this.* == other.*; + } + if (comptime T == []const u8) { + return bun.strings.eql(this.*, other.*); + } + if (comptime @typeInfo(T) == .Pointer) { + const TT = std.meta.Child(T); + return implementEql(TT, this.*, other.*); + } + if (comptime @typeInfo(T) == .Optional) { + const TT = std.meta.Child(T); + if (this.* != null and other.* != null) return implementEql(TT, &this.*.?, &other.*.?); + return false; + } + if (comptime T == VendorPrefix) { + return VendorPrefix.eql(this.*, other.*); + } + return switch (tyinfo) { + .Optional => @compileError("Handled above, this means Zack wrote a bug."), + .Pointer => @compileError("Handled above, this means Zack wrote a bug."), + .Array => { + const Child = std.meta.Child(T); + if (comptime bun.meta.isSimpleEqlType(Child)) { + return std.mem.eql(Child, &this.*, &other.*); + } + if (this.len != other.len) return false; + if (comptime canTransitivelyImplementEql(Child) and @hasDecl(Child, "__generateEql")) { + for (this.*, other.*) |*a, *b| { + if (!implementEql(Child, &a, &b)) return false; + } + } else { + for (this.*, other.*) |*a, *b| { + if (!eql(Child, a, b)) return false; + } + } + return true; + }, + .Struct => { + inline for (tyinfo.Struct.fields) |field| { + if (!eql(field.type, &@field(this, field.name), &@field(other, field.name))) return false; + } + return true; + }, + .Union => { + if (tyinfo.Union.tag_type == null) @compileError("Unions must have a tag type"); + if (@intFromEnum(this.*) != @intFromEnum(other.*)) return false; + const enum_fields = bun.meta.EnumFields(T); + inline for (enum_fields, std.meta.fields(T)) |enum_field, union_field| { + if (enum_field.value == @intFromEnum(this.*)) { + if (union_field.type != void) { + if (comptime canTransitivelyImplementEql(union_field.type) and @hasDecl(union_field.type, "__generateEql")) { + return implementEql(union_field.type, &@field(this, enum_field.name), &@field(other, enum_field.name)); + } + return eql(union_field.type, &@field(this, enum_field.name), &@field(other, enum_field.name)); + } else { + return true; + } + } + } + unreachable; + }, + else => @compileError("Unsupported type: " ++ @typeName(T)), + }; +} + +pub fn implementHash(comptime T: type, this: *const T, hasher: *std.hash.Wyhash) void { + const tyinfo = @typeInfo(T); + if (comptime T == void) return; + if (comptime bun.meta.isSimpleEqlType(T)) { + return hasher.update(std.mem.asBytes(&this)); + } + if (comptime bun.meta.looksLikeListContainerType(T)) |result| { + const list = switch (result) { + .array_list => this.items[0..], + .baby_list => this.sliceConst(), + .small_list => this.slice(), + }; + bun.writeAnyToHasher(hasher, list.len); + for (list) |*item| { + hash(tyinfo.Array.child, item, hasher); + } + return; + } + if (comptime T == []const u8) { + return hasher.update(this.*); + } + if (comptime @typeInfo(T) == .Pointer) { + @compileError("Invalid type for implementHash(): " ++ @typeName(T)); + } + if (comptime @typeInfo(T) == .Optional) { + @compileError("Invalid type for implementHash(): " ++ @typeName(T)); + } + return switch (tyinfo) { + .Optional => { + if (this.* == null) { + bun.writeAnyToHasher(hasher, "null"); + } else { + bun.writeAnyToHasher(hasher, "some"); + hash(tyinfo.Optional.child, &this.*.?, hasher); + } + }, + .Pointer => { + hash(tyinfo.Pointer.child, &this.*, hasher); + }, + .Array => { + bun.writeAnyToHasher(hasher, this.len); + for (this.*[0..]) |*item| { + hash(tyinfo.Array.child, item, hasher); + } + }, + .Struct => { + inline for (tyinfo.Struct.fields) |field| { + if (comptime hasHash(field.type)) { + hash(field.type, &@field(this, field.name), hasher); + } else if (@hasDecl(field.type, "__generateHash") and @typeInfo(field.type) == .Struct) { + implementHash(field.type, &@field(this, field.name), hasher); + } else { + @compileError("Can't hash these fields: " ++ @typeName(field.type) ++ ". On " ++ @typeName(T)); + } + } + return; + }, + .Enum => { + bun.writeAnyToHasher(hasher, @intFromEnum(this.*)); + }, + .Union => { + if (tyinfo.Union.tag_type == null) @compileError("Unions must have a tag type"); + bun.writeAnyToHasher(hasher, @intFromEnum(this.*)); + const enum_fields = bun.meta.EnumFields(T); + inline for (enum_fields, std.meta.fields(T)) |enum_field, union_field| { + if (enum_field.value == @intFromEnum(this.*)) { + const field = union_field; + if (comptime hasHash(field.type)) { + hash(field.type, &@field(this, field.name), hasher); + } else if (@hasDecl(field.type, "__generateHash") and @typeInfo(field.type) == .Struct) { + implementHash(field.type, &@field(this, field.name), hasher); + } else { + @compileError("Can't hash these fields: " ++ @typeName(field.type) ++ ". On " ++ @typeName(T)); + } + } + } + return; + }, + else => @compileError("Unsupported type: " ++ @typeName(T)), + }; +} pub fn slice(comptime T: type, val: *const T) []const bun.meta.looksLikeListContainerType(T).?.child { if (comptime bun.meta.looksLikeListContainerType(T)) |result| { @@ -86,7 +300,7 @@ pub inline fn parse(comptime T: type, input: *Parser) Result(T) { } if (comptime @typeInfo(T) == .Optional) { const TT = std.meta.Child(T); - return .{ .result = parse(TT, input).asValue() }; + return .{ .result = input.tryParse(parseFor(TT), .{}).asValue() }; } if (comptime bun.meta.looksLikeListContainerType(T)) |result| { switch (result.list) { @@ -221,12 +435,15 @@ pub inline fn eql(comptime T: type, lhs: *const T, rhs: *const T) bool { .small_list => lhs.eql(rhs), }; } + if (@hasDecl(T, "IMPL_BITFLAGS")) { + return T.eql(lhs.*, rhs.*); + } return switch (T) { f32 => lhs.* == rhs.*, CSSInteger => lhs.* == rhs.*, CustomIdent, DashedIdent, Ident => bun.strings.eql(lhs.v, rhs.v), []const u8 => bun.strings.eql(lhs.*, rhs.*), - css.VendorPrefix => css.VendorPrefix.eq(lhs.*, rhs.*), + // css.VendorPrefix => css.VendorPrefix.eq(lhs.*, rhs.*), else => T.eql(lhs, rhs), }; } diff --git a/src/css/prefixes.zig b/src/css/prefixes.zig index 2a9b29edaf..dfb384fe54 100644 --- a/src/css/prefixes.zig +++ b/src/css/prefixes.zig @@ -540,7 +540,7 @@ pub const Feature = enum { }, .element => { if (browsers.firefox) |version| { - if (version >= 131072) { + if (version >= 131072 and version <= 8650752) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .moz = true }); } } @@ -641,7 +641,7 @@ pub const Feature = enum { } } if (browsers.ios_saf) |version| { - if (version >= 197120) { + if (version >= 197120 and version <= 1179648) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } @@ -651,7 +651,7 @@ pub const Feature = enum { } } if (browsers.safari) |version| { - if (version >= 196864) { + if (version >= 196864 and version <= 1179648) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } @@ -1146,17 +1146,17 @@ pub const Feature = enum { }, .fill, .fill_available => { if (browsers.chrome) |version| { - if (version >= 1441792) { + if (version >= 1441792 and version <= 8519680) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.android) |version| { - if (version >= 263168) { + if (version >= 263168 and version <= 8323072) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.edge) |version| { - if (version >= 5177344) { + if (version >= 5177344 and version <= 8323072) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } @@ -1181,7 +1181,7 @@ pub const Feature = enum { } } if (browsers.samsung) |version| { - if (version >= 262144) { + if (version >= 262144 and version <= 1638400) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } @@ -1225,27 +1225,27 @@ pub const Feature = enum { }, .stretch => { if (browsers.chrome) |version| { - if (version >= 1441792) { + if (version >= 1441792 and version <= 8519680) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.firefox) |version| { - if (version >= 196608) { + if (version >= 196608 and version <= 8650752) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .moz = true }); } } if (browsers.android) |version| { - if (version >= 263168) { + if (version >= 263168 and version <= 8323072) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.edge) |version| { - if (version >= 5177344) { + if (version >= 5177344 and version <= 8323072) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.ios_saf) |version| { - if (version >= 458752) { + if (version >= 458752 and version <= 1179648) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } @@ -1255,12 +1255,12 @@ pub const Feature = enum { } } if (browsers.safari) |version| { - if (version >= 458752) { + if (version >= 458752 and version <= 1179648) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.samsung) |version| { - if (version >= 327680) { + if (version >= 327680 and version <= 1638400) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } @@ -1330,7 +1330,7 @@ pub const Feature = enum { }, .text_decoration_skip, .text_decoration_skip_ink => { if (browsers.ios_saf) |version| { - if (version >= 524288) { + if (version >= 524288 and version <= 1179648) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } @@ -1342,12 +1342,12 @@ pub const Feature = enum { }, .text_decoration => { if (browsers.ios_saf) |version| { - if (version >= 524288) { + if (version >= 524288 and version <= 1179648) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.safari) |version| { - if (version >= 524288) { + if (version >= 524288 and version <= 1179648) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } @@ -1370,8 +1370,10 @@ pub const Feature = enum { } }, .text_size_adjust => { - if (browsers.firefox != null) { - prefixes = prefixes.bitwiseOr(VendorPrefix{ .moz = true }); + if (browsers.firefox) |version| { + if (version <= 8323072) { + prefixes = prefixes.bitwiseOr(VendorPrefix{ .moz = true }); + } } if (browsers.edge) |version| { if (version >= 786432 and version <= 1179648) { @@ -1384,7 +1386,7 @@ pub const Feature = enum { } } if (browsers.ios_saf) |version| { - if (version >= 327680) { + if (version >= 327680 and version <= 1179648) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } @@ -1460,22 +1462,22 @@ pub const Feature = enum { }, .box_decoration_break => { if (browsers.chrome) |version| { - if (version >= 1441792) { + if (version >= 1441792 and version <= 8519680) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.android) |version| { - if (version >= 263168) { + if (version >= 263168 and version <= 8323072) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.edge) |version| { - if (version >= 5177344) { + if (version >= 5177344 and version <= 8323072) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.ios_saf) |version| { - if (version >= 458752) { + if (version >= 458752 and version <= 1179648) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } @@ -1485,12 +1487,12 @@ pub const Feature = enum { } } if (browsers.safari) |version| { - if (version >= 393472) { + if (version >= 393472 and version <= 1179648) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.samsung) |version| { - if (version >= 262144) { + if (version >= 262144 and version <= 1638400) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } @@ -1848,17 +1850,17 @@ pub const Feature = enum { }, .cross_fade => { if (browsers.chrome) |version| { - if (version >= 1114112) { + if (version >= 1114112 and version <= 8519680) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.android) |version| { - if (version >= 263168) { + if (version >= 263168 and version <= 8323072) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.edge) |version| { - if (version >= 5177344) { + if (version >= 5177344 and version <= 8323072) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } @@ -1878,7 +1880,7 @@ pub const Feature = enum { } } if (browsers.samsung) |version| { - if (version >= 262144) { + if (version >= 262144 and version <= 1638400) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } @@ -2065,17 +2067,17 @@ pub const Feature = enum { }, .print_color_adjust, .color_adjust => { if (browsers.chrome) |version| { - if (version >= 1114112) { + if (version >= 1114112 and version <= 8519680) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.android) |version| { - if (version >= 263168) { + if (version >= 263168 and version <= 8323072) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } if (browsers.edge) |version| { - if (version >= 5177344) { + if (version >= 5177344 and version <= 8323072) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } @@ -2100,7 +2102,7 @@ pub const Feature = enum { } } if (browsers.samsung) |version| { - if (version >= 262144) { + if (version >= 262144 and version <= 1638400) { prefixes = prefixes.bitwiseOr(VendorPrefix{ .webkit = true }); } } diff --git a/src/css/printer.zig b/src/css/printer.zig index c50adb3bcd..b68c1086bb 100644 --- a/src/css/printer.zig +++ b/src/css/printer.zig @@ -160,6 +160,18 @@ pub fn Printer(comptime Writer: type) type { return PrintErr.lol; } + pub fn addInvalidCssModulesPatternInGridError(this: *This) PrintErr!void { + this.error_kind = css.PrinterError{ + .kind = .invalid_css_modules_pattern_in_grid, + .loc = css.ErrorLocation{ + .filename = this.filename(), + .line = this.loc.line, + .column = this.loc.column, + }, + }; + return PrintErr.lol; + } + /// Returns an error of the given kind at the provided location in the current source file. pub fn newError( this: *This, diff --git a/src/css/properties/align.zig b/src/css/properties/align.zig index d5f3f5b716..af5fd00cb4 100644 --- a/src/css/properties/align.zig +++ b/src/css/properties/align.zig @@ -10,6 +10,10 @@ const PrintErr = css.PrintErr; const LengthPercentage = css.css_values.length.LengthPercentage; +const VendorPrefix = css.VendorPrefix; +const Property = css.Property; +const Feature = css.prefixes.Feature; + /// A value for the [align-content](https://www.w3.org/TR/css-align-3/#propdef-align-content) property. pub const AlignContent = union(enum) { /// Default alignment. @@ -35,7 +39,8 @@ pub const AlignContent = union(enum) { pub fn __generateToCss() void {} pub fn parse(input: *css.Parser) css.Result(@This()) { - const overflow = OverflowPosition.parse(input).asValue(); + const overflow = input.tryParse(OverflowPosition.parse, .{}).asValue(); + const value = switch (ContentPosition.parse(input)) { .result => |v| v, .err => |e| return .{ .err = e }, @@ -174,16 +179,16 @@ pub const JustifyContent = union(enum) { }, pub fn parse(input: *css.Parser) css.Result(@This()) { - if (input.expectIdentMatching("normal").isOk()) { + if (input.tryParse(css.Parser.expectIdentMatching, .{"normal"}).isOk()) { return .{ .result = .normal }; } - if (ContentDistribution.parse(input).asValue()) |val| { + if (input.tryParse(ContentDistribution.parse, .{}).asValue()) |val| { return .{ .result = .{ .content_distribution = val } }; } - const overflow = OverflowPosition.parse(input).asValue(); - if (ContentPosition.parse(input).asValue()) |content_position| { + const overflow = input.tryParse(OverflowPosition.parse, .{}).asValue(); + if (input.tryParse(ContentPosition.parse, .{}).asValue()) |content_position| { return .{ .result = .{ .content_position = .{ .overflow = overflow, @@ -281,7 +286,7 @@ pub const AlignSelf = union(enum) { } pub fn parse(input: *css.Parser) css.Result(@This()) { - const overflow = OverflowPosition.parse(input).asValue(); + const overflow = input.tryParse(OverflowPosition.parse, .{}).asValue(); const self_position = switch (SelfPosition.parse(input)) { .result => |v| v, .err => |e| return .{ .err = e }, @@ -465,7 +470,7 @@ pub const AlignItems = union(enum) { } pub fn parse(input: *css.Parser) css.Result(@This()) { - const overflow = OverflowPosition.parse(input).asValue(); + const overflow = input.tryParse(OverflowPosition.parse, .{}).asValue(); const self_position = switch (SelfPosition.parse(input)) { .result => |v| v, .err => |e| return .{ .err = e }, @@ -1083,3 +1088,420 @@ pub const ContentPositionInner = struct { return css.implementEql(@This(), lhs, rhs); } }; + +const FlexLinePack = css.css_properties.flex.FlexLinePack; +const BoxPack = css.css_properties.flex.BoxPack; +const FlexPack = css.css_properties.flex.FlexPack; +const BoxAlign = css.css_properties.flex.BoxAlign; +const FlexAlign = css.css_properties.flex.FlexAlign; +const FlexItemAlign = css.css_properties.flex.FlexItemAlign; + +pub const AlignHandler = struct { + align_content: ?struct { AlignContent, VendorPrefix } = null, + flex_line_pack: ?struct { FlexLinePack, VendorPrefix } = null, + justify_content: ?struct { JustifyContent, VendorPrefix } = null, + box_pack: ?struct { BoxPack, VendorPrefix } = null, + flex_pack: ?struct { FlexPack, VendorPrefix } = null, + align_self: ?struct { AlignSelf, VendorPrefix } = null, + flex_item_align: ?struct { FlexItemAlign, VendorPrefix } = null, + justify_self: ?JustifySelf = null, + align_items: ?struct { AlignItems, VendorPrefix } = null, + box_align: ?struct { BoxAlign, VendorPrefix } = null, + flex_align: ?struct { FlexAlign, VendorPrefix } = null, + justify_items: ?JustifyItems = null, + row_gap: ?GapValue = null, + column_gap: ?GapValue = null, + has_any: bool = false, + + pub fn handleProperty(this: *AlignHandler, property: *const Property, dest: *css.DeclarationList, context: *css.PropertyHandlerContext) bool { + switch (property.*) { + .@"align-content" => |*val| { + this.flex_line_pack = null; + this.handlePropertyHelper(dest, context, "align_content", &val.*[0], val.*[1]); + }, + .@"flex-line-pack" => |*val| this.handlePropertyHelper(dest, context, "flex_line_pack", &val.*[0], val.*[1]), + .@"justify-content" => |*val| { + this.box_pack = null; + this.flex_pack = null; + this.handlePropertyHelper(dest, context, "justify_content", &val.*[0], val.*[1]); + }, + .@"box-pack" => |*val| this.handlePropertyHelper(dest, context, "box_pack", &val.*[0], val.*[1]), + .@"flex-pack" => |*val| this.handlePropertyHelper(dest, context, "flex_pack", &val.*[0], val.*[1]), + .@"place-content" => |*val| { + this.flex_line_pack = null; + this.box_pack = null; + this.flex_pack = null; + this.handlePropertyMaybeFlush(dest, context, "align_content", &val.@"align", VendorPrefix.NONE); + this.handlePropertyMaybeFlush(dest, context, "justify_content", &val.justify, VendorPrefix.NONE); + this.handlePropertyHelper(dest, context, "align_content", &val.@"align", VendorPrefix.NONE); + this.handlePropertyHelper(dest, context, "justify_content", &val.justify, VendorPrefix.NONE); + }, + .@"align-self" => |*val| { + this.flex_item_align = null; + this.handlePropertyHelper(dest, context, "align_self", &val.*[0], val.*[1]); + }, + .@"flex-item-align" => |*val| this.handlePropertyHelper(dest, context, "flex_item_align", &val.*[0], val.*[1]), + .@"justify-self" => |*val| { + this.justify_self = css.generic.deepClone(@TypeOf(val.*), val, context.allocator); + this.has_any = true; + }, + .@"place-self" => |*val| { + this.flex_item_align = null; + this.handlePropertyHelper(dest, context, "align_self", &val.@"align", VendorPrefix.NONE); + this.justify_self = css.generic.deepClone(@TypeOf(val.justify), &val.justify, context.allocator); + }, + .@"align-items" => |*val| { + this.box_align = null; + this.flex_align = null; + this.handlePropertyHelper(dest, context, "align_items", &val.*[0], val.*[1]); + }, + .@"box-align" => |*val| this.handlePropertyHelper(dest, context, "box_align", &val.*[0], val.*[1]), + .@"flex-align" => |*val| this.handlePropertyHelper(dest, context, "flex_align", &val.*[0], val.*[1]), + .@"justify-items" => |*val| { + this.justify_items = css.generic.deepClone(@TypeOf(val.*), val, context.allocator); + this.has_any = true; + }, + .@"place-items" => |*val| { + this.box_align = null; + this.flex_align = null; + this.handlePropertyHelper(dest, context, "align_items", &val.@"align", VendorPrefix.NONE); + this.justify_items = css.generic.deepClone(@TypeOf(val.justify), &val.justify, context.allocator); + }, + .@"row-gap" => |*val| { + this.row_gap = css.generic.deepClone(@TypeOf(val.*), val, context.allocator); + this.has_any = true; + }, + .@"column-gap" => |*val| { + this.column_gap = css.generic.deepClone(@TypeOf(val.*), val, context.allocator); + this.has_any = true; + }, + .gap => |*val| { + this.row_gap = css.generic.deepClone(@TypeOf(val.row), &val.row, context.allocator); + this.column_gap = css.generic.deepClone(@TypeOf(val.column), &val.column, context.allocator); + this.has_any = true; + }, + .unparsed => |*val| { + if (isAlignProperty(val.property_id)) { + this.flush(dest, context); + dest.append(context.allocator, property.*) catch bun.outOfMemory(); + } else { + return false; + } + }, + else => return false, + } + + return true; + } + + pub fn finalize(this: *AlignHandler, dest: *css.DeclarationList, context: *css.PropertyHandlerContext) void { + this.flush(dest, context); + } + + fn flush(this: *AlignHandler, dest: *css.DeclarationList, context: *css.PropertyHandlerContext) void { + if (!this.has_any) { + return; + } + + this.has_any = false; + + var align_content = bun.take(&this.align_content); + var justify_content = bun.take(&this.justify_content); + var align_self = bun.take(&this.align_self); + var justify_self = bun.take(&this.justify_self); + var align_items = bun.take(&this.align_items); + var justify_items = bun.take(&this.justify_items); + const row_gap = bun.take(&this.row_gap); + const column_gap = bun.take(&this.column_gap); + var box_align = bun.take(&this.box_align); + var box_pack = bun.take(&this.box_pack); + var flex_line_pack = bun.take(&this.flex_line_pack); + var flex_pack = bun.take(&this.flex_pack); + var flex_align = bun.take(&this.flex_align); + var flex_item_align = bun.take(&this.flex_item_align); + + // 2009 properties + this.flushPrefixedProperty(dest, context, "box-align", bun.take(&box_align)); + this.flushPrefixedProperty(dest, context, "box-pack", bun.take(&box_pack)); + + // 2012 properties + this.flushPrefixedProperty(dest, context, "flex-pack", bun.take(&flex_pack)); + this.flushPrefixedProperty(dest, context, "flex-align", bun.take(&flex_align)); + this.flushPrefixedProperty(dest, context, "flex-item-align", bun.take(&flex_item_align)); + this.flushPrefixedProperty(dest, context, "flex-line-pack", bun.take(&flex_line_pack)); + + this.flushLegacyProperty(dest, context, Feature.align_content, &align_content, null, .{ FlexLinePack, "flex-line-pack" }); + this.flushLegacyProperty(dest, context, Feature.justify_content, &justify_content, .{ BoxPack, "box-pack" }, .{ FlexPack, "flex-pack" }); + if (context.targets.isCompatible(.place_content)) { + this.flushShorthandHelper( + dest, + context, + .{ .prop = "place-content", .ty = PlaceContent }, + .{ .feature = Feature.align_content, .prop = "align-content" }, + &align_content, + &justify_content, + .{ .feature = Feature.justify_content, .prop = "justify-content" }, + ); + } + this.flushStandardPropertyHelper(dest, context, "align-content", bun.take(&align_content), Feature.align_content); + this.flushStandardPropertyHelper(dest, context, "justify-content", bun.take(&justify_content), Feature.justify_content); + + this.flushLegacyProperty(dest, context, Feature.align_self, &align_self, null, .{ FlexItemAlign, "flex-item-align" }); + if (context.targets.isCompatible(.place_self)) { + this.flushShorthandHelper(dest, context, .{ .prop = "place-self", .ty = PlaceSelf }, .{ .feature = Feature.align_self, .prop = "align-self" }, &align_self, &justify_self, null); + } + this.flushStandardPropertyHelper(dest, context, "align-self", bun.take(&align_self), Feature.align_self); + this.flushUnprefixProperty(dest, context, "justify-self", bun.take(&justify_self)); + + this.flushLegacyProperty(dest, context, Feature.align_items, &align_items, .{ BoxAlign, "box-align" }, .{ FlexAlign, "flex-align" }); + if (context.targets.isCompatible(css.compat.Feature.place_items)) { + this.flushShorthandHelper(dest, context, .{ .prop = "place-items", .ty = PlaceItems }, .{ .feature = Feature.align_items, .prop = "align-items" }, &align_items, &justify_items, null); + } + this.flushStandardPropertyHelper(dest, context, "align-items", bun.take(&align_items), Feature.align_items); + this.flushUnprefixProperty(dest, context, "justify-items", bun.take(&justify_items)); + + if (row_gap != null and column_gap != null) { + dest.append(context.allocator, Property{ .gap = Gap{ + .row = row_gap.?, + .column = column_gap.?, + } }) catch bun.outOfMemory(); + } else { + if (row_gap != null) { + dest.append(context.allocator, Property{ .@"row-gap" = row_gap.? }) catch bun.outOfMemory(); + } + + if (column_gap != null) { + dest.append(context.allocator, Property{ .@"column-gap" = column_gap.? }) catch bun.outOfMemory(); + } + } + } + + fn handlePropertyMaybeFlush(this: *AlignHandler, dest: *css.DeclarationList, context: *css.PropertyHandlerContext, comptime prop: []const u8, val: anytype, vp: VendorPrefix) void { + // If two vendor prefixes for the same property have different + // values, we need to flush what we have immediately to preserve order. + if (@field(this, prop)) |*v| { + if (!val.eql(&v[0]) and !v[1].contains(vp)) { + this.flush(dest, context); + } + } + } + + fn handlePropertyHelper(this: *AlignHandler, dest: *css.DeclarationList, context: *css.PropertyHandlerContext, comptime prop: []const u8, val: anytype, vp: VendorPrefix) void { + this.handlePropertyMaybeFlush(dest, context, prop, val, vp); + // Otherwise, update the value and add the prefix. + if (@field(this, prop)) |*tuple| { + tuple.*[0] = css.generic.deepClone(@TypeOf(val.*), val, context.allocator); + tuple.*[1].insert(vp); + } else { + @field(this, prop) = .{ css.generic.deepClone(@TypeOf(val.*), val, context.allocator), vp }; + this.has_any = true; + } + } + + // Gets prefixes for standard properties. + fn flushPrefixesHelper(_: *AlignHandler, context: *css.PropertyHandlerContext, comptime feature: Feature) VendorPrefix { + var prefix = context.targets.prefixes(VendorPrefix.NONE, feature); + // Firefox only implemented the 2009 spec prefixed. + // Microsoft only implemented the 2012 spec prefixed. + prefix.remove(VendorPrefix{ + .moz = true, + .ms = true, + }); + return prefix; + } + + fn flushStandardPropertyHelper(this: *AlignHandler, dest: *css.DeclarationList, context: *css.PropertyHandlerContext, comptime prop: []const u8, key: anytype, comptime feature: Feature) void { + if (key) |v| { + const val = v[0]; + var prefix = v[1]; + // If we have an unprefixed property, override necessary prefixes. + prefix = if (prefix.contains(VendorPrefix.NONE)) flushPrefixesHelper(this, context, feature) else prefix; + dest.append(context.allocator, @unionInit(Property, prop, .{ val, prefix })) catch bun.outOfMemory(); + } + } + + fn flushLegacyProperty( + this: *AlignHandler, + dest: *css.DeclarationList, + context: *css.PropertyHandlerContext, + comptime feature: Feature, + key: anytype, + comptime prop_2009: ?struct { type, []const u8 }, + comptime prop_2012: ?struct { type, []const u8 }, + ) void { + _ = this; // autofix + if (key.*) |v| { + const val = v[0]; + var prefix = v[1]; + // If we have an unprefixed standard property, generate legacy prefixed versions. + prefix = context.targets.prefixes(prefix, feature); + + if (prefix.contains(VendorPrefix.NONE)) { + if (comptime prop_2009) |p2009| { + // 2009 spec, implemented by webkit and firefox. + if (context.targets.browsers) |targets| { + var prefixes_2009 = VendorPrefix.empty(); + if (Feature.isFlex2009(targets)) { + prefixes_2009.insert(VendorPrefix.WEBKIT); + } + if (prefix.contains(VendorPrefix.MOZ)) { + prefixes_2009.insert(VendorPrefix.MOZ); + } + if (!prefixes_2009.isEmpty()) { + const s = brk: { + const T = comptime p2009[0]; + if (comptime T == css.css_properties.flex.BoxOrdinalGroup) break :brk @as(?i32, val); + break :brk p2009[0].fromStandard(&val); + }; + if (s) |a| { + dest.append(context.allocator, @unionInit(Property, p2009[1], .{ + a, + prefixes_2009, + })) catch bun.outOfMemory(); + } + } + } + } + } + + // 2012 spec, implemented by microsoft. + if (prefix.contains(VendorPrefix.MS)) { + if (comptime prop_2012) |p2012| { + const s = brk: { + const T = comptime p2012[0]; + if (comptime T == css.css_properties.flex.BoxOrdinalGroup) break :brk @as(?i32, val); + break :brk p2012[0].fromStandard(&val); + }; + if (s) |q| { + dest.append(context.allocator, @unionInit(Property, p2012[1], .{ + q, + VendorPrefix.MS, + })) catch bun.outOfMemory(); + } + } + } + + // Remove Firefox and IE from standard prefixes. + prefix.remove(VendorPrefix.MOZ); + prefix.remove(VendorPrefix.MS); + } + } + + fn flushPrefixedProperty(this: *AlignHandler, dest: *css.DeclarationList, context: *css.PropertyHandlerContext, comptime prop: []const u8, key: anytype) void { + _ = this; // autofix + if (key) |v| { + const val = v[0]; + const prefix = v[1]; + dest.append(context.allocator, @unionInit(Property, prop, .{ val, prefix })) catch bun.outOfMemory(); + } + } + + fn flushUnprefixProperty(this: *AlignHandler, dest: *css.DeclarationList, context: *css.PropertyHandlerContext, comptime prop: []const u8, key: anytype) void { + _ = this; // autofix + if (key) |v| { + const val = v; + dest.append(context.allocator, @unionInit(Property, prop, val)) catch bun.outOfMemory(); + } + } + + fn flushShorthandHelper( + this: *AlignHandler, + dest: *css.DeclarationList, + context: *css.PropertyHandlerContext, + comptime prop: struct { prop: []const u8, ty: type }, + comptime align_prop: struct { + feature: Feature, + prop: []const u8, + }, + align_val: anytype, + justify_val: anytype, + comptime justify_prop: ?struct { + feature: Feature, + prop: []const u8, + }, + ) void { + // Only use shorthand if both align and justify are present + if (align_val.*) |*__v1| { + const @"align" = &__v1.*[0]; + const align_prefix: *css.VendorPrefix = &__v1.*[1]; + if (justify_val.*) |*__v2| { + const justify = __v2; + + const intersection = align_prefix.bitwiseAnd(if (comptime justify_prop != null) __v2.*[1] else align_prefix.*); + // Only use shorthand if unprefixed. + if (intersection.contains(VendorPrefix.NONE)) { + // Add prefixed longhands if needed. + align_prefix.* = flushPrefixesHelper(this, context, align_prop.feature); + align_prefix.remove(VendorPrefix.NONE); + if (!align_prefix.isEmpty()) { + dest.append( + context.allocator, + @unionInit(Property, align_prop.prop, .{ css.generic.deepClone(@TypeOf(@"align".*), @"align", context.allocator), align_prefix.* }), + ) catch bun.outOfMemory(); + } + + if (comptime justify_prop != null) { + const justify_actual = &__v2.*[0]; + const justify_prefix = &__v2.*[1]; + justify_prefix.* = this.flushPrefixesHelper(context, justify_prop.?.feature); + justify_prefix.remove(css.VendorPrefix.NONE); + + if (!justify_prefix.isEmpty()) { + dest.append( + context.allocator, + @unionInit(Property, justify_prop.?.prop, .{ css.generic.deepClone(@TypeOf(justify_actual.*), justify_actual, context.allocator), justify_prefix.* }), + ) catch bun.outOfMemory(); + } + + // Add shorthand. + dest.append( + context.allocator, + @unionInit(Property, prop.prop, prop.ty{ + .@"align" = css.generic.deepClone(@TypeOf(@"align".*), @"align", context.allocator), + .justify = css.generic.deepClone(@TypeOf(justify_actual.*), justify_actual, context.allocator), + }), + ) catch bun.outOfMemory(); + } else { + + // Add shorthand. + dest.append( + context.allocator, + @unionInit(Property, prop.prop, prop.ty{ + .@"align" = css.generic.deepClone(@TypeOf(@"align".*), @"align", context.allocator), + .justify = css.generic.deepClone(@TypeOf(justify.*), justify, context.allocator), + }), + ) catch bun.outOfMemory(); + } + + align_val.* = null; + justify_val.* = null; + } + } + } + } +}; + +fn isAlignProperty(property_id: css.PropertyId) bool { + return switch (property_id) { + .@"align-content", + .@"flex-line-pack", + .@"justify-content", + .@"box-pack", + .@"flex-pack", + .@"place-content", + .@"align-self", + .@"flex-item-align", + .@"justify-self", + .@"place-self", + .@"align-items", + .@"box-align", + .@"flex-align", + .@"justify-items", + .@"place-items", + .@"row-gap", + .@"column-gap", + .gap, + => true, + else => false, + }; +} diff --git a/src/css/properties/animation.zig b/src/css/properties/animation.zig index d6d8fb198f..aed8627d78 100644 --- a/src/css/properties/animation.zig +++ b/src/css/properties/animation.zig @@ -16,6 +16,8 @@ const CSSNumber = css.css_values.number.CSSNumber; const LengthPercentageOrAuto = css.css_values.length.LengthPercentageOrAuto; const Size2D = css.css_values.size.Size2D; const DashedIdent = css.css_values.ident.DashedIdent; +const Time = css.css_values.time.Time; +const EasingFunction = css.css_values.easing.EasingFunction; /// A list of animations. pub const AnimationList = SmallList(Animation, 1); @@ -24,7 +26,198 @@ pub const AnimationList = SmallList(Animation, 1); pub const AnimationNameList = SmallList(AnimationName, 1); /// A value for the [animation](https://drafts.csswg.org/css-animations/#animation) shorthand property. -pub const Animation = @compileError(css.todo_stuff.depth); +pub const Animation = struct { + /// The animation name. + name: AnimationName, + /// The animation duration. + duration: Time, + /// The easing function for the animation. + timing_function: EasingFunction, + /// The number of times the animation will run. + iteration_count: AnimationIterationCount, + /// The direction of the animation. + direction: AnimationDirection, + /// The current play state of the animation. + play_state: AnimationPlayState, + /// The animation delay. + delay: Time, + /// The animation fill mode. + fill_mode: AnimationFillMode, + /// The animation timeline. + timeline: AnimationTimeline, + + pub usingnamespace css.DefineListShorthand(@This()); + + pub const PropertyFieldMap = .{ + .name = css.PropertyIdTag.@"animation-name", + .duration = css.PropertyIdTag.@"animation-duration", + .timing_function = css.PropertyIdTag.@"animation-timing-function", + .iteration_count = css.PropertyIdTag.@"animation-iteration-count", + .direction = css.PropertyIdTag.@"animation-direction", + .play_state = css.PropertyIdTag.@"animation-play-state", + .delay = css.PropertyIdTag.@"animation-delay", + .fill_mode = css.PropertyIdTag.@"animation-fill-mode", + .timeline = css.PropertyIdTag.@"animation-timeline", + }; + + pub const VendorPrefixMap = .{ + .name = true, + .duration = true, + .timing_function = true, + .iteration_count = true, + .direction = true, + .play_state = true, + .delay = true, + .fill_mode = true, + }; + + pub fn parse(input: *css.Parser) css.Result(@This()) { + var name: ?AnimationName = null; + var duration: ?Time = null; + var timing_function: ?EasingFunction = null; + var iteration_count: ?AnimationIterationCount = null; + var direction: ?AnimationDirection = null; + var play_state: ?AnimationPlayState = null; + var delay: ?Time = null; + var fill_mode: ?AnimationFillMode = null; + var timeline: ?AnimationTimeline = null; + + while (true) { + if (duration == null) { + if (input.tryParse(Time.parse, .{})) |value| { + duration = value; + continue; + } + } + if (timing_function == null) { + if (input.tryParse(EasingFunction.parse, .{})) |value| { + timing_function = value; + continue; + } + } + if (delay == null) { + if (input.tryParse(Time.parse, .{})) |value| { + delay = value; + continue; + } + } + if (iteration_count == null) { + if (input.tryParse(AnimationIterationCount.parse, .{})) |value| { + iteration_count = value; + continue; + } + } + if (direction == null) { + if (input.tryParse(AnimationDirection.parse, .{})) |value| { + direction = value; + continue; + } + } + if (fill_mode == null) { + if (input.tryParse(AnimationFillMode.parse, .{})) |value| { + fill_mode = value; + continue; + } + } + if (play_state == null) { + if (input.tryParse(AnimationPlayState.parse, .{})) |value| { + play_state = value; + continue; + } + } + if (name == null) { + if (input.tryParse(AnimationName.parse, .{})) |value| { + name = value; + continue; + } + } + if (timeline == null) { + if (input.tryParse(AnimationTimeline.parse, .{})) |value| { + timeline = value; + continue; + } + } + break; + } + + return .{ + .result = Animation{ + .name = name orelse AnimationName.none, + .duration = duration orelse Time{ .seconds = 0.0 }, + .timing_function = timing_function orelse EasingFunction.ease, + .iteration_count = iteration_count orelse AnimationIterationCount.number(1), + .direction = direction orelse AnimationDirection.normal, + .play_state = play_state orelse AnimationPlayState.running, + .delay = delay orelse Time{ .seconds = 0.0 }, + .fill_mode = fill_mode orelse AnimationFillMode.none, + .timeline = timeline orelse AnimationTimeline.auto, + }, + }; + } + + pub fn toCss(this: *const @This(), comptime W: type, dest: *Printer(W)) PrintErr!void { + switch (this.name) { + .none => {}, + inline .ident, .string => |name| { + const name_str = if (this.name == .ident) name.v else name; + + if (!this.duration.isZero() or !this.delay.isZero()) { + try this.duration.toCss(W, dest); + try dest.writeChar(' '); + } + + if (!this.timing_function.isEase() or EasingFunction.isIdent(name_str)) { + try this.timing_function.toCss(W, dest); + try dest.writeChar(' '); + } + + if (!this.delay.isZero()) { + try this.delay.toCss(W, dest); + try dest.writeChar(' '); + } + + if (!this.iteration_count.eql(&AnimationIterationCount.default()) or bun.strings.eqlCaseInsensitiveASCII(name_str, "infinite")) { + try this.iteration_count.toCss(W, dest); + try dest.writeChar(' '); + } + + if (!this.direction.eql(&AnimationDirection.default()) or css.parse_utility.parseString( + dest.allocator, + AnimationDirection, + name_str, + AnimationDirection.parse, + ).isOk()) { + try this.direction.toCss(W, dest); + try dest.writeChar(' '); + } + + if (!this.fill_mode.eql(&AnimationFillMode.default()) or + (!bun.strings.eqlCaseInsensitiveASCII(name_str, "none") and css.parse_utility.parseString(dest.allocator, AnimationFillMode, name_str, AnimationFillMode.parse).isOk())) + { + try this.fill_mode.toCss(W, dest); + try dest.writeChar(' '); + } + + if (!this.play_state.eql(&AnimationPlayState.default()) or css.parse_utility.parseString( + dest.allocator, + AnimationPlayState, + name_str, + AnimationPlayState.parse, + ).isOk()) { + try this.play_state.toCss(W, dest); + try dest.writeChar(' '); + } + }, + } + + try this.name.toCss(W, dest); + + if (!this.name.eql(&AnimationName.none) and !this.timeline.eql(&AnimationTimeline.default())) { + try dest.writeChar(' '); + try this.timeline.toCss(W, dest); + } + } +}; /// A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property. pub const AnimationName = union(enum) { @@ -94,19 +287,80 @@ pub const AnimationIterationCount = union(enum) { number: CSSNumber, /// The animation will repeat forever. infinite, + + pub usingnamespace css.DeriveParse(@This()); + pub usingnamespace css.DeriveToCss(@This()); + + pub fn default() AnimationIterationCount { + return .{ .number = 1.0 }; + } + + pub fn eql(lhs: *const @This(), rhs: *const @This()) bool { + return css.implementEql(@This(), lhs, rhs); + } }; /// A value for the [animation-direction](https://drafts.csswg.org/css-animations/#animation-direction) property. -pub const AnimationDirection = css.DefineEnumProperty(@compileError(css.todo_stuff.depth)); +pub const AnimationDirection = enum { + /// The animation is played as specified + normal, + /// The animation is played in reverse. + reverse, + /// The animation iterations alternate between forward and reverse. + alternate, + /// The animation iterations alternate between forward and reverse, with reverse occurring first. + @"alternate-reverse", + + pub usingnamespace css.DefineEnumProperty(@This()); + + pub fn default() AnimationDirection { + return .normal; + } +}; /// A value for the [animation-play-state](https://drafts.csswg.org/css-animations/#animation-play-state) property. -pub const AnimationPlayState = css.DefineEnumProperty(@compileError(css.todo_stuff.depth)); +pub const AnimationPlayState = enum { + /// The animation is playing. + running, + /// The animation is paused. + paused, + + pub usingnamespace css.DefineEnumProperty(@This()); + + pub fn default() AnimationPlayState { + return .running; + } +}; /// A value for the [animation-fill-mode](https://drafts.csswg.org/css-animations/#animation-fill-mode) property. -pub const AnimationFillMode = css.DefineEnumProperty(@compileError(css.todo_stuff.depth)); +pub const AnimationFillMode = enum { + /// The animation has no effect while not playing. + none, + /// After the animation, the ending values are applied. + forwards, + /// Before the animation, the starting values are applied. + backwards, + /// Both forwards and backwards apply. + both, + + pub usingnamespace css.DefineEnumProperty(@This()); + + pub fn default() AnimationFillMode { + return .none; + } +}; /// A value for the [animation-composition](https://drafts.csswg.org/css-animations-2/#animation-composition) property. -pub const AnimationComposition = css.DefineEnumProperty(@compileError(css.todo_stuff.depth)); +pub const AnimationComposition = enum { + /// The result of compositing the effect value with the underlying value is simply the effect value. + replace, + /// The effect value is added to the underlying value. + add, + /// The effect value is accumulated onto the underlying value. + accumulate, + + pub usingnamespace css.DefineEnumProperty(@This()); +}; /// A value for the [animation-timeline](https://drafts.csswg.org/css-animations-2/#animation-timeline) property. pub const AnimationTimeline = union(enum) { @@ -120,6 +374,17 @@ pub const AnimationTimeline = union(enum) { scroll: ScrollTimeline, /// The view() function. view: ViewTimeline, + + pub usingnamespace css.DeriveParse(@This()); + pub usingnamespace css.DeriveToCss(@This()); + + pub fn eql(lhs: *const @This(), rhs: *const @This()) bool { + return css.implementEql(@This(), lhs, rhs); + } + + pub fn default() AnimationTimeline { + return .auto; + } }; /// The [scroll()](https://drafts.csswg.org/scroll-animations-1/#scroll-notation) function. @@ -139,10 +404,38 @@ pub const ViewTimeline = struct { }; /// A scroller, used in the `scroll()` function. -pub const Scroller = @compileError(css.todo_stuff.depth); +pub const Scroller = enum { + /// Specifies to use the document viewport as the scroll container. + root, + /// Specifies to use the nearest ancestor scroll container. + nearest, + /// Specifies to use the element's own principal box as the scroll container. + self, + + pub usingnamespace css.DefineEnumProperty(@This()); + + pub fn default() Scroller { + return .nearest; + } +}; /// A scroll axis, used in the `scroll()` function. -pub const ScrollAxis = @compileError(css.todo_stuff.depth); +pub const ScrollAxis = enum { + /// Specifies to use the measure of progress along the block axis of the scroll container. + block, + /// Specifies to use the measure of progress along the inline axis of the scroll container. + @"inline", + /// Specifies to use the measure of progress along the horizontal axis of the scroll container. + x, + /// Specifies to use the measure of progress along the vertical axis of the scroll container. + y, + + pub usingnamespace css.DefineEnumProperty(@This()); + + pub fn default() ScrollAxis { + return .block; + } +}; /// A value for the animation-range shorthand property. pub const AnimationRange = struct { diff --git a/src/css/properties/border.zig b/src/css/properties/border.zig index 26120ab545..091fb3ef03 100644 --- a/src/css/properties/border.zig +++ b/src/css/properties/border.zig @@ -158,7 +158,7 @@ pub fn GenericBorder(comptime S: type, comptime P: u8) type { } pub fn eql(this: *const This, other: *const This) bool { - return this.width.eql(&other.width) and this.style.eql(&other.style) and this.color.eql(&other.color); + return css.implementEql(@This(), this, other); } pub inline fn default() This { @@ -237,24 +237,7 @@ pub const BorderSideWidth = union(enum) { pub fn deinit(_: *@This(), _: std.mem.Allocator) void {} pub fn eql(this: *const @This(), other: *const @This()) bool { - return switch (this.*) { - .thin => switch (other.*) { - .thin => true, - else => false, - }, - .medium => switch (other.*) { - .medium => true, - else => false, - }, - .thick => switch (other.*) { - .thick => true, - else => false, - }, - .length => switch (other.*) { - .length => this.length.eql(&other.length), - else => false, - }, - }; + return css.implementEql(@This(), this, other); } }; @@ -1016,30 +999,30 @@ pub const BorderHandler = struct { comptime block_start_width: []const u8, comptime block_start_style: []const u8, comptime block_start_color: []const u8, - block_start: anytype, + block_start: *BorderShorthand, comptime block_end_prop: []const u8, comptime block_end_width: []const u8, comptime block_end_style: []const u8, comptime block_end_color: []const u8, - block_end: anytype, + block_end: *BorderShorthand, comptime inline_start_prop: []const u8, comptime inline_start_width: []const u8, comptime inline_start_style: []const u8, comptime inline_start_color: []const u8, - inline_start: anytype, + inline_start: *BorderShorthand, comptime inline_end_prop: []const u8, comptime inline_end_width: []const u8, comptime inline_end_style: []const u8, comptime inline_end_color: []const u8, - inline_end: anytype, + inline_end: *BorderShorthand, comptime is_logical: bool, ) void { const State = struct { f: *FlushContext, - block_start: @TypeOf(block_start), - block_end: @TypeOf(block_end), - inline_start: @TypeOf(inline_start), - inline_end: @TypeOf(inline_end), + block_start: *BorderShorthand, + block_end: *BorderShorthand, + inline_start: *BorderShorthand, + inline_end: *BorderShorthand, inline fn shorthand(s: *@This(), comptime p: type, comptime prop_name: []const u8, comptime key: []const u8) void { const has_prop = @field(s.block_start, key) != null and @field(s.block_end, key) != null and @field(s.inline_start, key) != null and @field(s.inline_end, key) != null; diff --git a/src/css/properties/border_image.zig b/src/css/properties/border_image.zig index 5054509d40..6d21e1e25f 100644 --- a/src/css/properties/border_image.zig +++ b/src/css/properties/border_image.zig @@ -188,9 +188,7 @@ pub const BorderImage = struct { var res = css.SmallList(BorderImage, 6).initCapacity(allocator, fallbacks.len()); res.setLen(fallbacks.len()); for (fallbacks.slice(), res.slice_mut()) |fallback, *out| { - out.* = this.*; - out.source = Image.default(); - out.* = out.deepClone(allocator); + out.* = this.deepClone(allocator); out.source = fallback; } diff --git a/src/css/properties/flex.zig b/src/css/properties/flex.zig index 5f52f82f20..57412f121b 100644 --- a/src/css/properties/flex.zig +++ b/src/css/properties/flex.zig @@ -303,6 +303,8 @@ pub const BoxDirection = enum { pub usingnamespace css.DefineEnumProperty(@This()); }; +pub const FlexAlign = BoxAlign; + /// A value for the legacy (prefixed) [box-align](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#alignment) property. /// Equivalent to the `align-items` property in the standard syntax. /// A value for the legacy (prefixed) [box-align](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#alignment) property. @@ -320,6 +322,19 @@ pub const BoxAlign = enum { stretch, pub usingnamespace css.DefineEnumProperty(@This()); + + pub fn fromStandard(@"align": *const css.css_properties.@"align".AlignItems) ?BoxAlign { + return switch (@"align".*) { + .self_position => |sp| if (sp.overflow == null) switch (sp.value) { + .start, .@"flex-start" => .start, + .end, .@"flex-end" => .end, + .center => .center, + else => null, + } else null, + .stretch => .stretch, + else => null, + }; + } }; /// A value for the legacy (prefixed) [box-pack](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#packing) property. @@ -337,6 +352,21 @@ pub const BoxPack = enum { justify, pub usingnamespace css.DefineEnumProperty(@This()); + + pub fn fromStandard(justify: *const css.css_properties.@"align".JustifyContent) ?BoxPack { + return switch (justify.*) { + .content_distribution => |cd| switch (cd) { + .@"space-between" => .justify, + else => null, + }, + .content_position => |cp| if (cp.overflow == null) switch (cp.value) { + .start, .@"flex-start" => .start, + .end, .@"flex-end" => .end, + .center => .center, + } else null, + else => null, + }; + } }; /// A value for the legacy (prefixed) [box-lines](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#multiple) property. @@ -378,6 +408,22 @@ pub const FlexPack = enum { distribute, pub usingnamespace css.DefineEnumProperty(@This()); + + pub fn fromStandard(justify: *const css.css_properties.@"align".JustifyContent) ?FlexPack { + return switch (justify.*) { + .content_distribution => |cd| switch (cd) { + .@"space-between" => .justify, + .@"space-around" => .distribute, + else => null, + }, + .content_position => |cp| if (cp.overflow == null) switch (cp.value) { + .start, .@"flex-start" => .start, + .end, .@"flex-end" => .end, + .center => .center, + } else null, + else => null, + }; + } }; /// A value for the legacy (prefixed) [flex-item-align](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-align) property. @@ -399,6 +445,20 @@ pub const FlexItemAlign = enum { stretch, pub usingnamespace css.DefineEnumProperty(@This()); + + pub fn fromStandard(justify: *const css.css_properties.@"align".AlignSelf) ?FlexItemAlign { + return switch (justify.*) { + .auto => .auto, + .stretch => .stretch, + .self_position => |sp| if (sp.overflow == null) switch (sp.value) { + .start, .@"flex-start" => .start, + .end, .@"flex-end" => .end, + .center => .center, + else => null, + } else null, + else => null, + }; + } }; /// A value for the legacy (prefixed) [flex-line-pack](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-line-pack) property. @@ -420,6 +480,23 @@ pub const FlexLinePack = enum { stretch, pub usingnamespace css.DefineEnumProperty(@This()); + + pub fn fromStandard(justify: *const css.css_properties.@"align".AlignContent) ?FlexLinePack { + return switch (justify.*) { + .content_distribution => |cd| switch (cd) { + .@"space-between" => .justify, + .@"space-around" => .distribute, + .stretch => .stretch, + else => null, + }, + .content_position => |cp| if (cp.overflow == null) switch (cp.value) { + .start, .@"flex-start" => .start, + .end, .@"flex-end" => .end, + .center => .center, + } else null, + else => null, + }; + } }; pub const BoxOrdinalGroup = CSSInteger; diff --git a/src/css/properties/generate_properties.ts b/src/css/properties/generate_properties.ts index f34bd3060d..e78ec97844 100644 --- a/src/css/properties/generate_properties.ts +++ b/src/css/properties/generate_properties.ts @@ -437,6 +437,21 @@ function generatePropertyIdImpl(property_defs: Record): str const tag = @intFromEnum(this.*); hasher.update(std.mem.asBytes(&tag)); } + + pub fn setPrefixesForTargets(this: *PropertyId, targets: Targets) void { + switch (this.*) { + ${Object.entries(property_defs) + .map(([name, meta]) => { + if (meta.unprefixed === false || meta.valid_prefixes === undefined) return `.${escapeIdent(name)} => {},`; + return `.${escapeIdent(name)} => |*x| { + x.* = targets.prefixes(x.*, Feature.${featureName(name)}); + }, + `; + }) + .join("\n")} + else => {}, + } + } `; } @@ -1274,27 +1289,27 @@ generateCode({ // "font-palette": { // ty: "DashedIdentReference", // }, - // "transition-property": { - // ty: "SmallList(PropertyId, 1)", - // valid_prefixes: ["webkit", "moz", "ms"], - // }, - // "transition-duration": { - // ty: "SmallList(Time, 1)", - // valid_prefixes: ["webkit", "moz", "ms"], - // }, - // "transition-delay": { - // ty: "SmallList(Time, 1)", - // valid_prefixes: ["webkit", "moz", "ms"], - // }, - // "transition-timing-function": { - // ty: "SmallList(EasingFunction, 1)", - // valid_prefixes: ["webkit", "moz", "ms"], - // }, - // transition: { - // ty: "SmallList(Transition, 1)", - // valid_prefixes: ["webkit", "moz", "ms"], - // shorthand: true, - // }, + "transition-property": { + ty: "SmallList(PropertyId, 1)", + valid_prefixes: ["webkit", "moz", "ms"], + }, + "transition-duration": { + ty: "SmallList(Time, 1)", + valid_prefixes: ["webkit", "moz", "ms"], + }, + "transition-delay": { + ty: "SmallList(Time, 1)", + valid_prefixes: ["webkit", "moz", "ms"], + }, + "transition-timing-function": { + ty: "SmallList(EasingFunction, 1)", + valid_prefixes: ["webkit", "moz", "ms"], + }, + transition: { + ty: "SmallList(Transition, 1)", + valid_prefixes: ["webkit", "moz", "ms"], + shorthand: true, + }, // "animation-name": { // ty: "AnimationNameList", // valid_prefixes: ["webkit", "moz", "o"], @@ -1347,42 +1362,42 @@ generateCode({ // valid_prefixes: ["webkit", "moz", "o"], // shorthand: true, // }, - // transform: { - // ty: "TransformList", - // valid_prefixes: ["webkit", "moz", "ms", "o"], - // }, - // "transform-origin": { - // ty: "Position", - // valid_prefixes: ["webkit", "moz", "ms", "o"], - // }, - // "transform-style": { - // ty: "TransformStyle", - // valid_prefixes: ["webkit", "moz"], - // }, - // "transform-box": { - // ty: "TransformBox", - // }, - // "backface-visibility": { - // ty: "BackfaceVisibility", - // valid_prefixes: ["webkit", "moz"], - // }, - // perspective: { - // ty: "Perspective", - // valid_prefixes: ["webkit", "moz"], - // }, - // "perspective-origin": { - // ty: "Position", - // valid_prefixes: ["webkit", "moz"], - // }, - // translate: { - // ty: "Translate", - // }, - // rotate: { - // ty: "Rotate", - // }, - // scale: { - // ty: "Scale", - // }, + transform: { + ty: "TransformList", + valid_prefixes: ["webkit", "moz", "ms", "o"], + }, + "transform-origin": { + ty: "Position", + valid_prefixes: ["webkit", "moz", "ms", "o"], + }, + "transform-style": { + ty: "TransformStyle", + valid_prefixes: ["webkit", "moz"], + }, + "transform-box": { + ty: "TransformBox", + }, + "backface-visibility": { + ty: "BackfaceVisibility", + valid_prefixes: ["webkit", "moz"], + }, + perspective: { + ty: "Perspective", + valid_prefixes: ["webkit", "moz"], + }, + "perspective-origin": { + ty: "Position", + valid_prefixes: ["webkit", "moz"], + }, + translate: { + ty: "Translate", + }, + rotate: { + ty: "Rotate", + }, + scale: { + ty: "Scale", + }, // "text-transform": { // ty: "TextTransform", // }, @@ -1674,6 +1689,7 @@ generateCode({ ty: "MaskBorder", shorthand: true, }, + // WebKit additions "-webkit-mask-composite": { ty: "SmallList(WebKitMaskComposite, 1)", }, @@ -1712,6 +1728,7 @@ generateCode({ valid_prefixes: ["webkit"], unprefixed: false, }, + // TODO: Hello future Zack, if you uncomment this, remember to uncomment the corresponding value in FallbackHandler in prefix_handler.zig :) // filter: { // ty: "FilterList", @@ -1761,6 +1778,17 @@ const PropertyIdImpl = @import("./properties_impl.zig").PropertyIdImpl; const CSSWideKeyword = css.css_properties.CSSWideKeyword; const UnparsedProperty = css.css_properties.custom.UnparsedProperty; const CustomProperty = css.css_properties.custom.CustomProperty; +const Targets = css.targets.Targets; +const Feature = css.prefixes.Feature; + +const TransformList = css.css_properties.transform.TransformList; +const TransformStyle = css.css_properties.transform.TransformStyle; +const TransformBox = css.css_properties.transform.TransformBox; +const BackfaceVisibility = css.css_properties.transform.BackfaceVisibility; +const Perspective = css.css_properties.transform.Perspective; +const Translate = css.css_properties.transform.Translate; +const Rotate = css.css_properties.transform.Rotate; +const Scale = css.css_properties.transform.Scale; const css_values = css.css_values; const CssColor = css.css_values.color.CssColor; @@ -1893,7 +1921,7 @@ const FontVariantCaps = font.FontVariantCaps; const LineHeight = font.LineHeight; const Font = font.Font; // const VerticalAlign = font.VerticalAlign; -// const Transition = transition.Transition; +const Transition = transition.Transition; // const AnimationNameList = animation.AnimationNameList; // const AnimationList = animation.AnimationList; // const AnimationIterationCount = animation.AnimationIterationCount; @@ -1994,3 +2022,7 @@ const SmallList = css.SmallList; `; } + +function featureName(name: string): string { + return name.replaceAll("-", "_"); +} diff --git a/src/css/properties/grid.zig b/src/css/properties/grid.zig new file mode 100644 index 0000000000..a4b2d60773 --- /dev/null +++ b/src/css/properties/grid.zig @@ -0,0 +1,561 @@ +const std = @import("std"); +const bun = @import("root").bun; +const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayListUnmanaged; + +pub const css = @import("../css_parser.zig"); + +const SmallList = css.SmallList; +const Printer = css.Printer; +const PrintErr = css.PrintErr; +const Error = css.Error; + +const Property = css.Property; +const PropertyId = css.PropertyId; + +const ContainerName = css.css_rules.container.ContainerName; + +const CSSNumberFns = css.css_values.number.CSSNumberFns; +const LengthPercentage = css.css_values.length.LengthPercentage; +const CustomIdent = css.css_values.ident.CustomIdent; +const CSSString = css.css_values.string.CSSString; +const CSSNumber = css.css_values.number.CSSNumber; +const LengthPercentageOrAuto = css.css_values.length.LengthPercentageOrAuto; +const Size2D = css.css_values.size.Size2D; +const DashedIdent = css.css_values.ident.DashedIdent; +const Image = css.css_values.image.Image; +const CssColor = css.css_values.color.CssColor; +const Ratio = css.css_values.ratio.Ratio; +const Length = css.css_values.length.LengthValue; +const Rect = css.css_values.rect.Rect; +const NumberOrPercentage = css.css_values.percentage.NumberOrPercentage; +const CustomIdentList = css.css_values.ident.CustomIdentList; +const Angle = css.css_values.angle.Angle; +const Url = css.css_values.url.Url; +const CSSInteger = css.css_values.number.CSSInteger; +const BabyList = bun.BabyList; + +const isFlex2009 = css.prefixes.Feature.isFlex2009; + +const VendorPrefix = css.VendorPrefix; + +/// A [track sizing](https://drafts.csswg.org/css-grid-2/#track-sizing) value +/// for the `grid-template-rows` and `grid-template-columns` properties. +pub const TrackSizing = union(enum) { + /// No explicit grid tracks. + none, + /// A list of grid tracks. + tracklist: TrackList, + + pub usingnamespace css.DeriveParse(@This()); + pub usingnamespace css.DeriveToCss(@This()); +}; + +/// A [``](https://drafts.csswg.org/css-grid-2/#typedef-track-list) value, +/// as used in the `grid-template-rows` and `grid-template-columns` properties. +/// +/// See [TrackSizing](TrackSizing). +pub const TrackList = struct { + /// A list of line names. + line_names: bun.BabyList(CustomIdentList), + /// A list of grid track items. + items: bun.BabyList(TrackListItem), + + pub fn parse(input: *css.Parser) css.Result(@This()) { + var line_names = BabyList(CustomIdentList){}; + var items = BabyList(TrackListItem){}; + + while (true) { + const line_name = input.tryParse(parseLineNames, .{}).asValue() orelse CustomIdentList{}; + line_names.append(input.allocator(), line_name) catch bun.outOfMemory(); + + if (input.tryParse(TrackSize.parse, .{}).asValue()) |track_size| { + // TODO: error handling + items.append(.{ .track_size = track_size }) catch bun.outOfMemory(); + } else if (input.tryParse(TrackRepeat.parse, .{}).asValue()) |repeat| { + // TODO: error handling + items.append(.{ .track_repeat = repeat }) catch bun.outOfMemory(); + } else { + break; + } + } + + if (items.len == 0) { + return .{ .err = input.newCustomError(.invalid_declaration) }; + } + + return .{ .result = .{ + .line_names = line_names, + .items = items, + } }; + } + + pub fn toCss(this: *const @This(), comptime W: type, dest: *css.Printer(W)) css.PrintErr!void { + var items_index = 0; + var first = true; + + for (this.line_names.sliceConst()) |*names| { + if (!names.isEmpty()) try serializeLineNames(names, W, dest); + + if (items_index < this.items.len) { + const item = this.items.at(items_index); + items_index += 1; + + // Whitespace is required if there are no line names. + if (!names.isEmpty()) { + try dest.whitespace(); + } else if (!first) { + try dest.writeChar(' '); + } + + switch (item.*) { + .track_repeat => |*repeat| try repeat.toCss(W, dest), + .track_size => |*size| try size.toCss(W, dest), + } + } + + first = false; + } + } +}; + +/// Either a track size or `repeat()` function. +/// +/// See [TrackList](TrackList). +pub const TrackListItem = union(enum) { + /// A track size. + track_size: TrackSize, + /// A `repeat()` function. + track_repeat: TrackRepeat, +}; + +/// A [track size](https://drafts.csswg.org/css-grid-2/#typedef-track-size) value. +/// +/// See [TrackList](TrackList). +pub const TrackSize = union(enum) { + /// An explicit track breadth. + track_breadth: TrackBreadth, + /// The `minmax()` function. + min_max: struct { + /// The minimum value. + min: TrackBreadth, + /// The maximum value. + max: TrackBreadth, + }, + /// The `fit-content()` function. + fit_content: LengthPercentage, + + pub fn default() @This() { + return .{ .track_breadth = TrackBreadth.auto }; + } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } + + pub fn parse(input: *css.Parser) css.Result(@This()) { + if (input.tryParse(TrackBreadth.parse, .{}).asValue()) |breadth| { + return .{ .result = .{ .track_breadth = breadth } }; + } + + if (input.tryParse(css.Parser.expectFunctionMatching, .{"minmax"}).isOk()) { + return input.parseNestedBlock(struct { + pub fn parse(i: *css.Parser) css.Result(TrackSize) { + const min = switch (TrackBreadth.parseInternal(i, false)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (i.expectComma().asErr()) |e| return .{ .err = e }; + return .{ + .result = .{ .min_max = .{ .min = min, .max = switch (TrackBreadth.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + } } }, + }; + } + }.parseFn); + } + + if (input.expectFunctionMatching("fit-content").asErr()) |e| return .{ .err = e }; + + const len = switch (input.parseNestedBlock(css.voidWrap(LengthPercentage, LengthPercentage.parse))) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + + return .{ .result = .{ .fit_content = len } }; + } + + pub fn toCss(this: *const @This(), comptime W: type, dest: *css.Printer(W)) css.PrintErr!void { + switch (this.*) { + .track_breadth => |breadth| try breadth.toCss(W, dest), + .min_max => |mm| { + try dest.writeStr("minmax("); + try mm.min.toCss(W, dest); + try dest.delim(',', false); + try mm.max.toCss(W, dest); + try dest.writeChar(')'); + }, + .fit_content => |len| { + try dest.writeStr("fit-content("); + try len.toCss(W, dest); + try dest.writeChar(')'); + }, + } + } +}; + +pub const TrackSizeList = struct { + v: SmallList(TrackSize, 1) = .{}, + + pub fn parse(input: *css.Parser) css.Result(@This()) { + var res = SmallList(TrackSize, 1){}; + while (input.tryParse(TrackSize.parse, .{}).asValue()) |size| { + res.append(input.allocator(), size) catch bun.outOfMemory(); + } + + if (res.len() == 1 and res.at(0).eql(&TrackSize.default())) { + res.clearRetainingCapacity(); + } + + return .{ .result = .{ .v = res } }; + } + + pub fn toCss(this: *const @This(), comptime W: type, dest: *css.Printer(W)) css.PrintErr!void { + if (this.v.len() == 0) { + try dest.writeStr("auto"); + return; + } + + var first = true; + for (this.v.slice()) |item| { + if (first) { + first = false; + } else { + try dest.writeChar(' '); + } + try item.toCss(W, dest); + } + } +}; + +/// A [track breadth](https://drafts.csswg.org/css-grid-2/#typedef-track-breadth) value. +/// +/// See [TrackSize](TrackSize). +pub const TrackBreadth = union(enum) { + /// An explicit length. + length: LengthPercentage, + /// A flex factor. + flex: CSSNumber, + /// The `min-content` keyword. + min_content, + /// The `max-content` keyword. + max_content, + /// The `auto` keyword. + auto, + + pub fn parse(input: *css.Parser) css.Result(@This()) { + return TrackBreadth.parseInternal(input, true); + } + + fn parseInternal(input: *css.Parser, allow_flex: bool) css.Result(@This()) { + if (input.tryParse(LengthPercentage.parse, .{}).asValue()) |len| { + return .{ .result = .{ .length = len } }; + } + + if (allow_flex) { + if (input.tryParse(TrackBreadth.parseFlex, .{}).asValue()) |flex| { + return .{ .result = .{ .flex = flex } }; + } + } + + const location = input.currentSourceLocation(); + const ident = switch (input.expectIdent()) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + + if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "auto")) { + return .{ .result = .auto }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "min-content")) { + return .{ .result = .min_content }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "max-content")) { + return .{ .result = .max_content }; + } + + return .{ .err = location.newUnexpectedTokenError(.{ .ident = ident }) }; + } + + fn parseFlex(input: *css.Parser) css.Result(CSSNumber) { + const location = input.currentSourceLocation(); + const token = switch (input.next()) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + + if (token == .dimension) { + if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(token.dimension.unit, "fr") and token.dimension.value >= 0.0) { + return .{ .result = token.dimension.value }; + } + } + + return .{ .err = location.newUnexpectedTokenError(token) }; + } + + pub fn toCss(this: *const @This(), comptime W: type, dest: *css.Printer(W)) css.PrintErr!void { + switch (this.*) { + .auto => try dest.writeStr("auto"), + .min_content => try dest.writeStr("min-content"), + .max_content => try dest.writeStr("max-content"), + .length => |len| try len.toCss(W, dest), + // .flex => |flex| try css.CSSNumberFns.serializeDimension(&flex, "fr", W, dest), + .flex => |flex| css.serializer.serializeDimension(flex, "fr", W, dest), + } + } +}; + +/// A `repeat()` function. +/// +/// See [TrackList](TrackList). +pub const TrackRepeat = struct { + /// The repeat count. + count: RepeatCount, + /// The line names to repeat. + line_names: bun.BabyList(CustomIdentList), + /// The track sizes to repeat. + track_sizes: bun.BabyList(TrackSize), + + pub fn parse(input: *css.Parser) css.Result(@This()) { + if (input.expectFunctionMatching("repeat").asErr()) |e| return .{ .err = e }; + + return input.parseNestedBlock(struct { + fn parse(i: *css.Parser) css.Result(TrackRepeat) { + const count = switch (@call(.auto, @field(RepeatCount, "parse"), .{i})) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + + if (i.expectComma().asErr()) |e| return .{ .err = e }; + + var line_names = bun.BabyList(CustomIdentList).init(i.allocator); + var track_sizes = bun.BabyList(TrackSize).init(i.allocator); + + while (true) { + const line_name = i.tryParse(parseLineNames, .{}).unwrapOr(CustomIdentList{}); + line_names.append(i.allocator(), line_name) catch bun.outOfMemory(); + + if (input.tryParse(TrackSize.parse, .{}).asValue()) |track_size| { + // TODO: error handling + track_sizes.append(i.allocator(), track_size) catch bun.outOfMemory(); + } else { + break; + } + } + + return .{ .result = .{ + .count = count, + .line_names = line_names, + .track_sizes = track_sizes, + } }; + } + }.parse); + } + + pub fn toCss(this: *const @This(), comptime W: type, dest: *Printer(W)) PrintErr!void { + try dest.writeStr("repeat("); + try this.count.toCss(W, dest); + try dest.delim(',', false); + + var track_sizes_index = 0; + var first = true; + for (this.line_names.sliceConst()) |*names| { + if (!names.isEmpty()) { + try serializeLineNames(names, W, dest); + } + + if (track_sizes_index < this.track_sizes.len) { + const size = this.track_sizes.at(track_sizes_index); + track_sizes_index += 1; + + if (!names.isEmpty()) { + try dest.whitespace(); + } else if (!first) { + try dest.writeChar(' '); + } + try size.toCss(W, dest); + } + + first = false; + } + + try dest.writeChar(')'); + } +}; + +fn serializeLineNames(names: []const CustomIdent, comptime W: type, dest: *Printer(W)) PrintErr!void { + try dest.writeChar('['); + var first = true; + for (names) |*name| { + if (first) { + first = false; + } else { + try dest.writeChar(' '); + } + try writeIdent(&name.value, W, dest); + } + try dest.writeChar(']'); +} + +fn writeIdent(name: []const u8, comptime W: type, dest: *Printer(W)) PrintErr!void { + const css_module_grid_enabled = if (dest.css_module) |*css_module| css_module.config.grid else false; + if (css_module_grid_enabled) { + if (dest.css_module) |*css_module| { + if (css_module.config.pattern.segments.last()) |last| { + if (last != css.css_modules.Segment.local) { + return try dest.addInvalidCssModulesPatternInGridError(); + } + } + } + } + + try dest.writeIdent(name, css_module_grid_enabled); +} + +fn parseLineNames(input: *css.Parser) css.Result(CustomIdentList) { + if (input.expectSquareBracketBlock().asErr()) |e| return .{ .err = e }; + + return input.parseNestedBlock(struct { + fn parse(i: *css.Parser) css.Result(CustomIdentList) { + var values = CustomIdentList{}; + + while (input.tryParse(CustomIdent.parse, .{}).asValue()) |ident| { + values.append(i.allocator(), ident) catch bun.outOfMemory(); + } + + return .{ .result = values }; + } + }.parse); +} + +/// A [``](https://drafts.csswg.org/css-grid-2/#typedef-track-repeat) value, +/// used in the `repeat()` function. +/// +/// See [TrackRepeat](TrackRepeat). +pub const RepeatCount = union(enum) { + /// The number of times to repeat. + number: CSSInteger, + /// The `auto-fill` keyword. + @"auto-fill", + /// The `auto-fit` keyword. + @"auto-fit", + + pub usingnamespace css.DeriveParse(@This()); + pub usingnamespace css.DeriveToCss(@This()); + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } +}; + +/// A grid template areas value. +/// See https://drafts.csswg.org/css-grid-2/#propdef-grid-template-areas +pub const GridTemplateAreas = union(enum) { + /// No named grid areas. + none, + /// Defines the list of named grid areas. + areas: struct { + /// The number of columns in the grid. + columns: u32, + /// A flattened list of grid area names. + /// Unnamed areas specified by the `.` token are represented as null. + areas: SmallList(?[]const u8, 1), + }, + + pub fn parse(input: *css.Parser) css.Result(@This()) { + if (input.tryParse(struct { + fn parse(i: *css.Parser) css.Result(void) { + return i.expectIdentMatching("none"); + } + }.parse, .{}).asValue()) |_| { + return .{ .result = .none }; + } + + var tokens = SmallList(?[]const u8, 1){}; + var row: u32 = 0; + var columns: u32 = 0; + + if (input.tryParse(css.Parser.expectString, .{}).asValue()) |s| { + const parsed_columns = switch (parseString(input.allocator(), s, &tokens)) { + .result => |v| v, + .err => return .{input.newError(.qualified_rule_invalid)}, + }; + + if (row == 0) { + columns = parsed_columns; + } else if (parsed_columns != columns) return .{ .err = input.newCustomError(.invalid_declaration) }; + + row += 1; + } + + return .{ .result = .{ .areas = .{ + .columns = columns, + .areas = tokens, + } } }; + } + + const HTML_SPACE_CHARACTERS: []const u8 = &.{ 0x0020, 0x0009, 0x000a, 0x000c, 0x000d }; + + fn parseString(allocator: Allocator, s: []const u8, tokens: *SmallList(?[]const u8, 1)) bun.Maybe(u32, void) { + var string = s; + var column = 0; + + while (true) { + const rest = bun.strings.trim(string, HTML_SPACE_CHARACTERS); + if (rest.len == 0) { + // Each string must produce a valid token. + if (column == 0) return .{ .err = {} }; + break; + } + + column += 1; + + if (bun.strings.startsWithChar(rest, '.')) { + const idx = idx: { + for (rest, 0..) |*c, i| { + if (c.* != '.') { + break :idx i; + } + } + break :idx rest.len; + }; + string = rest[idx..]; + } + + const starts_with_name_codepoint = brk: { + if (rest.len == 0) break :brk false; + break :brk isNameCodepoint(rest[0]); + }; + + if (!starts_with_name_codepoint) return .{ .err = {} }; + + const token_len = token_len: { + for (rest, 0..) |*c, i| { + if (!isNameCodepoint(c.*)) { + break :token_len i; + } + } + break :token_len rest.len; + }; + const token = rest[0..token_len]; + tokens.append(allocator, token) catch bun.outOfMemory(); + string = rest[token_len..]; + } + + return .{ .result = column }; + } +}; + +fn isNameCodepoint(c: u8) bool { + // alpha numeric, -, _, o + return c >= 'a' and c <= 'z' or c >= 'A' and c <= 'Z' or c == '_' or c >= '0' and c <= '9' or c == '-' or c >= 0x80; // codepoints larger than ascii; +} diff --git a/src/css/properties/masking.zig b/src/css/properties/masking.zig index e4d1573ef8..cf192d97e7 100644 --- a/src/css/properties/masking.zig +++ b/src/css/properties/masking.zig @@ -41,6 +41,8 @@ const BorderImageSideWidth = css.css_properties.border_image.BorderImageSideWidt const BorderImageRepeat = css.css_properties.border_image.BorderImageRepeat; const BorderImage = css.css_properties.border_image.BorderImage; +const VendorPrefix = css.VendorPrefix; + /// A value for the [clip-path](https://www.w3.org/TR/css-masking-1/#the-clip-path) property. const ClipPath = union(enum) { /// No clip path. @@ -539,3 +541,17 @@ pub const WebKitMaskSourceType = enum { pub usingnamespace css.DefineEnumProperty(@This()); }; + +pub fn getWebkitMaskProperty(property_id: *const css.PropertyId) ?css.PropertyId { + return switch (property_id.*) { + .@"mask-border-source" => .{ .@"mask-box-image-source" = VendorPrefix.WEBKIT }, + .@"mask-border-slice" => .{ .@"mask-box-image-slice" = VendorPrefix.WEBKIT }, + .@"mask-border-width" => .{ .@"mask-box-image-width" = VendorPrefix.WEBKIT }, + .@"mask-border-outset" => .{ .@"mask-box-image-outset" = VendorPrefix.WEBKIT }, + .@"mask-border-repeat" => .{ .@"mask-box-image-repeat" = VendorPrefix.WEBKIT }, + .@"mask-border" => .{ .@"mask-box-image" = VendorPrefix.WEBKIT }, + .@"mask-composite" => css.PropertyId.@"-webkit-mask-composite", + .@"mask-mode" => .{ .@"mask-source-type" = VendorPrefix.WEBKIT }, + else => null, + }; +} diff --git a/src/css/properties/properties.zig b/src/css/properties/properties.zig index 4ec12ffa41..de4c86c79d 100644 --- a/src/css/properties/properties.zig +++ b/src/css/properties/properties.zig @@ -25,6 +25,7 @@ pub const display = @import("./display.zig"); pub const effects = @import("./effects.zig"); pub const flex = @import("./flex.zig"); pub const font = @import("./font.zig"); +pub const grid = @import("./grid.zig"); pub const list = @import("./list.zig"); pub const margin_padding = @import("./margin_padding.zig"); pub const masking = @import("./masking.zig"); diff --git a/src/css/properties/properties_generated.zig b/src/css/properties/properties_generated.zig index 72ea851826..676fe2b963 100644 --- a/src/css/properties/properties_generated.zig +++ b/src/css/properties/properties_generated.zig @@ -14,6 +14,17 @@ const PropertyIdImpl = @import("./properties_impl.zig").PropertyIdImpl; const CSSWideKeyword = css.css_properties.CSSWideKeyword; const UnparsedProperty = css.css_properties.custom.UnparsedProperty; const CustomProperty = css.css_properties.custom.CustomProperty; +const Targets = css.targets.Targets; +const Feature = css.prefixes.Feature; + +const TransformList = css.css_properties.transform.TransformList; +const TransformStyle = css.css_properties.transform.TransformStyle; +const TransformBox = css.css_properties.transform.TransformBox; +const BackfaceVisibility = css.css_properties.transform.BackfaceVisibility; +const Perspective = css.css_properties.transform.Perspective; +const Translate = css.css_properties.transform.Translate; +const Rotate = css.css_properties.transform.Rotate; +const Scale = css.css_properties.transform.Scale; const css_values = css.css_values; const CssColor = css.css_values.color.CssColor; @@ -146,7 +157,7 @@ const FontVariantCaps = font.FontVariantCaps; const LineHeight = font.LineHeight; const Font = font.Font; // const VerticalAlign = font.VerticalAlign; -// const Transition = transition.Transition; +const Transition = transition.Transition; // const AnimationNameList = animation.AnimationNameList; // const AnimationList = animation.AnimationList; // const AnimationIterationCount = animation.AnimationIterationCount; @@ -443,6 +454,21 @@ pub const Property = union(PropertyIdTag) { @"font-variant-caps": FontVariantCaps, @"line-height": LineHeight, font: Font, + @"transition-property": struct { SmallList(PropertyId, 1), VendorPrefix }, + @"transition-duration": struct { SmallList(Time, 1), VendorPrefix }, + @"transition-delay": struct { SmallList(Time, 1), VendorPrefix }, + @"transition-timing-function": struct { SmallList(EasingFunction, 1), VendorPrefix }, + transition: struct { SmallList(Transition, 1), VendorPrefix }, + transform: struct { TransformList, VendorPrefix }, + @"transform-origin": struct { Position, VendorPrefix }, + @"transform-style": struct { TransformStyle, VendorPrefix }, + @"transform-box": TransformBox, + @"backface-visibility": struct { BackfaceVisibility, VendorPrefix }, + perspective: struct { Perspective, VendorPrefix }, + @"perspective-origin": struct { Position, VendorPrefix }, + translate: Translate, + rotate: Rotate, + scale: Scale, @"text-decoration-color": struct { CssColor, VendorPrefix }, @"text-emphasis-color": struct { CssColor, VendorPrefix }, @"text-shadow": SmallList(TextShadow, 1), @@ -3517,6 +3543,246 @@ pub const Property = union(PropertyIdTag) { compile_error = compile_error ++ @typeName(Font) ++ ": does not have a eql() function.\n"; } + if (!@hasDecl(SmallList(PropertyId, 1), "deepClone")) { + compile_error = compile_error ++ @typeName(SmallList(PropertyId, 1)) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(SmallList(PropertyId, 1), "parse")) { + compile_error = compile_error ++ @typeName(SmallList(PropertyId, 1)) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(SmallList(PropertyId, 1), "toCss")) { + compile_error = compile_error ++ @typeName(SmallList(PropertyId, 1)) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(SmallList(PropertyId, 1), "eql")) { + compile_error = compile_error ++ @typeName(SmallList(PropertyId, 1)) ++ ": does not have a eql() function.\n"; + } + + if (!@hasDecl(SmallList(Time, 1), "deepClone")) { + compile_error = compile_error ++ @typeName(SmallList(Time, 1)) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(SmallList(Time, 1), "parse")) { + compile_error = compile_error ++ @typeName(SmallList(Time, 1)) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(SmallList(Time, 1), "toCss")) { + compile_error = compile_error ++ @typeName(SmallList(Time, 1)) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(SmallList(Time, 1), "eql")) { + compile_error = compile_error ++ @typeName(SmallList(Time, 1)) ++ ": does not have a eql() function.\n"; + } + + if (!@hasDecl(SmallList(Time, 1), "deepClone")) { + compile_error = compile_error ++ @typeName(SmallList(Time, 1)) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(SmallList(Time, 1), "parse")) { + compile_error = compile_error ++ @typeName(SmallList(Time, 1)) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(SmallList(Time, 1), "toCss")) { + compile_error = compile_error ++ @typeName(SmallList(Time, 1)) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(SmallList(Time, 1), "eql")) { + compile_error = compile_error ++ @typeName(SmallList(Time, 1)) ++ ": does not have a eql() function.\n"; + } + + if (!@hasDecl(SmallList(EasingFunction, 1), "deepClone")) { + compile_error = compile_error ++ @typeName(SmallList(EasingFunction, 1)) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(SmallList(EasingFunction, 1), "parse")) { + compile_error = compile_error ++ @typeName(SmallList(EasingFunction, 1)) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(SmallList(EasingFunction, 1), "toCss")) { + compile_error = compile_error ++ @typeName(SmallList(EasingFunction, 1)) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(SmallList(EasingFunction, 1), "eql")) { + compile_error = compile_error ++ @typeName(SmallList(EasingFunction, 1)) ++ ": does not have a eql() function.\n"; + } + + if (!@hasDecl(SmallList(Transition, 1), "deepClone")) { + compile_error = compile_error ++ @typeName(SmallList(Transition, 1)) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(SmallList(Transition, 1), "parse")) { + compile_error = compile_error ++ @typeName(SmallList(Transition, 1)) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(SmallList(Transition, 1), "toCss")) { + compile_error = compile_error ++ @typeName(SmallList(Transition, 1)) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(SmallList(Transition, 1), "eql")) { + compile_error = compile_error ++ @typeName(SmallList(Transition, 1)) ++ ": does not have a eql() function.\n"; + } + + if (!@hasDecl(TransformList, "deepClone")) { + compile_error = compile_error ++ @typeName(TransformList) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(TransformList, "parse")) { + compile_error = compile_error ++ @typeName(TransformList) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(TransformList, "toCss")) { + compile_error = compile_error ++ @typeName(TransformList) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(TransformList, "eql")) { + compile_error = compile_error ++ @typeName(TransformList) ++ ": does not have a eql() function.\n"; + } + + if (!@hasDecl(Position, "deepClone")) { + compile_error = compile_error ++ @typeName(Position) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(Position, "parse")) { + compile_error = compile_error ++ @typeName(Position) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(Position, "toCss")) { + compile_error = compile_error ++ @typeName(Position) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(Position, "eql")) { + compile_error = compile_error ++ @typeName(Position) ++ ": does not have a eql() function.\n"; + } + + if (!@hasDecl(TransformStyle, "deepClone")) { + compile_error = compile_error ++ @typeName(TransformStyle) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(TransformStyle, "parse")) { + compile_error = compile_error ++ @typeName(TransformStyle) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(TransformStyle, "toCss")) { + compile_error = compile_error ++ @typeName(TransformStyle) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(TransformStyle, "eql")) { + compile_error = compile_error ++ @typeName(TransformStyle) ++ ": does not have a eql() function.\n"; + } + + if (!@hasDecl(TransformBox, "deepClone")) { + compile_error = compile_error ++ @typeName(TransformBox) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(TransformBox, "parse")) { + compile_error = compile_error ++ @typeName(TransformBox) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(TransformBox, "toCss")) { + compile_error = compile_error ++ @typeName(TransformBox) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(TransformBox, "eql")) { + compile_error = compile_error ++ @typeName(TransformBox) ++ ": does not have a eql() function.\n"; + } + + if (!@hasDecl(BackfaceVisibility, "deepClone")) { + compile_error = compile_error ++ @typeName(BackfaceVisibility) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(BackfaceVisibility, "parse")) { + compile_error = compile_error ++ @typeName(BackfaceVisibility) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(BackfaceVisibility, "toCss")) { + compile_error = compile_error ++ @typeName(BackfaceVisibility) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(BackfaceVisibility, "eql")) { + compile_error = compile_error ++ @typeName(BackfaceVisibility) ++ ": does not have a eql() function.\n"; + } + + if (!@hasDecl(Perspective, "deepClone")) { + compile_error = compile_error ++ @typeName(Perspective) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(Perspective, "parse")) { + compile_error = compile_error ++ @typeName(Perspective) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(Perspective, "toCss")) { + compile_error = compile_error ++ @typeName(Perspective) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(Perspective, "eql")) { + compile_error = compile_error ++ @typeName(Perspective) ++ ": does not have a eql() function.\n"; + } + + if (!@hasDecl(Position, "deepClone")) { + compile_error = compile_error ++ @typeName(Position) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(Position, "parse")) { + compile_error = compile_error ++ @typeName(Position) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(Position, "toCss")) { + compile_error = compile_error ++ @typeName(Position) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(Position, "eql")) { + compile_error = compile_error ++ @typeName(Position) ++ ": does not have a eql() function.\n"; + } + + if (!@hasDecl(Translate, "deepClone")) { + compile_error = compile_error ++ @typeName(Translate) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(Translate, "parse")) { + compile_error = compile_error ++ @typeName(Translate) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(Translate, "toCss")) { + compile_error = compile_error ++ @typeName(Translate) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(Translate, "eql")) { + compile_error = compile_error ++ @typeName(Translate) ++ ": does not have a eql() function.\n"; + } + + if (!@hasDecl(Rotate, "deepClone")) { + compile_error = compile_error ++ @typeName(Rotate) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(Rotate, "parse")) { + compile_error = compile_error ++ @typeName(Rotate) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(Rotate, "toCss")) { + compile_error = compile_error ++ @typeName(Rotate) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(Rotate, "eql")) { + compile_error = compile_error ++ @typeName(Rotate) ++ ": does not have a eql() function.\n"; + } + + if (!@hasDecl(Scale, "deepClone")) { + compile_error = compile_error ++ @typeName(Scale) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(Scale, "parse")) { + compile_error = compile_error ++ @typeName(Scale) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(Scale, "toCss")) { + compile_error = compile_error ++ @typeName(Scale) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(Scale, "eql")) { + compile_error = compile_error ++ @typeName(Scale) ++ ": does not have a eql() function.\n"; + } + if (!@hasDecl(CssColor, "deepClone")) { compile_error = compile_error ++ @typeName(CssColor) ++ ": does not have a deepClone() function.\n"; } @@ -5431,6 +5697,111 @@ pub const Property = union(PropertyIdTag) { } } }, + .@"transition-property" => |pre| { + if (css.generic.parseWithOptions(SmallList(PropertyId, 1), input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .@"transition-property" = .{ c, pre } } }; + } + } + }, + .@"transition-duration" => |pre| { + if (css.generic.parseWithOptions(SmallList(Time, 1), input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .@"transition-duration" = .{ c, pre } } }; + } + } + }, + .@"transition-delay" => |pre| { + if (css.generic.parseWithOptions(SmallList(Time, 1), input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .@"transition-delay" = .{ c, pre } } }; + } + } + }, + .@"transition-timing-function" => |pre| { + if (css.generic.parseWithOptions(SmallList(EasingFunction, 1), input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .@"transition-timing-function" = .{ c, pre } } }; + } + } + }, + .transition => |pre| { + if (css.generic.parseWithOptions(SmallList(Transition, 1), input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .transition = .{ c, pre } } }; + } + } + }, + .transform => |pre| { + if (css.generic.parseWithOptions(TransformList, input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .transform = .{ c, pre } } }; + } + } + }, + .@"transform-origin" => |pre| { + if (css.generic.parseWithOptions(Position, input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .@"transform-origin" = .{ c, pre } } }; + } + } + }, + .@"transform-style" => |pre| { + if (css.generic.parseWithOptions(TransformStyle, input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .@"transform-style" = .{ c, pre } } }; + } + } + }, + .@"transform-box" => { + if (css.generic.parseWithOptions(TransformBox, input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .@"transform-box" = c } }; + } + } + }, + .@"backface-visibility" => |pre| { + if (css.generic.parseWithOptions(BackfaceVisibility, input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .@"backface-visibility" = .{ c, pre } } }; + } + } + }, + .perspective => |pre| { + if (css.generic.parseWithOptions(Perspective, input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .perspective = .{ c, pre } } }; + } + } + }, + .@"perspective-origin" => |pre| { + if (css.generic.parseWithOptions(Position, input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .@"perspective-origin" = .{ c, pre } } }; + } + } + }, + .translate => { + if (css.generic.parseWithOptions(Translate, input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .translate = c } }; + } + } + }, + .rotate => { + if (css.generic.parseWithOptions(Rotate, input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .rotate = c } }; + } + } + }, + .scale => { + if (css.generic.parseWithOptions(Scale, input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .scale = c } }; + } + } + }, .@"text-decoration-color" => |pre| { if (css.generic.parseWithOptions(CssColor, input, options).asValue()) |c| { if (input.expectExhausted().isOk()) { @@ -5878,6 +6249,21 @@ pub const Property = union(PropertyIdTag) { .@"font-variant-caps" => .@"font-variant-caps", .@"line-height" => .@"line-height", .font => .font, + .@"transition-property" => |*v| PropertyId{ .@"transition-property" = v[1] }, + .@"transition-duration" => |*v| PropertyId{ .@"transition-duration" = v[1] }, + .@"transition-delay" => |*v| PropertyId{ .@"transition-delay" = v[1] }, + .@"transition-timing-function" => |*v| PropertyId{ .@"transition-timing-function" = v[1] }, + .transition => |*v| PropertyId{ .transition = v[1] }, + .transform => |*v| PropertyId{ .transform = v[1] }, + .@"transform-origin" => |*v| PropertyId{ .@"transform-origin" = v[1] }, + .@"transform-style" => |*v| PropertyId{ .@"transform-style" = v[1] }, + .@"transform-box" => .@"transform-box", + .@"backface-visibility" => |*v| PropertyId{ .@"backface-visibility" = v[1] }, + .perspective => |*v| PropertyId{ .perspective = v[1] }, + .@"perspective-origin" => |*v| PropertyId{ .@"perspective-origin" = v[1] }, + .translate => .translate, + .rotate => .rotate, + .scale => .scale, .@"text-decoration-color" => |*v| PropertyId{ .@"text-decoration-color" = v[1] }, .@"text-emphasis-color" => |*v| PropertyId{ .@"text-emphasis-color" = v[1] }, .@"text-shadow" => .@"text-shadow", @@ -6116,6 +6502,21 @@ pub const Property = union(PropertyIdTag) { .@"font-variant-caps" => |*v| .{ .@"font-variant-caps" = v.deepClone(allocator) }, .@"line-height" => |*v| .{ .@"line-height" = v.deepClone(allocator) }, .font => |*v| .{ .font = v.deepClone(allocator) }, + .@"transition-property" => |*v| .{ .@"transition-property" = .{ v[0].deepClone(allocator), v[1] } }, + .@"transition-duration" => |*v| .{ .@"transition-duration" = .{ v[0].deepClone(allocator), v[1] } }, + .@"transition-delay" => |*v| .{ .@"transition-delay" = .{ v[0].deepClone(allocator), v[1] } }, + .@"transition-timing-function" => |*v| .{ .@"transition-timing-function" = .{ v[0].deepClone(allocator), v[1] } }, + .transition => |*v| .{ .transition = .{ v[0].deepClone(allocator), v[1] } }, + .transform => |*v| .{ .transform = .{ v[0].deepClone(allocator), v[1] } }, + .@"transform-origin" => |*v| .{ .@"transform-origin" = .{ v[0].deepClone(allocator), v[1] } }, + .@"transform-style" => |*v| .{ .@"transform-style" = .{ v[0].deepClone(allocator), v[1] } }, + .@"transform-box" => |*v| .{ .@"transform-box" = v.deepClone(allocator) }, + .@"backface-visibility" => |*v| .{ .@"backface-visibility" = .{ v[0].deepClone(allocator), v[1] } }, + .perspective => |*v| .{ .perspective = .{ v[0].deepClone(allocator), v[1] } }, + .@"perspective-origin" => |*v| .{ .@"perspective-origin" = .{ v[0].deepClone(allocator), v[1] } }, + .translate => |*v| .{ .translate = v.deepClone(allocator) }, + .rotate => |*v| .{ .rotate = v.deepClone(allocator) }, + .scale => |*v| .{ .scale = v.deepClone(allocator) }, .@"text-decoration-color" => |*v| .{ .@"text-decoration-color" = .{ v[0].deepClone(allocator), v[1] } }, .@"text-emphasis-color" => |*v| .{ .@"text-emphasis-color" = .{ v[0].deepClone(allocator), v[1] } }, .@"text-shadow" => |*v| .{ .@"text-shadow" = v.deepClone(allocator) }, @@ -6364,6 +6765,21 @@ pub const Property = union(PropertyIdTag) { .@"font-variant-caps" => .{ "font-variant-caps", VendorPrefix{ .none = true } }, .@"line-height" => .{ "line-height", VendorPrefix{ .none = true } }, .font => .{ "font", VendorPrefix{ .none = true } }, + .@"transition-property" => |*x| .{ "transition-property", x.@"1" }, + .@"transition-duration" => |*x| .{ "transition-duration", x.@"1" }, + .@"transition-delay" => |*x| .{ "transition-delay", x.@"1" }, + .@"transition-timing-function" => |*x| .{ "transition-timing-function", x.@"1" }, + .transition => |*x| .{ "transition", x.@"1" }, + .transform => |*x| .{ "transform", x.@"1" }, + .@"transform-origin" => |*x| .{ "transform-origin", x.@"1" }, + .@"transform-style" => |*x| .{ "transform-style", x.@"1" }, + .@"transform-box" => .{ "transform-box", VendorPrefix{ .none = true } }, + .@"backface-visibility" => |*x| .{ "backface-visibility", x.@"1" }, + .perspective => |*x| .{ "perspective", x.@"1" }, + .@"perspective-origin" => |*x| .{ "perspective-origin", x.@"1" }, + .translate => .{ "translate", VendorPrefix{ .none = true } }, + .rotate => .{ "rotate", VendorPrefix{ .none = true } }, + .scale => .{ "scale", VendorPrefix{ .none = true } }, .@"text-decoration-color" => |*x| .{ "text-decoration-color", x.@"1" }, .@"text-emphasis-color" => |*x| .{ "text-emphasis-color", x.@"1" }, .@"text-shadow" => .{ "text-shadow", VendorPrefix{ .none = true } }, @@ -6609,6 +7025,21 @@ pub const Property = union(PropertyIdTag) { .@"font-variant-caps" => |*value| value.toCss(W, dest), .@"line-height" => |*value| value.toCss(W, dest), .font => |*value| value.toCss(W, dest), + .@"transition-property" => |*value| value[0].toCss(W, dest), + .@"transition-duration" => |*value| value[0].toCss(W, dest), + .@"transition-delay" => |*value| value[0].toCss(W, dest), + .@"transition-timing-function" => |*value| value[0].toCss(W, dest), + .transition => |*value| value[0].toCss(W, dest), + .transform => |*value| value[0].toCss(W, dest), + .@"transform-origin" => |*value| value[0].toCss(W, dest), + .@"transform-style" => |*value| value[0].toCss(W, dest), + .@"transform-box" => |*value| value.toCss(W, dest), + .@"backface-visibility" => |*value| value[0].toCss(W, dest), + .perspective => |*value| value[0].toCss(W, dest), + .@"perspective-origin" => |*value| value[0].toCss(W, dest), + .translate => |*value| value.toCss(W, dest), + .rotate => |*value| value.toCss(W, dest), + .scale => |*value| value.toCss(W, dest), .@"text-decoration-color" => |*value| value[0].toCss(W, dest), .@"text-emphasis-color" => |*value| value[0].toCss(W, dest), .@"text-shadow" => |*value| value.toCss(W, dest), @@ -6709,6 +7140,10 @@ pub const Property = union(PropertyIdTag) { .@"scroll-padding-inline" => |*v| return v.longhand(property_id), .@"scroll-padding" => |*v| return v.longhand(property_id), .font => |*v| return v.longhand(property_id), + .transition => |*v| { + if (!v[1].eq(property_id.prefix())) return null; + return v[0].longhand(property_id); + }, .mask => |*v| { if (!v[1].eq(property_id.prefix())) return null; return v[0].longhand(property_id); @@ -6920,6 +7355,21 @@ pub const Property = union(PropertyIdTag) { .@"font-variant-caps" => |*v| css.generic.eql(FontVariantCaps, v, &rhs.@"font-variant-caps"), .@"line-height" => |*v| css.generic.eql(LineHeight, v, &rhs.@"line-height"), .font => |*v| css.generic.eql(Font, v, &rhs.font), + .@"transition-property" => |*v| css.generic.eql(SmallList(PropertyId, 1), &v[0], &rhs.@"transition-property"[0]) and v[1].eq(rhs.@"transition-property"[1]), + .@"transition-duration" => |*v| css.generic.eql(SmallList(Time, 1), &v[0], &rhs.@"transition-duration"[0]) and v[1].eq(rhs.@"transition-duration"[1]), + .@"transition-delay" => |*v| css.generic.eql(SmallList(Time, 1), &v[0], &rhs.@"transition-delay"[0]) and v[1].eq(rhs.@"transition-delay"[1]), + .@"transition-timing-function" => |*v| css.generic.eql(SmallList(EasingFunction, 1), &v[0], &rhs.@"transition-timing-function"[0]) and v[1].eq(rhs.@"transition-timing-function"[1]), + .transition => |*v| css.generic.eql(SmallList(Transition, 1), &v[0], &rhs.transition[0]) and v[1].eq(rhs.transition[1]), + .transform => |*v| css.generic.eql(TransformList, &v[0], &rhs.transform[0]) and v[1].eq(rhs.transform[1]), + .@"transform-origin" => |*v| css.generic.eql(Position, &v[0], &rhs.@"transform-origin"[0]) and v[1].eq(rhs.@"transform-origin"[1]), + .@"transform-style" => |*v| css.generic.eql(TransformStyle, &v[0], &rhs.@"transform-style"[0]) and v[1].eq(rhs.@"transform-style"[1]), + .@"transform-box" => |*v| css.generic.eql(TransformBox, v, &rhs.@"transform-box"), + .@"backface-visibility" => |*v| css.generic.eql(BackfaceVisibility, &v[0], &rhs.@"backface-visibility"[0]) and v[1].eq(rhs.@"backface-visibility"[1]), + .perspective => |*v| css.generic.eql(Perspective, &v[0], &rhs.perspective[0]) and v[1].eq(rhs.perspective[1]), + .@"perspective-origin" => |*v| css.generic.eql(Position, &v[0], &rhs.@"perspective-origin"[0]) and v[1].eq(rhs.@"perspective-origin"[1]), + .translate => |*v| css.generic.eql(Translate, v, &rhs.translate), + .rotate => |*v| css.generic.eql(Rotate, v, &rhs.rotate), + .scale => |*v| css.generic.eql(Scale, v, &rhs.scale), .@"text-decoration-color" => |*v| css.generic.eql(CssColor, &v[0], &rhs.@"text-decoration-color"[0]) and v[1].eq(rhs.@"text-decoration-color"[1]), .@"text-emphasis-color" => |*v| css.generic.eql(CssColor, &v[0], &rhs.@"text-emphasis-color"[0]) and v[1].eq(rhs.@"text-emphasis-color"[1]), .@"text-shadow" => |*v| css.generic.eql(SmallList(TextShadow, 1), v, &rhs.@"text-shadow"), @@ -7157,6 +7607,21 @@ pub const PropertyId = union(PropertyIdTag) { @"font-variant-caps", @"line-height", font, + @"transition-property": VendorPrefix, + @"transition-duration": VendorPrefix, + @"transition-delay": VendorPrefix, + @"transition-timing-function": VendorPrefix, + transition: VendorPrefix, + transform: VendorPrefix, + @"transform-origin": VendorPrefix, + @"transform-style": VendorPrefix, + @"transform-box", + @"backface-visibility": VendorPrefix, + perspective: VendorPrefix, + @"perspective-origin": VendorPrefix, + translate, + rotate, + scale, @"text-decoration-color": VendorPrefix, @"text-emphasis-color": VendorPrefix, @"text-shadow", @@ -7402,6 +7867,21 @@ pub const PropertyId = union(PropertyIdTag) { .@"font-variant-caps" => VendorPrefix.empty(), .@"line-height" => VendorPrefix.empty(), .font => VendorPrefix.empty(), + .@"transition-property" => |p| p, + .@"transition-duration" => |p| p, + .@"transition-delay" => |p| p, + .@"transition-timing-function" => |p| p, + .transition => |p| p, + .transform => |p| p, + .@"transform-origin" => |p| p, + .@"transform-style" => |p| p, + .@"transform-box" => VendorPrefix.empty(), + .@"backface-visibility" => |p| p, + .perspective => |p| p, + .@"perspective-origin" => |p| p, + .translate => VendorPrefix.empty(), + .rotate => VendorPrefix.empty(), + .scale => VendorPrefix.empty(), .@"text-decoration-color" => |p| p, .@"text-emphasis-color" => |p| p, .@"text-shadow" => VendorPrefix.empty(), @@ -7439,7 +7919,7 @@ pub const PropertyId = union(PropertyIdTag) { } pub fn fromNameAndPrefix(name1: []const u8, pre: VendorPrefix) ?PropertyId { - const Enum = enum { @"background-color", @"background-image", @"background-position-x", @"background-position-y", @"background-position", @"background-size", @"background-repeat", @"background-attachment", @"background-clip", @"background-origin", background, @"box-shadow", opacity, color, display, visibility, width, height, @"min-width", @"min-height", @"max-width", @"max-height", @"block-size", @"inline-size", @"min-block-size", @"min-inline-size", @"max-block-size", @"max-inline-size", @"box-sizing", @"aspect-ratio", overflow, @"overflow-x", @"overflow-y", @"text-overflow", position, top, bottom, left, right, @"inset-block-start", @"inset-block-end", @"inset-inline-start", @"inset-inline-end", @"inset-block", @"inset-inline", inset, @"border-spacing", @"border-top-color", @"border-bottom-color", @"border-left-color", @"border-right-color", @"border-block-start-color", @"border-block-end-color", @"border-inline-start-color", @"border-inline-end-color", @"border-top-style", @"border-bottom-style", @"border-left-style", @"border-right-style", @"border-block-start-style", @"border-block-end-style", @"border-inline-start-style", @"border-inline-end-style", @"border-top-width", @"border-bottom-width", @"border-left-width", @"border-right-width", @"border-block-start-width", @"border-block-end-width", @"border-inline-start-width", @"border-inline-end-width", @"border-top-left-radius", @"border-top-right-radius", @"border-bottom-left-radius", @"border-bottom-right-radius", @"border-start-start-radius", @"border-start-end-radius", @"border-end-start-radius", @"border-end-end-radius", @"border-radius", @"border-image-source", @"border-image-outset", @"border-image-repeat", @"border-image-width", @"border-image-slice", @"border-image", @"border-color", @"border-style", @"border-width", @"border-block-color", @"border-block-style", @"border-block-width", @"border-inline-color", @"border-inline-style", @"border-inline-width", border, @"border-top", @"border-bottom", @"border-left", @"border-right", @"border-block", @"border-block-start", @"border-block-end", @"border-inline", @"border-inline-start", @"border-inline-end", outline, @"outline-color", @"outline-style", @"outline-width", @"flex-direction", @"flex-wrap", @"flex-flow", @"flex-grow", @"flex-shrink", @"flex-basis", flex, order, @"align-content", @"justify-content", @"place-content", @"align-self", @"justify-self", @"place-self", @"align-items", @"justify-items", @"place-items", @"row-gap", @"column-gap", gap, @"box-orient", @"box-direction", @"box-ordinal-group", @"box-align", @"box-flex", @"box-flex-group", @"box-pack", @"box-lines", @"flex-pack", @"flex-order", @"flex-align", @"flex-item-align", @"flex-line-pack", @"flex-positive", @"flex-negative", @"flex-preferred-size", @"margin-top", @"margin-bottom", @"margin-left", @"margin-right", @"margin-block-start", @"margin-block-end", @"margin-inline-start", @"margin-inline-end", @"margin-block", @"margin-inline", margin, @"padding-top", @"padding-bottom", @"padding-left", @"padding-right", @"padding-block-start", @"padding-block-end", @"padding-inline-start", @"padding-inline-end", @"padding-block", @"padding-inline", padding, @"scroll-margin-top", @"scroll-margin-bottom", @"scroll-margin-left", @"scroll-margin-right", @"scroll-margin-block-start", @"scroll-margin-block-end", @"scroll-margin-inline-start", @"scroll-margin-inline-end", @"scroll-margin-block", @"scroll-margin-inline", @"scroll-margin", @"scroll-padding-top", @"scroll-padding-bottom", @"scroll-padding-left", @"scroll-padding-right", @"scroll-padding-block-start", @"scroll-padding-block-end", @"scroll-padding-inline-start", @"scroll-padding-inline-end", @"scroll-padding-block", @"scroll-padding-inline", @"scroll-padding", @"font-weight", @"font-size", @"font-stretch", @"font-family", @"font-style", @"font-variant-caps", @"line-height", font, @"text-decoration-color", @"text-emphasis-color", @"text-shadow", direction, composes, @"mask-image", @"mask-mode", @"mask-repeat", @"mask-position-x", @"mask-position-y", @"mask-position", @"mask-clip", @"mask-origin", @"mask-size", @"mask-composite", @"mask-type", mask, @"mask-border-source", @"mask-border-mode", @"mask-border-slice", @"mask-border-width", @"mask-border-outset", @"mask-border-repeat", @"mask-border", @"-webkit-mask-composite", @"mask-source-type", @"mask-box-image", @"mask-box-image-source", @"mask-box-image-slice", @"mask-box-image-width", @"mask-box-image-outset", @"mask-box-image-repeat" }; + const Enum = enum { @"background-color", @"background-image", @"background-position-x", @"background-position-y", @"background-position", @"background-size", @"background-repeat", @"background-attachment", @"background-clip", @"background-origin", background, @"box-shadow", opacity, color, display, visibility, width, height, @"min-width", @"min-height", @"max-width", @"max-height", @"block-size", @"inline-size", @"min-block-size", @"min-inline-size", @"max-block-size", @"max-inline-size", @"box-sizing", @"aspect-ratio", overflow, @"overflow-x", @"overflow-y", @"text-overflow", position, top, bottom, left, right, @"inset-block-start", @"inset-block-end", @"inset-inline-start", @"inset-inline-end", @"inset-block", @"inset-inline", inset, @"border-spacing", @"border-top-color", @"border-bottom-color", @"border-left-color", @"border-right-color", @"border-block-start-color", @"border-block-end-color", @"border-inline-start-color", @"border-inline-end-color", @"border-top-style", @"border-bottom-style", @"border-left-style", @"border-right-style", @"border-block-start-style", @"border-block-end-style", @"border-inline-start-style", @"border-inline-end-style", @"border-top-width", @"border-bottom-width", @"border-left-width", @"border-right-width", @"border-block-start-width", @"border-block-end-width", @"border-inline-start-width", @"border-inline-end-width", @"border-top-left-radius", @"border-top-right-radius", @"border-bottom-left-radius", @"border-bottom-right-radius", @"border-start-start-radius", @"border-start-end-radius", @"border-end-start-radius", @"border-end-end-radius", @"border-radius", @"border-image-source", @"border-image-outset", @"border-image-repeat", @"border-image-width", @"border-image-slice", @"border-image", @"border-color", @"border-style", @"border-width", @"border-block-color", @"border-block-style", @"border-block-width", @"border-inline-color", @"border-inline-style", @"border-inline-width", border, @"border-top", @"border-bottom", @"border-left", @"border-right", @"border-block", @"border-block-start", @"border-block-end", @"border-inline", @"border-inline-start", @"border-inline-end", outline, @"outline-color", @"outline-style", @"outline-width", @"flex-direction", @"flex-wrap", @"flex-flow", @"flex-grow", @"flex-shrink", @"flex-basis", flex, order, @"align-content", @"justify-content", @"place-content", @"align-self", @"justify-self", @"place-self", @"align-items", @"justify-items", @"place-items", @"row-gap", @"column-gap", gap, @"box-orient", @"box-direction", @"box-ordinal-group", @"box-align", @"box-flex", @"box-flex-group", @"box-pack", @"box-lines", @"flex-pack", @"flex-order", @"flex-align", @"flex-item-align", @"flex-line-pack", @"flex-positive", @"flex-negative", @"flex-preferred-size", @"margin-top", @"margin-bottom", @"margin-left", @"margin-right", @"margin-block-start", @"margin-block-end", @"margin-inline-start", @"margin-inline-end", @"margin-block", @"margin-inline", margin, @"padding-top", @"padding-bottom", @"padding-left", @"padding-right", @"padding-block-start", @"padding-block-end", @"padding-inline-start", @"padding-inline-end", @"padding-block", @"padding-inline", padding, @"scroll-margin-top", @"scroll-margin-bottom", @"scroll-margin-left", @"scroll-margin-right", @"scroll-margin-block-start", @"scroll-margin-block-end", @"scroll-margin-inline-start", @"scroll-margin-inline-end", @"scroll-margin-block", @"scroll-margin-inline", @"scroll-margin", @"scroll-padding-top", @"scroll-padding-bottom", @"scroll-padding-left", @"scroll-padding-right", @"scroll-padding-block-start", @"scroll-padding-block-end", @"scroll-padding-inline-start", @"scroll-padding-inline-end", @"scroll-padding-block", @"scroll-padding-inline", @"scroll-padding", @"font-weight", @"font-size", @"font-stretch", @"font-family", @"font-style", @"font-variant-caps", @"line-height", font, @"transition-property", @"transition-duration", @"transition-delay", @"transition-timing-function", transition, transform, @"transform-origin", @"transform-style", @"transform-box", @"backface-visibility", perspective, @"perspective-origin", translate, rotate, scale, @"text-decoration-color", @"text-emphasis-color", @"text-shadow", direction, composes, @"mask-image", @"mask-mode", @"mask-repeat", @"mask-position-x", @"mask-position-y", @"mask-position", @"mask-clip", @"mask-origin", @"mask-size", @"mask-composite", @"mask-type", mask, @"mask-border-source", @"mask-border-mode", @"mask-border-slice", @"mask-border-width", @"mask-border-outset", @"mask-border-repeat", @"mask-border", @"-webkit-mask-composite", @"mask-source-type", @"mask-box-image", @"mask-box-image-source", @"mask-box-image-slice", @"mask-box-image-width", @"mask-box-image-outset", @"mask-box-image-repeat" }; const Map = comptime bun.ComptimeEnumMap(Enum); if (Map.getASCIIICaseInsensitive(name1)) |prop| { switch (prop) { @@ -8235,6 +8715,66 @@ pub const PropertyId = union(PropertyIdTag) { const allowed_prefixes = VendorPrefix{ .none = true }; if (allowed_prefixes.contains(pre)) return .font; }, + .@"transition-property" => { + const allowed_prefixes = VendorPrefix{ .none = true, .webkit = true, .moz = true, .ms = true }; + if (allowed_prefixes.contains(pre)) return .{ .@"transition-property" = pre }; + }, + .@"transition-duration" => { + const allowed_prefixes = VendorPrefix{ .none = true, .webkit = true, .moz = true, .ms = true }; + if (allowed_prefixes.contains(pre)) return .{ .@"transition-duration" = pre }; + }, + .@"transition-delay" => { + const allowed_prefixes = VendorPrefix{ .none = true, .webkit = true, .moz = true, .ms = true }; + if (allowed_prefixes.contains(pre)) return .{ .@"transition-delay" = pre }; + }, + .@"transition-timing-function" => { + const allowed_prefixes = VendorPrefix{ .none = true, .webkit = true, .moz = true, .ms = true }; + if (allowed_prefixes.contains(pre)) return .{ .@"transition-timing-function" = pre }; + }, + .transition => { + const allowed_prefixes = VendorPrefix{ .none = true, .webkit = true, .moz = true, .ms = true }; + if (allowed_prefixes.contains(pre)) return .{ .transition = pre }; + }, + .transform => { + const allowed_prefixes = VendorPrefix{ .none = true, .webkit = true, .moz = true, .ms = true, .o = true }; + if (allowed_prefixes.contains(pre)) return .{ .transform = pre }; + }, + .@"transform-origin" => { + const allowed_prefixes = VendorPrefix{ .none = true, .webkit = true, .moz = true, .ms = true, .o = true }; + if (allowed_prefixes.contains(pre)) return .{ .@"transform-origin" = pre }; + }, + .@"transform-style" => { + const allowed_prefixes = VendorPrefix{ .none = true, .webkit = true, .moz = true }; + if (allowed_prefixes.contains(pre)) return .{ .@"transform-style" = pre }; + }, + .@"transform-box" => { + const allowed_prefixes = VendorPrefix{ .none = true }; + if (allowed_prefixes.contains(pre)) return .@"transform-box"; + }, + .@"backface-visibility" => { + const allowed_prefixes = VendorPrefix{ .none = true, .webkit = true, .moz = true }; + if (allowed_prefixes.contains(pre)) return .{ .@"backface-visibility" = pre }; + }, + .perspective => { + const allowed_prefixes = VendorPrefix{ .none = true, .webkit = true, .moz = true }; + if (allowed_prefixes.contains(pre)) return .{ .perspective = pre }; + }, + .@"perspective-origin" => { + const allowed_prefixes = VendorPrefix{ .none = true, .webkit = true, .moz = true }; + if (allowed_prefixes.contains(pre)) return .{ .@"perspective-origin" = pre }; + }, + .translate => { + const allowed_prefixes = VendorPrefix{ .none = true }; + if (allowed_prefixes.contains(pre)) return .translate; + }, + .rotate => { + const allowed_prefixes = VendorPrefix{ .none = true }; + if (allowed_prefixes.contains(pre)) return .rotate; + }, + .scale => { + const allowed_prefixes = VendorPrefix{ .none = true }; + if (allowed_prefixes.contains(pre)) return .scale; + }, .@"text-decoration-color" => { const allowed_prefixes = VendorPrefix{ .none = true, .webkit = true, .moz = true }; if (allowed_prefixes.contains(pre)) return .{ .@"text-decoration-color" = pre }; @@ -8569,6 +9109,21 @@ pub const PropertyId = union(PropertyIdTag) { .@"font-variant-caps" => .@"font-variant-caps", .@"line-height" => .@"line-height", .font => .font, + .@"transition-property" => .{ .@"transition-property" = pre }, + .@"transition-duration" => .{ .@"transition-duration" = pre }, + .@"transition-delay" => .{ .@"transition-delay" = pre }, + .@"transition-timing-function" => .{ .@"transition-timing-function" = pre }, + .transition => .{ .transition = pre }, + .transform => .{ .transform = pre }, + .@"transform-origin" => .{ .@"transform-origin" = pre }, + .@"transform-style" => .{ .@"transform-style" = pre }, + .@"transform-box" => .@"transform-box", + .@"backface-visibility" => .{ .@"backface-visibility" = pre }, + .perspective => .{ .perspective = pre }, + .@"perspective-origin" => .{ .@"perspective-origin" = pre }, + .translate => .translate, + .rotate => .rotate, + .scale => .scale, .@"text-decoration-color" => .{ .@"text-decoration-color" = pre }, .@"text-emphasis-color" => .{ .@"text-emphasis-color" = pre }, .@"text-shadow" => .@"text-shadow", @@ -8881,6 +9436,43 @@ pub const PropertyId = union(PropertyIdTag) { .@"font-variant-caps" => {}, .@"line-height" => {}, .font => {}, + .@"transition-property" => |*p| { + p.insert(pre); + }, + .@"transition-duration" => |*p| { + p.insert(pre); + }, + .@"transition-delay" => |*p| { + p.insert(pre); + }, + .@"transition-timing-function" => |*p| { + p.insert(pre); + }, + .transition => |*p| { + p.insert(pre); + }, + .transform => |*p| { + p.insert(pre); + }, + .@"transform-origin" => |*p| { + p.insert(pre); + }, + .@"transform-style" => |*p| { + p.insert(pre); + }, + .@"transform-box" => {}, + .@"backface-visibility" => |*p| { + p.insert(pre); + }, + .perspective => |*p| { + p.insert(pre); + }, + .@"perspective-origin" => |*p| { + p.insert(pre); + }, + .translate => {}, + .rotate => {}, + .scale => {}, .@"text-decoration-color" => |*p| { p.insert(pre); }, @@ -8971,6 +9563,383 @@ pub const PropertyId = union(PropertyIdTag) { const tag = @intFromEnum(this.*); hasher.update(std.mem.asBytes(&tag)); } + + pub fn setPrefixesForTargets(this: *PropertyId, targets: Targets) void { + switch (this.*) { + .@"background-color" => {}, + .@"background-image" => {}, + .@"background-position-x" => {}, + .@"background-position-y" => {}, + .@"background-position" => {}, + .@"background-size" => {}, + .@"background-repeat" => {}, + .@"background-attachment" => {}, + .@"background-clip" => |*x| { + x.* = targets.prefixes(x.*, Feature.background_clip); + }, + + .@"background-origin" => {}, + .background => {}, + .@"box-shadow" => |*x| { + x.* = targets.prefixes(x.*, Feature.box_shadow); + }, + + .opacity => {}, + .color => {}, + .display => {}, + .visibility => {}, + .width => {}, + .height => {}, + .@"min-width" => {}, + .@"min-height" => {}, + .@"max-width" => {}, + .@"max-height" => {}, + .@"block-size" => {}, + .@"inline-size" => {}, + .@"min-block-size" => {}, + .@"min-inline-size" => {}, + .@"max-block-size" => {}, + .@"max-inline-size" => {}, + .@"box-sizing" => |*x| { + x.* = targets.prefixes(x.*, Feature.box_sizing); + }, + + .@"aspect-ratio" => {}, + .overflow => {}, + .@"overflow-x" => {}, + .@"overflow-y" => {}, + .@"text-overflow" => |*x| { + x.* = targets.prefixes(x.*, Feature.text_overflow); + }, + + .position => {}, + .top => {}, + .bottom => {}, + .left => {}, + .right => {}, + .@"inset-block-start" => {}, + .@"inset-block-end" => {}, + .@"inset-inline-start" => {}, + .@"inset-inline-end" => {}, + .@"inset-block" => {}, + .@"inset-inline" => {}, + .inset => {}, + .@"border-spacing" => {}, + .@"border-top-color" => {}, + .@"border-bottom-color" => {}, + .@"border-left-color" => {}, + .@"border-right-color" => {}, + .@"border-block-start-color" => {}, + .@"border-block-end-color" => {}, + .@"border-inline-start-color" => {}, + .@"border-inline-end-color" => {}, + .@"border-top-style" => {}, + .@"border-bottom-style" => {}, + .@"border-left-style" => {}, + .@"border-right-style" => {}, + .@"border-block-start-style" => {}, + .@"border-block-end-style" => {}, + .@"border-inline-start-style" => {}, + .@"border-inline-end-style" => {}, + .@"border-top-width" => {}, + .@"border-bottom-width" => {}, + .@"border-left-width" => {}, + .@"border-right-width" => {}, + .@"border-block-start-width" => {}, + .@"border-block-end-width" => {}, + .@"border-inline-start-width" => {}, + .@"border-inline-end-width" => {}, + .@"border-top-left-radius" => |*x| { + x.* = targets.prefixes(x.*, Feature.border_top_left_radius); + }, + + .@"border-top-right-radius" => |*x| { + x.* = targets.prefixes(x.*, Feature.border_top_right_radius); + }, + + .@"border-bottom-left-radius" => |*x| { + x.* = targets.prefixes(x.*, Feature.border_bottom_left_radius); + }, + + .@"border-bottom-right-radius" => |*x| { + x.* = targets.prefixes(x.*, Feature.border_bottom_right_radius); + }, + + .@"border-start-start-radius" => {}, + .@"border-start-end-radius" => {}, + .@"border-end-start-radius" => {}, + .@"border-end-end-radius" => {}, + .@"border-radius" => |*x| { + x.* = targets.prefixes(x.*, Feature.border_radius); + }, + + .@"border-image-source" => {}, + .@"border-image-outset" => {}, + .@"border-image-repeat" => {}, + .@"border-image-width" => {}, + .@"border-image-slice" => {}, + .@"border-image" => |*x| { + x.* = targets.prefixes(x.*, Feature.border_image); + }, + + .@"border-color" => {}, + .@"border-style" => {}, + .@"border-width" => {}, + .@"border-block-color" => {}, + .@"border-block-style" => {}, + .@"border-block-width" => {}, + .@"border-inline-color" => {}, + .@"border-inline-style" => {}, + .@"border-inline-width" => {}, + .border => {}, + .@"border-top" => {}, + .@"border-bottom" => {}, + .@"border-left" => {}, + .@"border-right" => {}, + .@"border-block" => {}, + .@"border-block-start" => {}, + .@"border-block-end" => {}, + .@"border-inline" => {}, + .@"border-inline-start" => {}, + .@"border-inline-end" => {}, + .outline => {}, + .@"outline-color" => {}, + .@"outline-style" => {}, + .@"outline-width" => {}, + .@"flex-direction" => |*x| { + x.* = targets.prefixes(x.*, Feature.flex_direction); + }, + + .@"flex-wrap" => |*x| { + x.* = targets.prefixes(x.*, Feature.flex_wrap); + }, + + .@"flex-flow" => |*x| { + x.* = targets.prefixes(x.*, Feature.flex_flow); + }, + + .@"flex-grow" => |*x| { + x.* = targets.prefixes(x.*, Feature.flex_grow); + }, + + .@"flex-shrink" => |*x| { + x.* = targets.prefixes(x.*, Feature.flex_shrink); + }, + + .@"flex-basis" => |*x| { + x.* = targets.prefixes(x.*, Feature.flex_basis); + }, + + .flex => |*x| { + x.* = targets.prefixes(x.*, Feature.flex); + }, + + .order => |*x| { + x.* = targets.prefixes(x.*, Feature.order); + }, + + .@"align-content" => |*x| { + x.* = targets.prefixes(x.*, Feature.align_content); + }, + + .@"justify-content" => |*x| { + x.* = targets.prefixes(x.*, Feature.justify_content); + }, + + .@"place-content" => {}, + .@"align-self" => |*x| { + x.* = targets.prefixes(x.*, Feature.align_self); + }, + + .@"justify-self" => {}, + .@"place-self" => {}, + .@"align-items" => |*x| { + x.* = targets.prefixes(x.*, Feature.align_items); + }, + + .@"justify-items" => {}, + .@"place-items" => {}, + .@"row-gap" => {}, + .@"column-gap" => {}, + .gap => {}, + .@"box-orient" => {}, + .@"box-direction" => {}, + .@"box-ordinal-group" => {}, + .@"box-align" => {}, + .@"box-flex" => {}, + .@"box-flex-group" => {}, + .@"box-pack" => {}, + .@"box-lines" => {}, + .@"flex-pack" => {}, + .@"flex-order" => {}, + .@"flex-align" => {}, + .@"flex-item-align" => {}, + .@"flex-line-pack" => {}, + .@"flex-positive" => {}, + .@"flex-negative" => {}, + .@"flex-preferred-size" => {}, + .@"margin-top" => {}, + .@"margin-bottom" => {}, + .@"margin-left" => {}, + .@"margin-right" => {}, + .@"margin-block-start" => {}, + .@"margin-block-end" => {}, + .@"margin-inline-start" => {}, + .@"margin-inline-end" => {}, + .@"margin-block" => {}, + .@"margin-inline" => {}, + .margin => {}, + .@"padding-top" => {}, + .@"padding-bottom" => {}, + .@"padding-left" => {}, + .@"padding-right" => {}, + .@"padding-block-start" => {}, + .@"padding-block-end" => {}, + .@"padding-inline-start" => {}, + .@"padding-inline-end" => {}, + .@"padding-block" => {}, + .@"padding-inline" => {}, + .padding => {}, + .@"scroll-margin-top" => {}, + .@"scroll-margin-bottom" => {}, + .@"scroll-margin-left" => {}, + .@"scroll-margin-right" => {}, + .@"scroll-margin-block-start" => {}, + .@"scroll-margin-block-end" => {}, + .@"scroll-margin-inline-start" => {}, + .@"scroll-margin-inline-end" => {}, + .@"scroll-margin-block" => {}, + .@"scroll-margin-inline" => {}, + .@"scroll-margin" => {}, + .@"scroll-padding-top" => {}, + .@"scroll-padding-bottom" => {}, + .@"scroll-padding-left" => {}, + .@"scroll-padding-right" => {}, + .@"scroll-padding-block-start" => {}, + .@"scroll-padding-block-end" => {}, + .@"scroll-padding-inline-start" => {}, + .@"scroll-padding-inline-end" => {}, + .@"scroll-padding-block" => {}, + .@"scroll-padding-inline" => {}, + .@"scroll-padding" => {}, + .@"font-weight" => {}, + .@"font-size" => {}, + .@"font-stretch" => {}, + .@"font-family" => {}, + .@"font-style" => {}, + .@"font-variant-caps" => {}, + .@"line-height" => {}, + .font => {}, + .@"transition-property" => |*x| { + x.* = targets.prefixes(x.*, Feature.transition_property); + }, + + .@"transition-duration" => |*x| { + x.* = targets.prefixes(x.*, Feature.transition_duration); + }, + + .@"transition-delay" => |*x| { + x.* = targets.prefixes(x.*, Feature.transition_delay); + }, + + .@"transition-timing-function" => |*x| { + x.* = targets.prefixes(x.*, Feature.transition_timing_function); + }, + + .transition => |*x| { + x.* = targets.prefixes(x.*, Feature.transition); + }, + + .transform => |*x| { + x.* = targets.prefixes(x.*, Feature.transform); + }, + + .@"transform-origin" => |*x| { + x.* = targets.prefixes(x.*, Feature.transform_origin); + }, + + .@"transform-style" => |*x| { + x.* = targets.prefixes(x.*, Feature.transform_style); + }, + + .@"transform-box" => {}, + .@"backface-visibility" => |*x| { + x.* = targets.prefixes(x.*, Feature.backface_visibility); + }, + + .perspective => |*x| { + x.* = targets.prefixes(x.*, Feature.perspective); + }, + + .@"perspective-origin" => |*x| { + x.* = targets.prefixes(x.*, Feature.perspective_origin); + }, + + .translate => {}, + .rotate => {}, + .scale => {}, + .@"text-decoration-color" => |*x| { + x.* = targets.prefixes(x.*, Feature.text_decoration_color); + }, + + .@"text-emphasis-color" => |*x| { + x.* = targets.prefixes(x.*, Feature.text_emphasis_color); + }, + + .@"text-shadow" => {}, + .direction => {}, + .composes => {}, + .@"mask-image" => |*x| { + x.* = targets.prefixes(x.*, Feature.mask_image); + }, + + .@"mask-mode" => {}, + .@"mask-repeat" => |*x| { + x.* = targets.prefixes(x.*, Feature.mask_repeat); + }, + + .@"mask-position-x" => {}, + .@"mask-position-y" => {}, + .@"mask-position" => |*x| { + x.* = targets.prefixes(x.*, Feature.mask_position); + }, + + .@"mask-clip" => |*x| { + x.* = targets.prefixes(x.*, Feature.mask_clip); + }, + + .@"mask-origin" => |*x| { + x.* = targets.prefixes(x.*, Feature.mask_origin); + }, + + .@"mask-size" => |*x| { + x.* = targets.prefixes(x.*, Feature.mask_size); + }, + + .@"mask-composite" => {}, + .@"mask-type" => {}, + .mask => |*x| { + x.* = targets.prefixes(x.*, Feature.mask); + }, + + .@"mask-border-source" => {}, + .@"mask-border-mode" => {}, + .@"mask-border-slice" => {}, + .@"mask-border-width" => {}, + .@"mask-border-outset" => {}, + .@"mask-border-repeat" => {}, + .@"mask-border" => {}, + .@"-webkit-mask-composite" => {}, + .@"mask-source-type" => {}, + .@"mask-box-image" => {}, + .@"mask-box-image-source" => {}, + .@"mask-box-image-slice" => {}, + .@"mask-box-image-width" => {}, + .@"mask-box-image-outset" => {}, + .@"mask-box-image-repeat" => {}, + else => {}, + } + } }; pub const PropertyIdTag = enum(u16) { @"background-color", @@ -9171,6 +10140,21 @@ pub const PropertyIdTag = enum(u16) { @"font-variant-caps", @"line-height", font, + @"transition-property", + @"transition-duration", + @"transition-delay", + @"transition-timing-function", + transition, + transform, + @"transform-origin", + @"transform-style", + @"transform-box", + @"backface-visibility", + perspective, + @"perspective-origin", + translate, + rotate, + scale, @"text-decoration-color", @"text-emphasis-color", @"text-shadow", @@ -9409,6 +10393,21 @@ pub const PropertyIdTag = enum(u16) { .@"font-variant-caps" => false, .@"line-height" => false, .font => false, + .@"transition-property" => true, + .@"transition-duration" => true, + .@"transition-delay" => true, + .@"transition-timing-function" => true, + .transition => true, + .transform => true, + .@"transform-origin" => true, + .@"transform-style" => true, + .@"transform-box" => false, + .@"backface-visibility" => true, + .perspective => true, + .@"perspective-origin" => true, + .translate => false, + .rotate => false, + .scale => false, .@"text-decoration-color" => true, .@"text-emphasis-color" => true, .@"text-shadow" => false, @@ -9649,6 +10648,21 @@ pub const PropertyIdTag = enum(u16) { .@"font-variant-caps" => FontVariantCaps, .@"line-height" => LineHeight, .font => Font, + .@"transition-property" => SmallList(PropertyId, 1), + .@"transition-duration" => SmallList(Time, 1), + .@"transition-delay" => SmallList(Time, 1), + .@"transition-timing-function" => SmallList(EasingFunction, 1), + .transition => SmallList(Transition, 1), + .transform => TransformList, + .@"transform-origin" => Position, + .@"transform-style" => TransformStyle, + .@"transform-box" => TransformBox, + .@"backface-visibility" => BackfaceVisibility, + .perspective => Perspective, + .@"perspective-origin" => Position, + .translate => Translate, + .rotate => Rotate, + .scale => Scale, .@"text-decoration-color" => CssColor, .@"text-emphasis-color" => CssColor, .@"text-shadow" => SmallList(TextShadow, 1), diff --git a/src/css/properties/properties_impl.zig b/src/css/properties/properties_impl.zig index 871a61cdd3..514578d6ec 100644 --- a/src/css/properties/properties_impl.zig +++ b/src/css/properties/properties_impl.zig @@ -18,7 +18,7 @@ pub fn PropertyIdImpl() type { return struct { pub fn toCss(this: *const PropertyId, comptime W: type, dest: *Printer(W)) PrintErr!void { var first = true; - const name = this.name(this); + const name = this.name(); const prefix_value = this.prefix().orNone(); inline for (VendorPrefix.FIELDS) |field| { diff --git a/src/css/properties/transform.zig b/src/css/properties/transform.zig index 8a1c879798..0beb971e73 100644 --- a/src/css/properties/transform.zig +++ b/src/css/properties/transform.zig @@ -9,6 +9,9 @@ const SmallList = css.SmallList; const Printer = css.Printer; const PrintErr = css.PrintErr; const Result = css.Result; +const VendorPrefix = css.VendorPrefix; +const PropertyId = css.css_properties.PropertyId; +const Property = css.css_properties.Property; const ContainerName = css.css_rules.container.ContainerName; @@ -86,6 +89,8 @@ pub const TransformList = struct { return dest.writeStr(base.items); } + + return this.toCssBase(W, dest); } fn toCssBase(this: *const @This(), comptime W: type, dest: *Printer(W)) PrintErr!void { @@ -97,6 +102,10 @@ pub const TransformList = struct { pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } }; /// An individual transform function (https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#two-d-transform-functions). @@ -109,6 +118,10 @@ pub const Transform = union(enum) { pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } }, /// A translation in the X direction. translate_x: LengthPercentage, @@ -125,6 +138,10 @@ pub const Transform = union(enum) { pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } }, /// A 2D scale. scale: struct { @@ -134,6 +151,10 @@ pub const Transform = union(enum) { pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } }, /// A scale in the X direction. scale_x: NumberOrPercentage, @@ -150,6 +171,10 @@ pub const Transform = union(enum) { pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } }, /// A 2D rotation. rotate: Angle, @@ -166,6 +191,10 @@ pub const Transform = union(enum) { z: f32, angle: Angle, + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } + pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } @@ -178,6 +207,10 @@ pub const Transform = union(enum) { pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } }, /// A skew along the X axis. skew_x: Angle, @@ -773,6 +806,10 @@ pub const Transform = union(enum) { pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } }; /// A 2D matrix. @@ -814,17 +851,43 @@ pub fn Matrix3d(comptime T: type) type { m42: T, m43: T, m44: T, + + pub fn eql(lhs: *const @This(), rhs: *const @This()) bool { + return css.implementEql(@This(), lhs, rhs); + } }; } /// A value for the [transform-style](https://drafts.csswg.org/css-transforms-2/#transform-style-property) property. -pub const TransformStyle = css.DefineEnumProperty(@compileError(css.todo_stuff.depth)); +pub const TransformStyle = enum { + flat, + @"preserve-3d", + pub usingnamespace css.DefineEnumProperty(@This()); +}; /// A value for the [transform-box](https://drafts.csswg.org/css-transforms-1/#transform-box) property. -pub const TransformBox = css.DefineEnumProperty(@compileError(css.todo_stuff.depth)); +pub const TransformBox = enum { + /// Uses the content box as reference box. + @"content-box", + /// Uses the border box as reference box. + @"border-box", + /// Uses the object bounding box as reference box. + @"fill-box", + /// Uses the stroke bounding box as reference box. + @"stroke-box", + /// Uses the nearest SVG viewport as reference box. + @"view-box", + + pub usingnamespace css.DefineEnumProperty(@This()); +}; /// A value for the [backface-visibility](https://drafts.csswg.org/css-transforms-2/#backface-visibility-property) property. -pub const BackfaceVisibility = css.DefineEnumProperty(@compileError(css.todo_stuff.depth)); +pub const BackfaceVisibility = enum { + visible, + hidden, + + pub usingnamespace css.DefineEnumProperty(@This()); +}; /// A value for the perspective property. pub const Perspective = union(enum) { @@ -832,6 +895,17 @@ pub const Perspective = union(enum) { none, /// Distance to the center of projection. length: Length, + + pub usingnamespace css.DeriveParse(@This()); + pub usingnamespace css.DeriveToCss(@This()); + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } + + pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { + return css.implementDeepClone(@This(), this, allocator); + } }; /// A value for the [translate](https://drafts.csswg.org/css-transforms-2/#propdef-translate) property. @@ -847,7 +921,77 @@ pub const Translate = union(enum) { y: LengthPercentage, /// The z translation. z: Length, + + pub fn __generateDeepClone() void {} + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } }, + + pub fn parse(input: *css.Parser) css.Result(@This()) { + if (input.tryParse(css.Parser.expectIdentMatching, .{"none"}).isOk()) { + return .{ .result = .none }; + } + + const x = switch (LengthPercentage.parse(input)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + const y = input.tryParse(LengthPercentage.parse, .{}); + const z = if (y.isOk()) input.tryParse(Length.parse, .{}).asValue() else null; + + return .{ + .result = Translate{ + .xyz = .{ + .x = x, + .y = y.unwrapOr(comptime LengthPercentage.zero()), + .z = z orelse Length.zero(), + }, + }, + }; + } + + pub fn toCss(this: *const @This(), comptime W: type, dest: *css.Printer(W)) PrintErr!void { + switch (this.*) { + .none => try dest.writeStr("none"), + .xyz => |xyz| { + try xyz.x.toCss(W, dest); + if (!xyz.y.isZero() or !xyz.z.isZero()) { + try dest.writeChar(' '); + try xyz.y.toCss(W, dest); + if (!xyz.z.isZero()) { + try dest.writeChar(' '); + try xyz.z.toCss(W, dest); + } + } + }, + } + return; + } + + pub fn toTransform(this: *const @This(), allocator: std.mem.Allocator) Transform { + return switch (this.*) { + .none => .{ .translate_3d = .{ + .x = LengthPercentage.zero(), + .y = LengthPercentage.zero(), + .z = Length.zero(), + } }, + .xyz => |xyz| .{ .translate_3d = .{ + .x = xyz.x.deepClone(allocator), + .y = xyz.y.deepClone(allocator), + .z = xyz.z.deepClone(allocator), + } }, + }; + } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } + + pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { + return css.implementDeepClone(@This(), this, allocator); + } }; /// A value for the [rotate](https://drafts.csswg.org/css-transforms-2/#propdef-rotate) property. @@ -860,6 +1004,112 @@ pub const Rotate = struct { z: f32, /// The angle of rotation. angle: Angle, + + pub fn parse(input: *css.Parser) css.Result(@This()) { + if (input.tryParse(css.Parser.expectIdentMatching, .{"none"}).isOk()) { + return .{ .result = .{ + .x = 0.0, + .y = 0.0, + .z = 1.0, + .angle = .{ .deg = 0.0 }, + } }; + } + + const angle = input.tryParse(Angle.parse, .{}); + + const XYZ = struct { x: f32, y: f32, z: f32 }; + const xyz = switch (input.tryParse(struct { + fn parse(i: *css.Parser) css.Result(XYZ) { + const location = i.currentSourceLocation(); + const ident = switch (i.expectIdent()) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "x")) { + return .{ .result = .{ .x = 1.0, .y = 0.0, .z = 0.0 } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "y")) { + return .{ .result = .{ .x = 0.0, .y = 1.0, .z = 0.0 } }; + } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "z")) { + return .{ .result = .{ .x = 0.0, .y = 0.0, .z = 1.0 } }; + } + return .{ .err = location.newUnexpectedTokenError(.{ .ident = ident }) }; + } + }.parse, .{})) { + .result => |v| v, + .err => input.tryParse(struct { + fn parse(i: *css.Parser) css.Result(XYZ) { + const x = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + const y = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + const z = switch (css.CSSNumberFns.parse(i)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + return .{ .result = .{ .x = x, .y = y, .z = z } }; + } + }.parse, .{}).unwrapOr(.{ .x = 0.0, .y = 0.0, .z = 1.0 }), + }; + + const final_angle = switch (angle) { + .result => |v| v, + .err => switch (Angle.parse(input)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }, + }; + + return .{ .result = .{ + .x = xyz.x, + .y = xyz.y, + .z = xyz.z, + .angle = final_angle, + } }; + } + + pub fn toCss(this: *const @This(), comptime W: type, dest: *css.Printer(W)) PrintErr!void { + if (this.x == 0.0 and this.y == 0.0 and this.z == 1.0 and this.angle.isZero()) { + try dest.writeStr("none"); + return; + } + + if (this.x == 1.0 and this.y == 0.0 and this.z == 0.0) { + try dest.writeStr("x "); + } else if (this.x == 0.0 and this.y == 1.0 and this.z == 0.0) { + try dest.writeStr("y "); + } else if (!(this.x == 0.0 and this.y == 0.0 and this.z == 1.0)) { + try css.CSSNumberFns.toCss(&this.x, W, dest); + try dest.writeChar(' '); + try css.CSSNumberFns.toCss(&this.y, W, dest); + try dest.writeChar(' '); + try css.CSSNumberFns.toCss(&this.z, W, dest); + try dest.writeChar(' '); + } + + try this.angle.toCss(W, dest); + } + + /// Converts the rotation to a transform function. + pub fn toTransform(this: *const @This(), allocator: std.mem.Allocator) Transform { + return .{ .rotate_3d = .{ + .x = this.x, + .y = this.y, + .z = this.z, + .angle = this.angle.deepClone(allocator), + } }; + } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } + + pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { + return css.implementDeepClone(@This(), this, allocator); + } }; /// A value for the [scale](https://drafts.csswg.org/css-transforms-2/#propdef-scale) property. @@ -875,5 +1125,178 @@ pub const Scale = union(enum) { y: NumberOrPercentage, /// Scale on the z axis. z: NumberOrPercentage, + + pub fn __generateDeepClone() void {} + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } }, + + pub fn parse(input: *css.Parser) css.Result(@This()) { + if (input.tryParse(css.Parser.expectIdentMatching, .{"none"}).isOk()) { + return .{ .result = .none }; + } + + const x = switch (NumberOrPercentage.parse(input)) { + .result => |v| v, + .err => |e| return .{ .err = e }, + }; + + const y = input.tryParse(NumberOrPercentage.parse, .{}); + const z = if (y.isOk()) input.tryParse(NumberOrPercentage.parse, .{}).asValue() else null; + + return .{ .result = .{ .xyz = .{ + .x = x, + .y = if (y.asValue()) |val| val else x, + .z = if (z) |val| val else .{ .number = 1.0 }, + } } }; + } + + pub fn toCss(this: *const @This(), comptime W: type, dest: *css.Printer(W)) PrintErr!void { + switch (this.*) { + .none => try dest.writeStr("none"), + .xyz => |xyz| { + try xyz.x.toCss(W, dest); + const z_val = xyz.z.intoF32(); + if (!xyz.y.eql(&xyz.x) or z_val != 1.0) { + try dest.writeChar(' '); + try xyz.y.toCss(W, dest); + if (z_val != 1.0) { + try dest.writeChar(' '); + try xyz.z.toCss(W, dest); + } + } + }, + } + } + + pub fn toTransform(this: *const @This(), allocator: std.mem.Allocator) Transform { + return switch (this.*) { + .none => .{ .scale_3d = .{ + .x = .{ .number = 1.0 }, + .y = .{ .number = 1.0 }, + .z = .{ .number = 1.0 }, + } }, + .xyz => |xyz| .{ .scale_3d = .{ + .x = xyz.x.deepClone(allocator), + .y = xyz.y.deepClone(allocator), + .z = xyz.z.deepClone(allocator), + } }, + }; + } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } + + pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { + return css.implementDeepClone(@This(), this, allocator); + } +}; + +pub const TransformHandler = struct { + transform: ?struct { TransformList, VendorPrefix } = null, + translate: ?Translate = null, + rotate: ?Rotate = null, + scale: ?Scale = null, + has_any: bool = false, + + pub fn handleProperty( + this: *@This(), + property: *const css.Property, + dest: *css.DeclarationList, + context: *css.PropertyHandlerContext, + ) bool { + const individualProperty = struct { + fn individualProperty(self: *TransformHandler, allocator: std.mem.Allocator, comptime field: []const u8, val: anytype) void { + if (self.transform) |*transform| { + transform.*[0].v.append(allocator, val.toTransform(allocator)) catch bun.outOfMemory(); + } else { + @field(self, field) = val.deepClone(allocator); + self.has_any = true; + } + } + }.individualProperty; + const allocator = context.allocator; + + switch (property.*) { + .transform => |val| { + const transform_val = val[0]; + const vp = val[1]; + + // If two vendor prefixes for the same property have different + // values, we need to flush what we have immediately to preserve order. + if (this.transform) |current| { + if (!current[0].eql(&transform_val) and !current[1].contains(vp)) { + this.flush(allocator, dest, context); + } + } + + // Otherwise, update the value and add the prefix. + if (this.transform) |*transform| { + transform.* = .{ transform_val.deepClone(allocator), transform.*[1].bitwiseOr(vp) }; + } else { + this.transform = .{ transform_val.deepClone(allocator), vp }; + this.has_any = true; + } + + this.translate = null; + this.rotate = null; + this.scale = null; + }, + .translate => |val| individualProperty(this, allocator, "translate", val), + .rotate => |val| individualProperty(this, allocator, "rotate", val), + .scale => |val| individualProperty(this, allocator, "scale", val), + .unparsed => |unparsed| { + if (unparsed.property_id == .transform or + unparsed.property_id == .translate or + unparsed.property_id == .rotate or + unparsed.property_id == .scale) + { + this.flush(allocator, dest, context); + const prop = if (unparsed.property_id == .transform) + Property{ .unparsed = unparsed.getPrefixed(allocator, context.targets, css.prefixes.Feature.transform) } + else + property.deepClone(allocator); + dest.append(allocator, prop) catch bun.outOfMemory(); + } else return false; + }, + else => return false, + } + + return true; + } + + pub fn finalize(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext) void { + this.flush(context.allocator, dest, context); + } + + fn flush(this: *@This(), allocator: std.mem.Allocator, dest: *css.DeclarationList, context: *css.PropertyHandlerContext) void { + if (!this.has_any) return; + + this.has_any = false; + + const transform = bun.take(&this.transform); + const translate = bun.take(&this.translate); + const rotate = bun.take(&this.rotate); + const scale = bun.take(&this.scale); + + if (transform) |t| { + const prefix = context.targets.prefixes(t[1], css.prefixes.Feature.transform); + dest.append(allocator, Property{ .transform = .{ t[0], prefix } }) catch bun.outOfMemory(); + } + + if (translate) |t| { + dest.append(allocator, Property{ .translate = t }) catch bun.outOfMemory(); + } + + if (rotate) |r| { + dest.append(allocator, Property{ .rotate = r }) catch bun.outOfMemory(); + } + + if (scale) |s| { + dest.append(allocator, Property{ .scale = s }) catch bun.outOfMemory(); + } + } }; diff --git a/src/css/properties/transition.zig b/src/css/properties/transition.zig index a44aa5cc11..4c7d5b5c31 100644 --- a/src/css/properties/transition.zig +++ b/src/css/properties/transition.zig @@ -33,5 +33,515 @@ const Percentage = css.css_values.percentage.Percentage; const GenericBorder = css.css_properties.border.GenericBorder; const LineStyle = css.css_properties.border.LineStyle; +const Property = css.css_properties.Property; +const PropertyId = css.css_properties.PropertyId; +const Time = css.css_values.time.Time; +const EasingFunction = css.css_values.easing.EasingFunction; + +const VendorPrefix = css.VendorPrefix; +const Feature = css.prefixes.Feature; + /// A value for the [transition](https://www.w3.org/TR/2018/WD-css-transitions-1-20181011/#transition-shorthand-property) property. -pub const Transition = @compileError(css.todo_stuff.depth); +pub const Transition = struct { + /// The property to transition. + property: PropertyId, + /// The duration of the transition. + duration: Time, + /// The delay before the transition starts. + delay: Time, + /// The easing function for the transition. + timing_function: EasingFunction, + + pub usingnamespace css.DefineShorthand(@This(), css.PropertyIdTag.transition); + pub usingnamespace css.DefineListShorthand(@This()); + + pub const PropertyFieldMap = .{ + .property = css.PropertyIdTag.@"transition-property", + .duration = css.PropertyIdTag.@"transition-duration", + .delay = css.PropertyIdTag.@"transition-delay", + .timing_function = css.PropertyIdTag.@"transition-timing-function", + }; + + pub fn eql(lhs: *const @This(), rhs: *const @This()) bool { + return css.implementEql(@This(), lhs, rhs); + } + + pub fn deepClone(this: *const @This(), allocator: Allocator) @This() { + return css.implementDeepClone(comptime @This(), this, allocator); + } + + pub fn parse(parser: *css.Parser) css.Result(@This()) { + var property: ?PropertyId = null; + var duration: ?Time = null; + var delay: ?Time = null; + var timing_function: ?EasingFunction = null; + + while (true) { + if (duration == null) { + if (parser.tryParse(Time.parse, .{}).asValue()) |value| { + duration = value; + continue; + } + } + + if (timing_function == null) { + if (parser.tryParse(EasingFunction.parse, .{}).asValue()) |value| { + timing_function = value; + continue; + } + } + + if (delay == null) { + if (parser.tryParse(Time.parse, .{}).asValue()) |value| { + delay = value; + continue; + } + } + + if (property == null) { + if (parser.tryParse(PropertyId.parse, .{}).asValue()) |value| { + property = value; + continue; + } + } + + break; + } + + return .{ .result = .{ + .property = property orelse .all, + .duration = duration orelse .{ .seconds = 0.0 }, + .delay = delay orelse .{ .seconds = 0.0 }, + .timing_function = timing_function orelse .ease, + } }; + } + + pub fn toCss(this: *const @This(), comptime W: type, dest: *Printer(W)) PrintErr!void { + try this.property.toCss(W, dest); + if (!this.duration.isZero() or !this.delay.isZero()) { + try dest.writeChar(' '); + try this.duration.toCss(W, dest); + } + + if (!this.timing_function.isEase()) { + try dest.writeChar(' '); + try this.timing_function.toCss(W, dest); + } + + if (!this.delay.isZero()) { + try dest.writeChar(' '); + try this.delay.toCss(W, dest); + } + } +}; + +pub const TransitionHandler = struct { + properties: ?struct { SmallList(PropertyId, 1), VendorPrefix } = null, + durations: ?struct { SmallList(Time, 1), VendorPrefix } = null, + delays: ?struct { SmallList(Time, 1), VendorPrefix } = null, + timing_functions: ?struct { SmallList(EasingFunction, 1), VendorPrefix } = null, + has_any: bool = false, + + pub fn handleProperty(this: *@This(), prop: *const Property, dest: *css.DeclarationList, context: *css.PropertyHandlerContext) bool { + switch (prop.*) { + .@"transition-property" => |*x| this.property(dest, context, Feature.transition_property, "properties", &x.*[0], x.*[1]), + .@"transition-duration" => |*x| this.property(dest, context, Feature.transition_duration, "durations", &x.*[0], x.*[1]), + .@"transition-delay" => |*x| this.property(dest, context, Feature.transition_delay, "delays", &x.*[0], x.*[1]), + .@"transition-timing-function" => |*x| this.property(dest, context, Feature.transition_timing_function, "timing_functions", &x.*[0], x.*[1]), + .transition => |*x| { + const val: *const SmallList(Transition, 1) = &x.*[0]; + const vp: VendorPrefix = x.*[1]; + + var properties = SmallList(PropertyId, 1).initCapacity(context.allocator, val.len()); + var durations = SmallList(Time, 1).initCapacity(context.allocator, val.len()); + var delays = SmallList(Time, 1).initCapacity(context.allocator, val.len()); + var timing_functions = SmallList(EasingFunction, 1).initCapacity(context.allocator, val.len()); + properties.setLen(val.len()); + durations.setLen(val.len()); + delays.setLen(val.len()); + timing_functions.setLen(val.len()); + + for (val.slice(), properties.slice_mut()) |*item, *out_prop| { + out_prop.* = item.property.deepClone(context.allocator); + } + this.maybeFlush(dest, context, "properties", &properties, vp); + + for (val.slice(), durations.slice_mut()) |*item, *out_dur| { + out_dur.* = item.duration.deepClone(context.allocator); + } + this.maybeFlush(dest, context, "durations", &durations, vp); + + for (val.slice(), delays.slice_mut()) |*item, *out_delay| { + out_delay.* = item.delay.deepClone(context.allocator); + } + this.maybeFlush(dest, context, "delays", &delays, vp); + + for (val.slice(), timing_functions.slice_mut()) |*item, *out_timing| { + out_timing.* = item.timing_function.deepClone(context.allocator); + } + this.maybeFlush(dest, context, "timing_functions", &timing_functions, vp); + + this.property(dest, context, Feature.transition_property, "properties", &properties, vp); + this.property(dest, context, Feature.transition_duration, "durations", &durations, vp); + this.property(dest, context, Feature.transition_delay, "delays", &delays, vp); + this.property(dest, context, Feature.transition_timing_function, "timing_functions", &timing_functions, vp); + }, + .unparsed => |*x| if (isTransitionProperty(&x.property_id)) { + this.flush(dest, context); + dest.append( + context.allocator, + .{ .unparsed = x.getPrefixed(context.allocator, context.targets, Feature.transition) }, + ) catch bun.outOfMemory(); + } else return false, + else => return false, + } + + return true; + } + + pub fn finalize(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext) void { + this.flush(dest, context); + } + + fn property(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext, comptime feature: Feature, comptime prop: []const u8, val: anytype, vp: VendorPrefix) void { + this.maybeFlush(dest, context, prop, val, vp); + + // Otherwise, update the value and add the prefix. + if (@field(this, prop)) |*p| { + const v = &p.*[0]; + const prefixes = &p.*[1]; + v.* = val.deepClone(context.allocator); + prefixes.insert(vp); + prefixes.* = context.targets.prefixes(prefixes.*, feature); + } else { + const prefixes = context.targets.prefixes(vp, feature); + const cloned_val = val.deepClone(context.allocator); + @field(this, prop) = .{ cloned_val, prefixes }; + this.has_any = true; + } + } + + fn maybeFlush(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext, comptime prop: []const u8, val: anytype, vp: VendorPrefix) void { + // If two vendor prefixes for the same property have different + // values, we need to flush what we have immediately to preserve order. + if (@field(this, prop)) |*p| { + const v = &p.*[0]; + const prefixes = &p.*[1]; + if (!val.eql(v) and !prefixes.contains(vp)) { + this.flush(dest, context); + } + } + } + + fn flush(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext) void { + if (!this.has_any) return; + this.has_any = false; + + var _properties: ?struct { SmallList(PropertyId, 1), VendorPrefix } = bun.take(&this.properties); + var _durations: ?struct { SmallList(Time, 1), VendorPrefix } = bun.take(&this.durations); + var _delays: ?struct { SmallList(Time, 1), VendorPrefix } = bun.take(&this.delays); + var _timing_functions: ?struct { SmallList(EasingFunction, 1), VendorPrefix } = bun.take(&this.timing_functions); + + var rtl_properties: ?SmallList(PropertyId, 1) = if (_properties) |*p| expandProperties(&p.*[0], context) else null; + + if (_properties != null and _durations != null and _delays != null and _timing_functions != null) { + const properties: *SmallList(PropertyId, 1) = &_properties.?[0]; + const property_prefixes: *VendorPrefix = &_properties.?[1]; + const durations: *SmallList(Time, 1) = &_durations.?[0]; + const duration_prefixes: *VendorPrefix = &_durations.?[1]; + const delays: *SmallList(Time, 1) = &_delays.?[0]; + const delay_prefixes: *VendorPrefix = &_delays.?[1]; + const timing_functions: *SmallList(EasingFunction, 1) = &_timing_functions.?[0]; + const timing_prefixes: *VendorPrefix = &_timing_functions.?[1]; + + // Find the intersection of prefixes with the same value. + // Remove that from the prefixes of each of the properties. The remaining + // prefixes will be handled by outputting individual properties below. + const intersection = property_prefixes.bitwiseAnd(duration_prefixes.*).bitwiseAnd(delay_prefixes.*).bitwiseAnd(timing_prefixes.*); + if (!intersection.isEmpty()) { + const transitions = getTransitions(context, properties, durations, delays, timing_functions); + + if (rtl_properties) |*rtl_properties2| { + const rtl_transitions = getTransitions(context, rtl_properties2, durations, delays, timing_functions); + context.addLogicalRule( + context.allocator, + Property{ + .transition = .{ transitions, intersection }, + }, + Property{ + .transition = .{ rtl_transitions, intersection }, + }, + ); + } else { + dest.append( + context.allocator, + Property{ .transition = .{ transitions.deepClone(context.allocator), intersection } }, + ) catch bun.outOfMemory(); + } + + property_prefixes.remove(intersection); + duration_prefixes.remove(intersection); + delay_prefixes.remove(intersection); + timing_prefixes.remove(intersection); + } + } + + if (_properties != null) { + const properties: SmallList(PropertyId, 1) = _properties.?[0]; + const prefix: VendorPrefix = _properties.?[1]; + _properties = null; + if (!prefix.isEmpty()) { + if (rtl_properties) |rtl_properties2| { + context.addLogicalRule( + context.allocator, + Property{ .@"transition-property" = .{ properties, prefix } }, + Property{ .@"transition-property" = .{ rtl_properties2, prefix } }, + ); + rtl_properties = null; + } else { + dest.append(context.allocator, Property{ .@"transition-property" = .{ properties, prefix } }) catch bun.outOfMemory(); + } + } + } + + if (_durations != null) { + const durations: SmallList(Time, 1) = _durations.?[0]; + const prefix: VendorPrefix = _durations.?[1]; + _durations = null; + if (!prefix.isEmpty()) { + dest.append(context.allocator, Property{ .@"transition-duration" = .{ durations, prefix } }) catch bun.outOfMemory(); + } + } + + if (_delays != null) { + const delays: SmallList(Time, 1) = _delays.?[0]; + const prefix: VendorPrefix = _delays.?[1]; + _delays = null; + if (!prefix.isEmpty()) { + dest.append(context.allocator, Property{ .@"transition-delay" = .{ delays, prefix } }) catch bun.outOfMemory(); + } + } + + if (_timing_functions != null) { + const timing_functions: SmallList(EasingFunction, 1) = _timing_functions.?[0]; + const prefix: VendorPrefix = _timing_functions.?[1]; + _timing_functions = null; + if (!prefix.isEmpty()) { + dest.append(context.allocator, Property{ .@"transition-timing-function" = .{ timing_functions, prefix } }) catch bun.outOfMemory(); + } + } + + this.reset(); + } + + inline fn getTransitions( + context: *const css.PropertyHandlerContext, + properties: *SmallList(PropertyId, 1), + durations: *SmallList(Time, 1), + delays: *SmallList(Time, 1), + timing_functions: *SmallList(EasingFunction, 1), + ) SmallList(Transition, 1) { + const cycleBump = struct { + inline fn cycleBump(idx: *u32, len: u32) void { + idx.* = (idx.* + 1) % len; + } + }.cycleBump; + + // transition-property determines the number of transitions. The values of other + // properties are repeated to match this length. + var transitions = SmallList(Transition, 1).initCapacity(context.allocator, 1); + var durations_idx: u32 = 0; + var delays_idx: u32 = 0; + var timing_idx: u32 = 0; + for (properties.slice()) |*property_id| { + const duration = if (durations.len() > durations_idx) durations.at(durations_idx).deepClone(context.allocator) else Time{ .seconds = 0.0 }; + const delay = if (delays.len() > delays_idx) delays.at(delays_idx).deepClone(context.allocator) else Time{ .seconds = 0.0 }; + const timing_function = if (timing_functions.len() > timing_idx) timing_functions.at(timing_idx).deepClone(context.allocator) else EasingFunction.ease; + cycleBump(&durations_idx, durations.len()); + cycleBump(&delays_idx, delays.len()); + cycleBump(&timing_idx, timing_functions.len()); + const transition = Transition{ + .property = property_id.deepClone(context.allocator), + .duration = duration, + .delay = delay, + .timing_function = timing_function, + }; + var cloned = false; + + const prefix_to_iter = property_id.prefix().orNone(); + // Expand vendor prefixes into multiple transitions. + inline for (VendorPrefix.FIELDS) |prefix_field| { + if (@field(prefix_to_iter, prefix_field)) { + var t = if (cloned) transition.deepClone(context.allocator) else transition; + cloned = true; + var new_prefix = VendorPrefix{}; + @field(new_prefix, prefix_field) = true; + t.property = property_id.withPrefix(new_prefix); + transitions.append(context.allocator, t); + } + } + } + return transitions; + } + + pub fn reset(this: *@This()) void { + this.properties = null; + this.durations = null; + this.delays = null; + this.timing_functions = null; + this.has_any = false; + } +}; + +fn expandProperties(properties: *css.SmallList(PropertyId, 1), context: *css.PropertyHandlerContext) ?SmallList(PropertyId, 1) { + const replace = struct { + inline fn replace(allocator: Allocator, propertiez: anytype, props: []const PropertyId, i: u32) void { + propertiez.mut(i).* = props[0].deepClone(allocator); + if (props.len > 1) { + propertiez.insertSlice(allocator, i + 1, props[1..]); + } + } + }.replace; + + var rtl_properties: ?SmallList(PropertyId, 1) = null; + var i: u32 = 0; + + // Expand logical properties in place. + while (i < properties.len()) { + const result = getLogicalProperties(properties.at(i)); + if (result == .block and context.shouldCompileLogical(result.block[0])) { + replace(context.allocator, properties, result.block[1], i); + if (rtl_properties) |*rtl| { + replace(context.allocator, rtl, result.block[1], i); + } + i += 1; + } else if (result == .@"inline" and context.shouldCompileLogical(result.@"inline"[0])) { + const ltr = result.@"inline"[1]; + const rtl = result.@"inline"[2]; + // Clone properties to create RTL version only when needed. + if (rtl_properties == null) { + rtl_properties = properties.deepClone(context.allocator); + } + + replace(context.allocator, properties, ltr, i); + if (rtl_properties) |*rtl_props| { + replace(context.allocator, rtl_props, rtl, i); + } + + i += @intCast(ltr.len); + } else { + // Expand vendor prefixes for targets. + properties.mut(i).setPrefixesForTargets(context.targets); + + // Expand mask properties, which use different vendor-prefixed names. + if (css.css_properties.masking.getWebkitMaskProperty(properties.at(i))) |property_id| { + if (context.targets.prefixes(VendorPrefix.NONE, Feature.mask_border).contains(VendorPrefix.WEBKIT)) { + properties.insert(context.allocator, i, property_id); + i += 1; + } + } + + if (rtl_properties) |*rtl_props| { + rtl_props.mut(i).setPrefixesForTargets(context.targets); + + if (css.css_properties.masking.getWebkitMaskProperty(rtl_props.at(i))) |property_id| { + if (context.targets.prefixes(VendorPrefix.NONE, Feature.mask_border).contains(VendorPrefix.WEBKIT)) { + rtl_props.insert(context.allocator, i, property_id); + i += 1; + } + } + } + i += 1; + } + } + + return rtl_properties; +} + +const LogicalPropertyId = union(enum) { + none, + block: struct { css.compat.Feature, []const PropertyId }, + @"inline": struct { css.compat.Feature, []const PropertyId, []const PropertyId }, +}; + +fn getLogicalProperties(property_id: *const PropertyId) LogicalPropertyId { + return switch (property_id.*) { + .@"block-size" => .{ .block = .{ .logical_size, &[_]PropertyId{.height} } }, + .@"inline-size" => .{ .@"inline" = .{ .logical_size, &[_]PropertyId{.width}, &[_]PropertyId{.height} } }, + .@"min-block-size" => .{ .block = .{ .logical_size, &[_]PropertyId{.@"min-height"} } }, + .@"max-block-size" => .{ .block = .{ .logical_size, &[_]PropertyId{.@"max-height"} } }, + .@"min-inline-size" => .{ .@"inline" = .{ .logical_size, &[_]PropertyId{.@"min-width"}, &[_]PropertyId{.@"min-height"} } }, + .@"max-inline-size" => .{ .@"inline" = .{ .logical_size, &[_]PropertyId{.@"max-width"}, &[_]PropertyId{.@"max-height"} } }, + + .@"inset-block-start" => .{ .block = .{ .logical_inset, &[_]PropertyId{.top} } }, + .@"inset-block-end" => .{ .block = .{ .logical_inset, &[_]PropertyId{.bottom} } }, + .@"inset-inline-start" => .{ .@"inline" = .{ .logical_inset, &[_]PropertyId{.left}, &[_]PropertyId{.right} } }, + .@"inset-inline-end" => .{ .@"inline" = .{ .logical_inset, &[_]PropertyId{.right}, &[_]PropertyId{.left} } }, + .@"inset-block" => .{ .block = .{ .logical_inset, &[_]PropertyId{ .top, .bottom } } }, + .@"inset-inline" => .{ .block = .{ .logical_inset, &[_]PropertyId{ .left, .right } } }, + .inset => .{ .block = .{ .logical_inset, &[_]PropertyId{ .top, .bottom, .left, .right } } }, + + .@"margin-block-start" => .{ .block = .{ .logical_margin, &[_]PropertyId{.@"margin-top"} } }, + .@"margin-block-end" => .{ .block = .{ .logical_margin, &[_]PropertyId{.@"margin-bottom"} } }, + .@"margin-inline-start" => .{ .@"inline" = .{ .logical_margin, &[_]PropertyId{.@"margin-left"}, &[_]PropertyId{.@"margin-right"} } }, + .@"margin-inline-end" => .{ .@"inline" = .{ .logical_margin, &[_]PropertyId{.@"margin-right"}, &[_]PropertyId{.@"margin-left"} } }, + .@"margin-block" => .{ .block = .{ .logical_margin, &[_]PropertyId{ .@"margin-top", .@"margin-bottom" } } }, + .@"margin-inline" => .{ .block = .{ .logical_margin, &[_]PropertyId{ .@"margin-left", .@"margin-right" } } }, + + .@"padding-block-start" => .{ .block = .{ .logical_padding, &[_]PropertyId{.@"padding-top"} } }, + .@"padding-block-end" => .{ .block = .{ .logical_padding, &[_]PropertyId{.@"padding-bottom"} } }, + .@"padding-inline-start" => .{ .@"inline" = .{ .logical_padding, &[_]PropertyId{.@"padding-left"}, &[_]PropertyId{.@"padding-right"} } }, + .@"padding-inline-end" => .{ .@"inline" = .{ .logical_padding, &[_]PropertyId{.@"padding-right"}, &[_]PropertyId{.@"padding-left"} } }, + .@"padding-block" => .{ .block = .{ .logical_padding, &[_]PropertyId{ .@"padding-top", .@"padding-bottom" } } }, + .@"padding-inline" => .{ .block = .{ .logical_padding, &[_]PropertyId{ .@"padding-left", .@"padding-right" } } }, + + .@"border-block-start" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-top"} } }, + .@"border-block-start-width" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-top-width"} } }, + .@"border-block-start-color" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-top-color"} } }, + .@"border-block-start-style" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-top-style"} } }, + + .@"border-block-end" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-bottom"} } }, + .@"border-block-end-width" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-bottom-width"} } }, + .@"border-block-end-color" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-bottom-color"} } }, + .@"border-block-end-style" => .{ .block = .{ .logical_borders, &[_]PropertyId{.@"border-bottom-style"} } }, + + .@"border-inline-start" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-left"}, &[_]PropertyId{.@"border-right"} } }, + .@"border-inline-start-width" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-left-width"}, &[_]PropertyId{.@"border-right-width"} } }, + .@"border-inline-start-color" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-left-color"}, &[_]PropertyId{.@"border-right-color"} } }, + .@"border-inline-start-style" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-left-style"}, &[_]PropertyId{.@"border-right-style"} } }, + + .@"border-inline-end" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-right"}, &[_]PropertyId{.@"border-left"} } }, + .@"border-inline-end-width" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-right-width"}, &[_]PropertyId{.@"border-left-width"} } }, + .@"border-inline-end-color" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-right-color"}, &[_]PropertyId{.@"border-left-color"} } }, + .@"border-inline-end-style" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{.@"border-right-style"}, &[_]PropertyId{.@"border-left-style"} } }, + + .@"border-block" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-top", .@"border-bottom" } } }, + .@"border-block-color" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-top-color", .@"border-bottom-color" } } }, + .@"border-block-width" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-top-width", .@"border-bottom-width" } } }, + .@"border-block-style" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-top-style", .@"border-bottom-style" } } }, + + .@"border-inline" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-left", .@"border-right" } } }, + .@"border-inline-color" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-left-color", .@"border-right-color" } } }, + .@"border-inline-width" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-left-width", .@"border-right-width" } } }, + .@"border-inline-style" => .{ .block = .{ .logical_borders, &[_]PropertyId{ .@"border-left-style", .@"border-right-style" } } }, + + .@"border-start-start-radius" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{PropertyId{ .@"border-top-left-radius" = VendorPrefix.NONE }}, &[_]PropertyId{PropertyId{ .@"border-top-right-radius" = VendorPrefix.NONE }} } }, + .@"border-start-end-radius" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{PropertyId{ .@"border-top-right-radius" = VendorPrefix.NONE }}, &[_]PropertyId{PropertyId{ .@"border-top-left-radius" = VendorPrefix.NONE }} } }, + .@"border-end-start-radius" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{PropertyId{ .@"border-bottom-left-radius" = VendorPrefix.NONE }}, &[_]PropertyId{PropertyId{ .@"border-bottom-right-radius" = VendorPrefix.NONE }} } }, + .@"border-end-end-radius" => .{ .@"inline" = .{ .logical_borders, &[_]PropertyId{PropertyId{ .@"border-bottom-right-radius" = VendorPrefix.NONE }}, &[_]PropertyId{PropertyId{ .@"border-bottom-left-radius" = VendorPrefix.NONE }} } }, + + else => .none, + }; +} + +fn isTransitionProperty(property_id: *const PropertyId) bool { + return switch (property_id.*) { + .@"transition-property", + .@"transition-duration", + .@"transition-delay", + .@"transition-timing-function", + .transition, + => true, + else => false, + }; +} diff --git a/src/css/selectors/parser.zig b/src/css/selectors/parser.zig index 1a7fd7b7bc..88a811a4b9 100644 --- a/src/css/selectors/parser.zig +++ b/src/css/selectors/parser.zig @@ -2250,7 +2250,7 @@ pub const SpecifityAndFlags = struct { flags: SelectorFlags, pub fn eql(this: *const SpecifityAndFlags, other: *const SpecifityAndFlags) bool { - return this.specificity == other.specificity and this.flags.eql(other.flags); + return css.implementEql(@This(), this, other); } pub fn hasPseudoElement(this: *const SpecifityAndFlags) bool { diff --git a/src/css/small_list.zig b/src/css/small_list.zig index cf05e490a3..d2749f7913 100644 --- a/src/css/small_list.zig +++ b/src/css/small_list.zig @@ -404,10 +404,10 @@ pub fn SmallList(comptime T: type, comptime N: comptime_int) type { } pub fn deepClone(this: *const @This(), allocator: Allocator) @This() { - var ret: @This() = .{}; - ret.appendSlice(allocator, this.slice()); - for (ret.slice_mut()) |*item| { - item.* = generic.deepClone(T, item, allocator); + var ret: @This() = initCapacity(allocator, this.len()); + ret.setLen(this.len()); + for (this.slice(), ret.slice_mut()) |*in, *out| { + out.* = generic.deepClone(T, in, allocator); } return ret; } diff --git a/src/css/targets.zig b/src/css/targets.zig index f429e6f2c0..97c2dda099 100644 --- a/src/css/targets.zig +++ b/src/css/targets.zig @@ -31,7 +31,58 @@ pub const Targets = struct { }; } + fn parseDebugTarget(val_: []const u8) ?u32 { + const val = bun.strings.trim(val_, " \n\r\t"); + if (val.len == 0) return null; + if (bun.strings.eqlCaseInsensitiveASCII(val, "null", true)) return null; + + var lhs: u32 = 0; + var rhs: u32 = 0; + + var i: usize = 0; + for (val, 0..) |c, j| { + if (!std.ascii.isDigit(c)) { + i = j; + lhs = std.fmt.parseInt(u32, val[0..j], 10) catch @panic("invalid bytes"); + break; + } + } + if (i >= val.len) { + lhs = std.fmt.parseInt(u32, val, 10) catch @panic("invalid bytes"); + return lhs; + } + if (val[i] != ' ') { + @panic("bad string"); + } + i += 1; + if (val[i] != '<' or i + 1 >= val.len or val[i + 1] != '<') { + @panic("bad string"); + } + i += 2; + if (val[i] != ' ') { + @panic("bad string"); + } + i += 1; + rhs = std.fmt.parseInt(u32, val[i..], 10) catch @panic("invalid bytes"); + return lhs << @intCast(rhs); + } + pub fn forBundlerTarget(target: bun.transpiler.options.Target) Targets { + if (comptime bun.Environment.isDebug) { + var browsers: Browsers = .{}; + const browser_fields = std.meta.fields(Browsers); + var has_any = false; + inline for (browser_fields) |field| { + const env_var = "BUN_DEBUG_CSS_TARGET_" ++ field.name; + if (bun.getenvZAnyCase(env_var)) |val| { + @field(browsers, field.name) = parseDebugTarget(val); + has_any = true; + } + } + if (has_any) { + return .{ .browsers = browsers }; + } + } return switch (target) { .node, .bun => runtimeDefault(), .browser, .bun_macro, .bake_server_components_ssr => browserDefault(), @@ -135,7 +186,6 @@ pub const Features = packed struct(u32) { vendor_prefixes: bool = false, logical_properties: bool = false, __unused: u12 = 0, - pub const selectors = Features.fromNames(&.{ "nesting", "not_selector_list", "dir_selector", "lang_selector_list", "is_selector" }); pub const media_queries = Features.fromNames(&.{ "media_interval_syntax", "media_range_syntax", "custom_media_queries" }); pub const colors = Features.fromNames(&.{ "color_function", "oklab_colors", "lab_colors", "p3_colors", "hex_alpha_colors", "space_separated_color_notation" }); diff --git a/src/css/values/angle.zig b/src/css/values/angle.zig index ee207c553a..4649960715 100644 --- a/src/css/values/angle.zig +++ b/src/css/values/angle.zig @@ -183,6 +183,10 @@ pub const Angle = union(Tag) { return map(this, opfn); } + pub fn addInternal(this: Angle, _: std.mem.Allocator, other: Angle) Angle { + return this.add(other); + } + pub fn add(this: Angle, rhs: Angle) Angle { const addfn = struct { pub fn add(_: void, a: f32, b: f32) f32 { @@ -240,19 +244,19 @@ pub const Angle = union(Tag) { comptime op_fn: *const fn (@TypeOf(ctx), a: f32, b: f32) f32, ) Angle { // PERF: not sure if this is faster - const self_tag: u8 = @intFromEnum(this.*); - const other_tag: u8 = @intFromEnum(this.*); - const DEG: u8 = @intFromEnum(Tag.deg); - const GRAD: u8 = @intFromEnum(Tag.grad); - const RAD: u8 = @intFromEnum(Tag.rad); - const TURN: u8 = @intFromEnum(Tag.turn); + const self_tag: u16 = @intFromEnum(this.*); + const other_tag: u16 = @intFromEnum(other.*); + const DEG: u16 = @intFromEnum(Tag.deg); + const GRAD: u16 = @intFromEnum(Tag.grad); + const RAD: u16 = @intFromEnum(Tag.rad); + const TURN: u16 = @intFromEnum(Tag.turn); - const switch_val: u8 = self_tag | other_tag; + const switch_val: u16 = self_tag | (other_tag << 8); return switch (switch_val) { - DEG | DEG => Angle{ .deg = op_fn(ctx, this.deg, other.deg) }, - RAD | RAD => Angle{ .rad = op_fn(ctx, this.rad, other.rad) }, - GRAD | GRAD => Angle{ .grad = op_fn(ctx, this.grad, other.grad) }, - TURN | TURN => Angle{ .turn = op_fn(ctx, this.turn, other.turn) }, + DEG | (DEG << 8) => Angle{ .deg = op_fn(ctx, this.deg, other.deg) }, + RAD | (RAD << 8) => Angle{ .rad = op_fn(ctx, this.rad, other.rad) }, + GRAD | (GRAD << 8) => Angle{ .grad = op_fn(ctx, this.grad, other.grad) }, + TURN | (TURN << 8) => Angle{ .turn = op_fn(ctx, this.turn, other.turn) }, else => Angle{ .deg = op_fn(ctx, this.toDegrees(), other.toDegrees()) }, }; } diff --git a/src/css/values/calc.zig b/src/css/values/calc.zig index cf4d213738..a86ee0d9fc 100644 --- a/src/css/values/calc.zig +++ b/src/css/values/calc.zig @@ -169,20 +169,15 @@ pub fn Calc(comptime V: type) type { } // TODO: addValueOwned - fn addValue(allocator: Allocator, lhs: V, rhs: V) V { + pub fn addValue(allocator: Allocator, lhs: V, rhs: V) V { return switch (V) { f32 => return lhs + rhs, - Angle => return lhs.add(rhs), - // CSSNumber => return lhs.add(rhs), - Length => return lhs.add(allocator, rhs), - Percentage => return lhs.add(allocator, rhs), - Time => return lhs.add(allocator, rhs), - else => lhs.add(allocator, rhs), + else => lhs.addInternal(allocator, rhs), }; } // TODO: intoValueOwned - fn intoValue(this: @This(), allocator: std.mem.Allocator) V { + pub fn intoValue(this: @This(), allocator: std.mem.Allocator) V { switch (V) { Angle => return switch (this) { .value => |v| v.*, @@ -222,19 +217,26 @@ pub fn Calc(comptime V: type) type { } } + pub fn intoCalc(val: V, allocator: std.mem.Allocator) Calc(V) { + return switch (V) { + f32 => .{ .value = bun.create(allocator, f32, val) }, + else => val.intoCalc(allocator), + }; + } + // TODO: change to addOwned() pub fn add(this: @This(), allocator: std.mem.Allocator, rhs: @This()) @This() { if (this == .value and rhs == .value) { // PERF: we can reuse the allocation here - return .{ .value = bun.create(allocator, V, addValue(allocator, this.value.*, rhs.value.*)) }; + return intoCalc(addValue(allocator, this.value.*, rhs.value.*), allocator); } else if (this == .number and rhs == .number) { return .{ .number = this.number + rhs.number }; } else if (this == .value) { // PERF: we can reuse the allocation here - return .{ .value = bun.create(allocator, V, addValue(allocator, this.value.*, intoValue(rhs, allocator))) }; + return intoCalc(addValue(allocator, this.value.*, intoValue(rhs, allocator)), allocator); } else if (rhs == .value) { // PERF: we can reuse the allocation here - return .{ .value = bun.create(allocator, V, addValue(allocator, intoValue(this, allocator), rhs.value.*)) }; + return intoCalc(addValue(allocator, intoValue(this, allocator), rhs.value.*), allocator); } else if (this == .function) { return This{ .sum = .{ @@ -250,11 +252,7 @@ pub fn Calc(comptime V: type) type { }, }; } else { - return .{ .value = bun.create( - allocator, - V, - addValue(allocator, intoValue(this, allocator), intoValue(rhs, allocator)), - ) }; + return intoCalc(addValue(allocator, intoValue(this, allocator), intoValue(rhs, allocator)), allocator); } } diff --git a/src/css/values/easing.zig b/src/css/values/easing.zig index b5e5ea51c2..6de9a827b4 100644 --- a/src/css/values/easing.zig +++ b/src/css/values/easing.zig @@ -46,15 +46,45 @@ pub const EasingFunction = union(enum) { x2: CSSNumber, /// The y-position of the second point in the curve. y2: CSSNumber, + + pub fn eql(lhs: *const @This(), rhs: *const @This()) bool { + return css.implementEql(@This(), lhs, rhs); + } + + pub fn __generateDeepClone() void {} }, /// A step easing function. steps: struct { /// The number of intervals in the function. count: CSSInteger, /// The step position. - position: StepPosition = StepPosition.default, + position: StepPosition = StepPosition.default(), + + pub fn eql(lhs: *const @This(), rhs: *const @This()) bool { + return css.implementEql(@This(), lhs, rhs); + } + + pub fn __generateDeepClone() void {} }, + pub fn eql(lhs: *const @This(), rhs: *const @This()) bool { + return css.implementEql(@This(), lhs, rhs); + } + + pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { + return css.implementDeepClone(@This(), this, allocator); + } + + const Map = bun.ComptimeEnumMap(enum { + linear, + ease, + @"ease-in", + @"ease-out", + @"ease-in-out", + @"step-start", + @"step-end", + }); + pub fn parse(input: *css.Parser) Result(EasingFunction) { const location = input.currentSourceLocation(); if (input.tryParse(struct { @@ -62,23 +92,16 @@ pub const EasingFunction = union(enum) { return i.expectIdent(); } }.parse, .{}).asValue()) |ident| { - // todo_stuff.match_ignore_ascii_case - const keyword = if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "linear")) - EasingFunction.linear - else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "ease")) - EasingFunction.ease - else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "ease-in")) - EasingFunction.ease_in - else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "ease-out")) - EasingFunction.ease_out - else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "ease-in-out")) - EasingFunction.ease_in_out - else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "step-start")) - EasingFunction{ .steps = .{ .count = 1, .position = .start } } - else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "step-end")) - EasingFunction{ .steps = .{ .count = 1, .position = .end } } - else - return .{ .err = location.newUnexpectedTokenError(.{ .ident = ident }) }; + const keyword = if (Map.getASCIIICaseInsensitive(ident)) |e| switch (e) { + .linear => EasingFunction.linear, + .ease => EasingFunction.ease, + .@"ease-in" => EasingFunction.ease_in, + .@"ease-out" => EasingFunction.ease_out, + .@"ease-in-out" => EasingFunction.ease_in_out, + .@"step-start" => EasingFunction{ .steps = .{ .count = 1, .position = .start } }, + .@"step-end" => EasingFunction{ .steps = .{ .count = 1, .position = .end } }, + } else return .{ .err = location.newUnexpectedTokenError(.{ .ident = ident }) }; + return .{ .result = keyword }; } @@ -86,12 +109,13 @@ pub const EasingFunction = union(enum) { .result => |vv| vv, .err => |e| return .{ .err = e }, }; + const Closure = struct { loc: css.SourceLocation, function: []const u8 }; return input.parseNestedBlock( EasingFunction, - .{ .loc = location, .function = function }, + &Closure{ .loc = location, .function = function }, struct { fn parse( - closure: *const struct { loc: css.SourceLocation, function: []const u8 }, + closure: *const Closure, i: *css.Parser, ) Result(EasingFunction) { if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(closure.function, "cubic-bezier")) { @@ -125,10 +149,10 @@ pub const EasingFunction = union(enum) { if (p.expectComma().asErr()) |e| return .{ .err = e }; return StepPosition.parse(p); } - }.parse, .{}).unwrapOr(StepPosition.default); + }.parse, .{}).unwrapOr(StepPosition.default()); return .{ .result = EasingFunction{ .steps = .{ .count = count, .position = position } } }; } else { - return closure.loc.newUnexpectedTokenError(.{ .ident = closure.function }); + return .{ .err = closure.loc.newUnexpectedTokenError(.{ .ident = closure.function }) }; } } }.parse, @@ -145,21 +169,21 @@ pub const EasingFunction = union(enum) { else => { if (this.isEase()) { return dest.writeStr("ease"); - } else if (this == .cubic_bezier and std.meta.eql(this.cubic_bezier, .{ + } else if (this.* == .cubic_bezier and this.cubic_bezier.eql(&.{ .x1 = 0.42, .y1 = 0.0, .x2 = 1.0, .y2 = 1.0, })) { return dest.writeStr("ease-in"); - } else if (this == .cubic_bezier and std.meta.eql(this.cubic_bezier, .{ + } else if (this.* == .cubic_bezier and this.cubic_bezier.eql(&.{ .x1 = 0.0, .y1 = 0.0, .x2 = 0.58, .y2 = 1.0, })) { return dest.writeStr("ease-out"); - } else if (this == .cubic_bezier and std.meta.eql(this.cubic_bezier, .{ + } else if (this.* == .cubic_bezier and this.cubic_bezier.eql(&.{ .x1 = 0.42, .y1 = 0.0, .x2 = 0.58, @@ -171,13 +195,13 @@ pub const EasingFunction = union(enum) { switch (this.*) { .cubic_bezier => |cb| { try dest.writeStr("cubic-bezier("); - try css.generic.toCss(cb.x1, W, dest); + try css.generic.toCss(CSSNumber, &cb.x1, W, dest); try dest.writeChar(','); - try css.generic.toCss(cb.y1, W, dest); + try css.generic.toCss(CSSNumber, &cb.y1, W, dest); try dest.writeChar(','); - try css.generic.toCss(cb.x2, W, dest); + try css.generic.toCss(CSSNumber, &cb.x2, W, dest); try dest.writeChar(','); - try css.generic.toCss(cb.y2, W, dest); + try css.generic.toCss(CSSNumber, &cb.y2, W, dest); try dest.writeChar(')'); }, .steps => { @@ -187,7 +211,6 @@ pub const EasingFunction = union(enum) { if (this.steps.count == 1 and this.steps.position == .end) { return try dest.writeStr("step-end"); } - try dest.writeStr("steps("); try dest.writeFmt("steps({d}", .{this.steps.count}); try dest.delim(',', false); try this.steps.position.toCss(W, dest); @@ -199,10 +222,15 @@ pub const EasingFunction = union(enum) { }; } + /// Returns whether the given string is a valid easing function name. + pub fn isIdent(s: []const u8) bool { + return Map.getASCIIICaseInsensitive(s) != null; + } + /// Returns whether the easing function is equivalent to the `ease` keyword. pub fn isEase(this: *const EasingFunction) bool { return this.* == .ease or - (this.* == .cubic_bezier and std.meta.eql(this.cubic_bezier == .{ + (this.* == .cubic_bezier and this.cubic_bezier.eql(&.{ .x1 = 0.25, .y1 = 0.1, .x2 = 0.25, @@ -218,18 +246,20 @@ pub const StepPosition = enum { /// The last rise occurs at input progress value of 1. end, /// All rises occur within the range (0, 1). - jump_none, + @"jump-none", /// The first rise occurs at input progress value of 0 and the last rise occurs at input progress value of 1. - jump_both, + @"jump-both", - // TODO: implement this - // pub usingnamespace css.DeriveToCss(@This()); + pub usingnamespace css.DeriveToCss(@This()); - pub fn toCss(this: *const StepPosition, comptime W: type, dest: *css.Printer(W)) css.PrintErr!void { - _ = this; // autofix - _ = dest; // autofix - @compileError(css.todo_stuff.depth); - } + const Map = bun.ComptimeEnumMap(enum { + start, + end, + @"jump-none", + @"jump-both", + @"jump-start", + @"jump-end", + }); pub fn parse(input: *css.Parser) Result(StepPosition) { const location = input.currentSourceLocation(); @@ -237,21 +267,19 @@ pub const StepPosition = enum { .result => |vv| vv, .err => |e| return .{ .err = e }, }; - // todo_stuff.match_ignore_ascii_case - const keyword = if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "start")) - StepPosition.start - else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "end")) - StepPosition.end - else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "jump-start")) - StepPosition.start - else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "jump-end")) - StepPosition.end - else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "jump-none")) - StepPosition.jump_none - else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength(ident, "jump-both")) - StepPosition.jump_both - else - return .{ .err = location.newUnexpectedTokenError(.{ .ident = ident }) }; + const keyword = if (Map.getASCIIICaseInsensitive(ident)) |e| switch (e) { + .start => StepPosition.start, + .end => StepPosition.end, + .@"jump-start" => StepPosition.start, + .@"jump-end" => StepPosition.end, + .@"jump-none" => StepPosition.@"jump-none", + .@"jump-both" => StepPosition.@"jump-both", + } else return .{ .err = location.newUnexpectedTokenError(.{ .ident = ident }) }; + return .{ .result = keyword }; } + + pub fn default() StepPosition { + return .end; + } }; diff --git a/src/css/values/gradient.zig b/src/css/values/gradient.zig index 0910a65a78..0ee9d2010d 100644 --- a/src/css/values/gradient.zig +++ b/src/css/values/gradient.zig @@ -450,7 +450,7 @@ pub const LinearGradient = struct { } pub fn eql(this: *const LinearGradient, other: *const LinearGradient) bool { - return this.vendor_prefix.eql(other.vendor_prefix) and this.direction.eql(&other.direction) and css.generic.eqlList(GradientItem(LengthPercentage), &this.items, &other.items); + return css.implementEql(@This(), this, other); } pub fn getFallback(this: *const @This(), allocator: std.mem.Allocator, kind: css.ColorFallbackKind) LinearGradient { @@ -561,10 +561,7 @@ pub const RadialGradient = struct { } pub fn eql(this: *const RadialGradient, other: *const RadialGradient) bool { - return this.vendor_prefix.eql(other.vendor_prefix) and - this.shape.eql(&other.shape) and - this.position.eql(&other.position) and - css.generic.eqlList(GradientItem(LengthPercentage), &this.items, &other.items); + return css.implementEql(@This(), this, other); } }; @@ -656,9 +653,7 @@ pub const ConicGradient = struct { } pub fn eql(this: *const ConicGradient, other: *const ConicGradient) bool { - return this.angle.eql(&other.angle) and - this.position.eql(&other.position) and - css.generic.eqlList(GradientItem(AnglePercentage), &this.items, &other.items); + return css.implementEql(@This(), this, other); } }; @@ -676,6 +671,10 @@ pub const WebKitGradient = union(enum) { pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } }, /// A radial `-webkit-gradient()`. radial: struct { @@ -693,6 +692,10 @@ pub const WebKitGradient = union(enum) { pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } }, pub fn parse(input: *css.Parser) Result(WebKitGradient) { @@ -917,16 +920,7 @@ pub const WebKitGradient = union(enum) { } pub fn eql(this: *const WebKitGradient, other: *const WebKitGradient) bool { - return switch (this.*) { - .linear => |*a| switch (other.*) { - .linear => a.from.eql(&other.linear.from) and a.to.eql(&other.linear.to) and css.generic.eqlList(WebKitColorStop, &a.stops, &other.linear.stops), - else => false, - }, - .radial => |*a| switch (other.*) { - .radial => a.from.eql(&other.radial.from) and a.to.eql(&other.radial.to) and a.r0 == other.radial.r0 and a.r1 == other.radial.r1 and css.generic.eqlList(WebKitColorStop, &a.stops, &other.radial.stops), - else => false, - }, - }; + return css.implementEql(@This(), this, other); } }; @@ -950,6 +944,10 @@ pub const LineDirection = union(enum) { pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } }, pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { @@ -957,24 +955,7 @@ pub const LineDirection = union(enum) { } pub fn eql(this: *const LineDirection, other: *const LineDirection) bool { - return switch (this.*) { - .angle => |*a| switch (other.*) { - .angle => a.eql(&other.angle), - else => false, - }, - .horizontal => |*v| switch (other.*) { - .horizontal => v.* == other.horizontal, - else => false, - }, - .vertical => |*v| switch (other.*) { - .vertical => v.* == other.vertical, - else => false, - }, - .corner => |*c| switch (other.*) { - .corner => c.horizontal == other.corner.horizontal and c.vertical == other.corner.vertical, - else => false, - }, - }; + return css.implementEql(@This(), this, other); } pub fn parse(input: *css.Parser, is_prefixed: bool) Result(LineDirection) { @@ -1071,16 +1052,7 @@ pub fn GradientItem(comptime D: type) type { } pub fn eql(this: *const GradientItem(D), other: *const GradientItem(D)) bool { - return switch (this.*) { - .color_stop => |*a| switch (other.*) { - .color_stop => a.eql(&other.color_stop), - else => false, - }, - .hint => |*a| switch (other.*) { - .hint => css.generic.eql(D, a, &other.hint), - else => false, - }, - }; + return css.implementEql(@This(), this, other); } pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { @@ -1138,16 +1110,7 @@ pub const EndingShape = union(enum) { } pub fn eql(this: *const EndingShape, other: *const EndingShape) bool { - return switch (this.*) { - .ellipse => |*a| switch (other.*) { - .ellipse => a.eql(&other.ellipse), - else => false, - }, - .circle => |*a| switch (other.*) { - .circle => a.eql(&other.circle), - else => false, - }, - }; + return css.implementEql(@This(), this, other); } }; @@ -1177,7 +1140,7 @@ pub const WebKitGradientPoint = struct { } pub fn eql(this: *const WebKitGradientPoint, other: *const WebKitGradientPoint) bool { - return this.x.eql(&other.x) and this.y.eql(&other.y); + return css.implementEql(@This(), this, other); } pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { @@ -1262,20 +1225,7 @@ pub fn WebKitGradientPointComponent(comptime S: type) type { } pub fn eql(this: *const This, other: *const This) bool { - return switch (this.*) { - .center => switch (other.*) { - .center => true, - else => false, - }, - .number => |*a| switch (other.*) { - .number => a.eql(&other.number), - else => false, - }, - .side => |*a| switch (other.*) { - .side => |*b| a.eql(&b.*), - else => false, - }, - }; + return css.implementEql(@This(), this, other); } }; } @@ -1398,7 +1348,7 @@ pub fn ColorStop(comptime D: type) type { } pub fn eql(this: *const This, other: *const This) bool { - return this.color.eql(&other.color) and css.generic.eql(?D, &this.position, &other.position); + return css.implementEql(@This(), this, other); } }; } @@ -1417,6 +1367,10 @@ pub const Ellipse = union(enum) { pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } }, /// A shape extent keyword. extent: ShapeExtent, @@ -1479,7 +1433,7 @@ pub const Ellipse = union(enum) { } pub fn eql(this: *const Ellipse, other: *const Ellipse) bool { - return this.size.x.eql(&other.size.x) and this.size.y.eql(&other.size.y) and this.extent.eql(&other.extent); + return css.implementEql(@This(), this, other); } }; @@ -1571,7 +1525,7 @@ pub const Circle = union(enum) { } pub fn eql(this: *const Circle, other: *const Circle) bool { - return this.radius.eql(&other.radius) and this.extent.eql(&other.extent); + return css.implementEql(@This(), this, other); } }; diff --git a/src/css/values/image.zig b/src/css/values/image.zig index fc82ef938f..2ed2c1df71 100644 --- a/src/css/values/image.zig +++ b/src/css/values/image.zig @@ -95,24 +95,7 @@ pub const Image = union(enum) { } pub inline fn eql(this: *const Image, other: *const Image) bool { - return switch (this.*) { - .none => switch (other.*) { - .none => true, - else => false, - }, - .url => |*a| switch (other.*) { - .url => a.eql(&other.url), - else => false, - }, - .image_set => |*a| switch (other.*) { - .image_set => a.eql(&other.image_set), - else => false, - }, - .gradient => |a| switch (other.*) { - .gradient => a.eql(other.gradient), - else => false, - }, - }; + return css.implementEql(@This(), this, other); } pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { @@ -294,7 +277,7 @@ pub const ImageSet = struct { } pub fn eql(this: *const ImageSet, other: *const ImageSet) bool { - return this.vendor_prefix.eql(other.vendor_prefix) and css.generic.eqlList(ImageSetOption, &this.options, &other.options); + return css.implementEql(@This(), this, other); } pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { @@ -411,12 +394,7 @@ pub const ImageSetOption = struct { } pub fn eql(lhs: *const ImageSetOption, rhs: *const ImageSetOption) bool { - return lhs.image.eql(&rhs.image) and lhs.resolution.eql(&rhs.resolution) and (brk: { - if (lhs.file_type != null and rhs.file_type != null) { - break :brk bun.strings.eql(lhs.file_type.?, rhs.file_type.?); - } - break :brk false; - }); + return css.implementEql(@This(), lhs, rhs); } }; diff --git a/src/css/values/length.zig b/src/css/values/length.zig index b3a9fe12e4..a4e1a0989e 100644 --- a/src/css/values/length.zig +++ b/src/css/values/length.zig @@ -34,10 +34,7 @@ pub const LengthOrNumber = union(enum) { } pub fn eql(this: *const @This(), other: *const @This()) bool { - return switch (this.*) { - .number => |*n| n.* == other.number, - .length => |*l| l.eql(&other.length), - }; + return css.implementEql(@This(), this, other); } pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { @@ -311,6 +308,10 @@ pub const LengthValue = union(enum) { unreachable; } + pub fn deepClone(this: *const @This(), _: std.mem.Allocator) @This() { + return this.*; + } + pub fn zero() LengthValue { return .{ .px = 0.0 }; } @@ -569,10 +570,7 @@ pub const Length = union(enum) { } pub fn eql(this: *const @This(), other: *const @This()) bool { - return switch (this.*) { - .value => |a| other.* == .value and a.eql(&other.value), - .calc => |a| other.* == .calc and a.eql(other.calc), - }; + return css.implementEql(@This(), this, other); } pub fn px(p: CSSNumber) Length { @@ -615,12 +613,12 @@ pub const Length = union(enum) { return res; } - fn addInternal(this: Length, allocator: Allocator, other: Length) Length { + pub fn addInternal(this: Length, allocator: Allocator, other: Length) Length { if (this.tryAdd(allocator, &other)) |r| return r; return this.add__(allocator, other); } - fn intoCalc(this: Length, allocator: Allocator) Calc(Length) { + pub fn intoCalc(this: Length, allocator: Allocator) Calc(Length) { return switch (this) { .calc => |c| c.*, else => |v| Calc(Length){ .value = bun.create(allocator, Length, v) }, diff --git a/src/css/values/number.zig b/src/css/values/number.zig index ddb701a7c0..448ccb71ad 100644 --- a/src/css/values/number.zig +++ b/src/css/values/number.zig @@ -25,19 +25,13 @@ pub const CSSNumberFns = struct { pub fn toCss(this: *const CSSNumber, comptime W: type, dest: *Printer(W)) PrintErr!void { const number: f32 = this.*; if (number != 0.0 and @abs(number) < 1.0) { - // PERF(alloc): Use stack fallback here? - // why the extra allocation anyway? isn't max amount of digits to stringify an f32 small? - var s = ArrayList(u8){}; - defer s.deinit(dest.allocator); - const writer = s.writer(dest.allocator); - css.to_css.float32(number, writer) catch { - return dest.addFmtError(); - }; + var dtoa_buf: [129]u8 = undefined; + const str, _ = try css.dtoa_short(&dtoa_buf, number, 6); if (number < 0.0) { try dest.writeChar('-'); - try dest.writeStr(bun.strings.trimLeadingPattern2(s.items, '-', '0')); + try dest.writeStr(bun.strings.trimLeadingPattern2(str, '-', '0')); } else { - try dest.writeStr(bun.strings.trimLeadingChar(s.items, '0')); + try dest.writeStr(bun.strings.trimLeadingChar(str, '0')); } } else { return css.to_css.float32(number, dest) catch { diff --git a/src/css/values/percentage.zig b/src/css/values/percentage.zig index 081206f7d0..d0795ec426 100644 --- a/src/css/values/percentage.zig +++ b/src/css/values/percentage.zig @@ -62,10 +62,18 @@ pub const Percentage = struct { return this.v == other.v; } + pub fn addInternal(this: Percentage, allocator: std.mem.Allocator, other: Percentage) Percentage { + return this.add(allocator, other); + } + pub fn add(lhs: Percentage, _: std.mem.Allocator, rhs: Percentage) Percentage { return Percentage{ .v = lhs.v + rhs.v }; } + pub fn intoCalc(this: Percentage, allocator: std.mem.Allocator) Calc(Percentage) { + return Calc(Percentage){ .value = bun.create(allocator, Percentage, this) }; + } + pub fn mulF32(this: Percentage, _: std.mem.Allocator, other: f32) Percentage { return Percentage{ .v = this.v * other }; } @@ -267,7 +275,7 @@ pub fn DimensionPercentage(comptime D: type) type { }; } - fn addInternal(this: This, allocator: std.mem.Allocator, other: This) This { + pub fn addInternal(this: This, allocator: std.mem.Allocator, other: This) This { if (this.addRecursive(allocator, &other)) |res| return res; return this.addImpl(allocator, other); } @@ -285,12 +293,12 @@ pub fn DimensionPercentage(comptime D: type) type { .sum => |sum| { const left_calc = This{ .calc = sum.left }; if (left_calc.addRecursive(allocator, other)) |res| { - return res.add(allocator, This{ .calc = sum.right }); + return res.addImpl(allocator, This{ .calc = sum.right }); } const right_calc = This{ .calc = sum.right }; if (right_calc.addRecursive(allocator, other)) |res| { - return (This{ .calc = sum.left }).add(allocator, res); + return (This{ .calc = sum.left }).addImpl(allocator, res); } }, else => {}, @@ -301,12 +309,12 @@ pub fn DimensionPercentage(comptime D: type) type { .sum => |sum| { const left_calc = This{ .calc = sum.left }; if (this.addRecursive(allocator, &left_calc)) |res| { - return res.add(allocator, This{ .calc = sum.right }); + return res.addImpl(allocator, This{ .calc = sum.right }); } const right_calc = This{ .calc = sum.right }; if (this.addRecursive(allocator, &right_calc)) |res| { - return (This{ .calc = sum.left }).add(allocator, res); + return (This{ .calc = sum.left }).addImpl(allocator, res); } }, else => {}, @@ -331,7 +339,7 @@ pub fn DimensionPercentage(comptime D: type) type { return .{ .calc = bun.create(allocator, Calc(DimensionPercentage(D)), a.calc.add(allocator, b.calc.*)) }; } else if (a == .calc) { if (a.calc.* == .value) { - return a.calc.value.add(allocator, b); + return a.calc.value.addImpl(allocator, b); } else { return .{ .calc = bun.create( @@ -346,7 +354,7 @@ pub fn DimensionPercentage(comptime D: type) type { } } else if (b == .calc) { if (b.calc.* == .value) { - return a.add(allocator, b.calc.value.*); + return a.addImpl(allocator, b.calc.value.*); } else { return .{ .calc = bun.create( @@ -484,16 +492,7 @@ pub const NumberOrPercentage = union(enum) { } pub fn eql(this: *const NumberOrPercentage, other: *const NumberOrPercentage) bool { - return switch (this.*) { - .number => |*a| switch (other.*) { - .number => a.* == other.number, - .percentage => false, - }, - .percentage => |*a| switch (other.*) { - .number => false, - .percentage => a.eql(&other.percentage), - }, - }; + return css.implementEql(@This(), this, other); } pub fn intoF32(this: *const @This()) f32 { diff --git a/src/css/values/position.zig b/src/css/values/position.zig index 83c00b9692..d01085f1ed 100644 --- a/src/css/values/position.zig +++ b/src/css/values/position.zig @@ -284,6 +284,10 @@ pub fn PositionComponent(comptime S: type) type { pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { return css.implementDeepClone(@This(), this, allocator); } + + pub fn eql(this: *const @This(), other: *const @This()) bool { + return css.implementEql(@This(), this, other); + } }, const This = @This(); @@ -298,20 +302,7 @@ pub fn PositionComponent(comptime S: type) type { } pub fn eql(this: *const This, other: *const This) bool { - return switch (this.*) { - .center => switch (other.*) { - .center => true, - else => false, - }, - .length => |*a| switch (other.*) { - .length => a.eql(&other.length), - else => false, - }, - .side => |*a| switch (other.*) { - .side => a.side.eql(&other.side.side) and css.generic.eql(?LengthPercentage, &a.offset, &other.side.offset), - else => false, - }, - }; + return css.implementEql(@This(), this, other); } pub fn parse(input: *css.Parser) Result(This) { diff --git a/src/css/values/resolution.zig b/src/css/values/resolution.zig index 951b809b21..cfe0f081f2 100644 --- a/src/css/values/resolution.zig +++ b/src/css/values/resolution.zig @@ -39,20 +39,7 @@ pub const Resolution = union(enum) { } pub fn eql(this: *const Resolution, other: *const Resolution) bool { - return switch (this.*) { - .dpi => |*a| switch (other.*) { - .dpi => a.* == other.dpi, - else => false, - }, - .dpcm => |*a| switch (other.*) { - .dpcm => a.* == other.dpcm, - else => false, - }, - .dppx => |*a| switch (other.*) { - .dppx => a.* == other.dppx, - else => false, - }, - }; + return css.implementEql(@This(), this, other); } pub fn parse(input: *css.Parser) Result(Resolution) { diff --git a/src/css/values/time.zig b/src/css/values/time.zig index 23ed5c9f86..39d5b2210a 100644 --- a/src/css/values/time.zig +++ b/src/css/values/time.zig @@ -28,7 +28,7 @@ const Ident = css.css_values.ident.Ident; /// /// Time values may be explicit or computed by `calc()`, but are always stored and serialized /// as their computed value. -pub const Time = union(enum) { +pub const Time = union(Tag) { /// A time in seconds. seconds: CSSNumber, /// A time in milliseconds. @@ -36,6 +36,10 @@ pub const Time = union(enum) { const Tag = enum(u8) { seconds = 1, milliseconds = 2 }; + pub fn deepClone(this: *const @This(), allocator: std.mem.Allocator) @This() { + return css.implementDeepClone(@This(), this, allocator); + } + pub fn eql(lhs: *const @This(), rhs: *const @This()) bool { return css.implementEql(@This(), lhs, rhs); } @@ -44,19 +48,19 @@ pub const Time = union(enum) { } pub fn parse(input: *css.Parser) Result(Time) { - var calc_result = switch (input.tryParse(Calc(Time).parse, .{})) { - .result => |v| v, - .err => |e| return .{ .err = e }, - }; - switch (calc_result) { - .value => |v| { - const ret: Time = v.*; - // redundant allocation - calc_result.deinit(input.allocator()); - return .{ .result = ret }; + switch (input.tryParse(Calc(Time).parse, .{})) { + .result => |vv| switch (vv) { + .value => |v| { + const ret: Time = v.*; + // redundant allocation + var vvv = vv; + vvv.deinit(input.allocator()); + return .{ .result = ret }; + }, + // Time is always compatible, so they will always compute to a value. + else => return .{ .err = input.newErrorForNextToken() }, }, - // Time is always compatible, so they will always compute to a value. - else => return .{ .err = input.newErrorForNextToken() }, + .err => {}, } const location = input.currentSourceLocation(); @@ -67,14 +71,14 @@ pub const Time = union(enum) { switch (token.*) { .dimension => |*dim| { if (bun.strings.eqlCaseInsensitiveASCIIICheckLength("s", dim.unit)) { - return .{ .result = .{ .seconds = dim.value } }; + return .{ .result = .{ .seconds = dim.num.value } }; } else if (bun.strings.eqlCaseInsensitiveASCIIICheckLength("ms", dim.unit)) { - return .{ .result = .{ .milliseconds = dim.value } }; + return .{ .result = .{ .milliseconds = dim.num.value } }; } else { return .{ .err = location.newUnexpectedTokenError(css.Token{ .ident = dim.unit }) }; } }, - else => return .{ .err = location.newUnexpectedTokenError(token) }, + else => return .{ .err = location.newUnexpectedTokenError(token.*) }, } } @@ -103,6 +107,13 @@ pub const Time = union(enum) { } } + pub fn isZero(this: *const Time) bool { + return switch (this.*) { + .seconds => |s| s == 0.0, + .milliseconds => |ms| ms == 0.0, + }; + } + /// Returns the time in milliseconds. pub fn toMs(this: *const Time) CSSNumber { return switch (this.*) { @@ -138,11 +149,20 @@ pub const Time = union(enum) { }; } + pub fn addInternal(this: Time, allocator: std.mem.Allocator, other: Time) Time { + return this.add(allocator, other); + } + + pub fn intoCalc(this: Time, allocator: std.mem.Allocator) Calc(Time) { + return Calc(Time){ .value = bun.create(allocator, Time, this) }; + } + pub fn add(this: @This(), _: std.mem.Allocator, other: @This()) Time { - return switch (this) { - .seconds => |v| .{ .seconds = v + other.seconds }, - .milliseconds => |v| .{ .milliseconds = v + other.milliseconds }, - }; + return this.op(&other, {}, struct { + fn mul(_: void, a: f32, b: f32) f32 { + return a + b; + } + }.mul); } pub fn partialCmp(this: *const Time, other: *const Time) ?std.math.Order { diff --git a/src/string_immutable.zig b/src/string_immutable.zig index 1ede872e27..d10190ce67 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -4642,11 +4642,10 @@ pub fn trimLeadingChar(slice: []const u8, char: u8) []const u8 { /// e.g. /// `trimLeadingPattern2("abcdef", 'a', 'b') == "cdef"` pub fn trimLeadingPattern2(slice_: []const u8, comptime byte1: u8, comptime byte2: u8) []const u8 { - const pattern: u16 = comptime @as(u16, byte2) << 8 | @as(u16, byte1); + // const pattern: u16 = comptime @as(u16, byte2) << 8 | @as(u16, byte1); var slice = slice_; while (slice.len >= 2) { - const sliceu16: [*]const u16 = @ptrCast(@alignCast(slice.ptr)); - if (sliceu16[0] == pattern) { + if (slice[0] == byte1 and slice[1] == byte2) { slice = slice[2..]; } else { break; diff --git a/test/js/bun/css/css.test.ts b/test/js/bun/css/css.test.ts index 3e256d1e8e..1d60d337f6 100644 --- a/test/js/bun/css/css.test.ts +++ b/test/js/bun/css/css.test.ts @@ -16,6 +16,10 @@ import { ParserOptions, } from "./util"; +function Some(n: number): number { + return n; +} + function error_test(css: string, error: unknown) { // going to ignore this test for now test.skip(`ERROR: ${css}`, () => {}); @@ -1296,19 +1300,19 @@ describe("css tests", () => { ` .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) { border-left-color: #b32323; - border-left-color: color(display-p3 .6433075 .19245467 .1677117); + border-left-color: color(display-p3 .643308 .192455 .167712); border-left-color: lab(40% 56.6 39); border-right-color: #ee00be; - border-right-color: color(display-p3 .9729615 -.36207756 .80420625); + border-right-color: color(display-p3 .972961 -.362078 .804206); border-right-color: lch(50.998% 135.363 338); } .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { border-left-color: #ee00be; - border-left-color: color(display-p3 .9729615 -.36207756 .80420625); + border-left-color: color(display-p3 .972961 -.362078 .804206); border-left-color: lch(50.998% 135.363 338); border-right-color: #b32323; - border-right-color: color(display-p3 .6433075 .19245467 .1677117); + border-right-color: color(display-p3 .643308 .192455 .167712); border-right-color: lab(40% 56.6 39); } `, @@ -3258,270 +3262,270 @@ describe("css tests", () => { `, ); - // cssTest( - // ` - // .foo { - // align-content: center; - // justify-content: center; - // } - // `, - // ` - // .foo { - // place-content: center; - // } - // `, - // ); - - // cssTest( - // ` - // .foo { - // align-content: first baseline; - // justify-content: safe right; - // } - // `, - // ` - // .foo { - // place-content: baseline safe right; - // } - // `, - // ); - - // cssTest( - // ` - // .foo { - // place-content: first baseline unsafe left; - // } - // `, - // ` - // .foo { - // place-content: baseline unsafe left; - // } - // `, - // ); - - // cssTest( - // ` - // .foo { - // place-content: center center; - // } - // `, - // ` - // .foo { - // place-content: center; - // } - // `, - // ); - - // cssTest( - // ` - // .foo { - // align-self: center; - // justify-self: center; - // } - // `, - // ` - // .foo { - // place-self: center; - // } - // `, - // ); - - // cssTest( - // ` - // .foo { - // align-self: center; - // justify-self: unsafe left; - // } - // `, - // ` - // .foo { - // place-self: center unsafe left; - // } - // `, - // ); - - // cssTest( - // ` - // .foo { - // align-items: center; - // justify-items: center; - // } - // `, - // ` - // .foo { - // place-items: center; - // } - // `, - // ); - - // cssTest( - // ` - // .foo { - // align-items: center; - // justify-items: legacy left; - // } - // `, - // ` - // .foo { - // place-items: center legacy left; - // } - // `, - // ); - - // cssTest( - // ` - // .foo { - // place-items: center; - // justify-items: var(--justify); - // } - // `, - // ` - // .foo { - // place-items: center; - // justify-items: var(--justify); - // } - // `, - // ); - - // cssTest( - // ` - // .foo { - // row-gap: 10px; - // column-gap: 20px; - // } - // `, - // ` - // .foo { - // gap: 10px 20px; - // } - // `, - // ); - - // cssTest( - // ` - // .foo { - // row-gap: 10px; - // column-gap: 10px; - // } - // `, - // ` - // .foo { - // gap: 10px; - // } - // `, - // ); - - // cssTest( - // ` - // .foo { - // gap: 10px; - // column-gap: 20px; - // } - // `, - // ` - // .foo { - // gap: 10px 20px; - // } - // `, - // ); - - // cssTest( - // ` - // .foo { - // column-gap: 20px; - // gap: 10px; - // } - // `, - // ` - // .foo { - // gap: 10px; - // } - // `, - // ); - - // cssTest( - // ` - // .foo { - // row-gap: normal; - // column-gap: 20px; - // } - // `, - // ` - // .foo { - // gap: normal 20px; - // } - // `, - // ); - cssTest( ` .foo { - -webkit-flex-grow: 1; - -webkit-flex-shrink: 1; - -webkit-flex-basis: auto; + align-content: center; + justify-content: center; } `, ` .foo { - -webkit-flex: auto; + place-content: center; } `, ); + cssTest( ` - .foo { - -webkit-flex-grow: 1; - -webkit-flex-shrink: 1; - -webkit-flex-basis: auto; - flex-grow: 1; - flex-shrink: 1; - flex-basis: auto; - } - `, + .foo { + align-content: first baseline; + justify-content: safe right; + } + `, ` - .foo { - -webkit-flex: auto; - flex: auto; - } - `, + .foo { + place-content: baseline safe right; + } + `, + ); + + cssTest( + ` + .foo { + place-content: first baseline unsafe left; + } + `, + ` + .foo { + place-content: baseline unsafe left; + } + `, + ); + + cssTest( + ` + .foo { + place-content: center center; + } + `, + ` + .foo { + place-content: center; + } + `, + ); + + cssTest( + ` + .foo { + align-self: center; + justify-self: center; + } + `, + ` + .foo { + place-self: center; + } + `, + ); + + cssTest( + ` + .foo { + align-self: center; + justify-self: unsafe left; + } + `, + ` + .foo { + place-self: center unsafe left; + } + `, + ); + + cssTest( + ` + .foo { + align-items: center; + justify-items: center; + } + `, + ` + .foo { + place-items: center; + } + `, + ); + + cssTest( + ` + .foo { + align-items: center; + justify-items: legacy left; + } + `, + ` + .foo { + place-items: center legacy left; + } + `, + ); + + cssTest( + ` + .foo { + place-items: center; + justify-items: var(--justify); + } + `, + ` + .foo { + place-items: center; + justify-items: var(--justify); + } + `, + ); + + cssTest( + ` + .foo { + row-gap: 10px; + column-gap: 20px; + } + `, + ` + .foo { + gap: 10px 20px; + } + `, + ); + + cssTest( + ` + .foo { + row-gap: 10px; + column-gap: 10px; + } + `, + ` + .foo { + gap: 10px; + } + `, + ); + + cssTest( + ` + .foo { + gap: 10px; + column-gap: 20px; + } + `, + ` + .foo { + gap: 10px 20px; + } + `, + ); + + cssTest( + ` + .foo { + column-gap: 20px; + gap: 10px; + } + `, + ` + .foo { + gap: 10px; + } + `, + ); + + cssTest( + ` + .foo { + row-gap: normal; + column-gap: 20px; + } + `, + ` + .foo { + gap: normal 20px; + } + `, + ); + + cssTest( + ` + .foo { + -webkit-flex-grow: 1; + -webkit-flex-shrink: 1; + -webkit-flex-basis: auto; + } + `, + ` + .foo { + -webkit-flex: auto; + } + `, + ); + cssTest( + ` + .foo { + -webkit-flex-grow: 1; + -webkit-flex-shrink: 1; + -webkit-flex-basis: auto; + flex-grow: 1; + flex-shrink: 1; + flex-basis: auto; + } + `, + ` + .foo { + -webkit-flex: auto; + flex: auto; + } + `, ); prefix_test( ` - .foo { - -webkit-box-orient: horizontal; - -webkit-box-direction: normal; - flex-direction: row; - } - `, + .foo { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + flex-direction: row; + } + `, ` - .foo { - -webkit-box-orient: horizontal; - -webkit-box-direction: normal; - -webkit-flex-direction: row; - flex-direction: row; - } - `, + .foo { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + flex-direction: row; + } + `, { safari: 4 << 16, }, ); prefix_test( ` - .foo { - flex-direction: row; - } - `, + .foo { + flex-direction: row; + } + `, ` - .foo { - -webkit-box-orient: horizontal; - -moz-box-orient: horizontal; - -webkit-box-direction: normal; - -moz-box-direction: normal; - -webkit-flex-direction: row; - -ms-flex-direction: row; - flex-direction: row; - } - `, + .foo { + -webkit-box-orient: horizontal; + -moz-box-orient: horizontal; + -webkit-box-direction: normal; + -moz-box-direction: normal; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + } + `, { safari: 4 << 16, firefox: 4 << 16, @@ -3530,40 +3534,40 @@ describe("css tests", () => { ); prefix_test( ` - .foo { - -webkit-box-orient: horizontal; - -webkit-box-direction: normal; - -moz-box-orient: horizontal; - -moz-box-direction: normal; - -webkit-flex-direction: row; - -ms-flex-direction: row; - flex-direction: row; - } - `, + .foo { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -moz-box-orient: horizontal; + -moz-box-direction: normal; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + } + `, ` - .foo { - flex-direction: row; - } - `, + .foo { + flex-direction: row; + } + `, { safari: 14 << 16, }, ); prefix_test( ` - .foo { - flex-wrap: wrap; - } - `, + .foo { + flex-wrap: wrap; + } + `, ` - .foo { - -webkit-box-lines: multiple; - -moz-box-lines: multiple; - -webkit-flex-wrap: wrap; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - } - `, + .foo { + -webkit-box-lines: multiple; + -moz-box-lines: multiple; + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + } + `, { safari: 4 << 16, firefox: 4 << 16, @@ -3572,40 +3576,40 @@ describe("css tests", () => { ); prefix_test( ` - .foo { - -webkit-box-lines: multiple; - -moz-box-lines: multiple; - -webkit-flex-wrap: wrap; - -ms-flex-wrap: wrap; - flex-wrap: wrap; - } - `, + .foo { + -webkit-box-lines: multiple; + -moz-box-lines: multiple; + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + } + `, ` - .foo { - flex-wrap: wrap; - } - `, + .foo { + flex-wrap: wrap; + } + `, { safari: 11 << 16, }, ); prefix_test( ` - .foo { - flex-flow: row wrap; - } - `, + .foo { + flex-flow: row wrap; + } + `, ` - .foo { - -webkit-box-orient: horizontal; - -moz-box-orient: horizontal; - -webkit-box-direction: normal; - -moz-box-direction: normal; - -webkit-flex-flow: wrap; - -ms-flex-flow: wrap; - flex-flow: wrap; - } - `, + .foo { + -webkit-box-orient: horizontal; + -moz-box-orient: horizontal; + -webkit-box-direction: normal; + -moz-box-direction: normal; + -webkit-flex-flow: wrap; + -ms-flex-flow: wrap; + flex-flow: wrap; + } + `, { safari: 4 << 16, firefox: 4 << 16, @@ -3614,40 +3618,40 @@ describe("css tests", () => { ); prefix_test( ` - .foo { - -webkit-box-orient: horizontal; - -moz-box-orient: horizontal; - -webkit-box-direction: normal; - -moz-box-direction: normal; - -webkit-flex-flow: wrap; - -ms-flex-flow: wrap; - flex-flow: wrap; - } - `, + .foo { + -webkit-box-orient: horizontal; + -moz-box-orient: horizontal; + -webkit-box-direction: normal; + -moz-box-direction: normal; + -webkit-flex-flow: wrap; + -ms-flex-flow: wrap; + flex-flow: wrap; + } + `, ` - .foo { - flex-flow: wrap; - } - `, + .foo { + flex-flow: wrap; + } + `, { safari: 11 << 16, }, ); prefix_test( ` - .foo { - flex-grow: 1; - } - `, + .foo { + flex-grow: 1; + } + `, ` - .foo { - -webkit-box-flex: 1; - -moz-box-flex: 1; - -ms-flex-positive: 1; - -webkit-flex-grow: 1; - flex-grow: 1; - } - `, + .foo { + -webkit-box-flex: 1; + -moz-box-flex: 1; + -ms-flex-positive: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + } + `, { safari: 4 << 16, firefox: 4 << 16, @@ -3656,36 +3660,36 @@ describe("css tests", () => { ); prefix_test( ` - .foo { - -webkit-box-flex: 1; - -moz-box-flex: 1; - -ms-flex-positive: 1; - -webkit-flex-grow: 1; - flex-grow: 1; - } - `, + .foo { + -webkit-box-flex: 1; + -moz-box-flex: 1; + -ms-flex-positive: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + } + `, ` - .foo { - flex-grow: 1; - } - `, + .foo { + flex-grow: 1; + } + `, { safari: 11 << 16, }, ); prefix_test( ` - .foo { - flex-shrink: 1; - } - `, + .foo { + flex-shrink: 1; + } + `, ` - .foo { - -ms-flex-negative: 1; - -webkit-flex-shrink: 1; - flex-shrink: 1; - } - `, + .foo { + -ms-flex-negative: 1; + -webkit-flex-shrink: 1; + flex-shrink: 1; + } + `, { safari: 4 << 16, firefox: 4 << 16, @@ -3694,34 +3698,34 @@ describe("css tests", () => { ); prefix_test( ` - .foo { - -ms-flex-negative: 1; - -webkit-flex-shrink: 1; - flex-shrink: 1; - } - `, + .foo { + -ms-flex-negative: 1; + -webkit-flex-shrink: 1; + flex-shrink: 1; + } + `, ` - .foo { - flex-shrink: 1; - } - `, + .foo { + flex-shrink: 1; + } + `, { safari: 11 << 16, }, ); prefix_test( ` - .foo { - flex-basis: 1px; - } - `, + .foo { + flex-basis: 1px; + } + `, ` - .foo { - -ms-flex-preferred-size: 1px; - -webkit-flex-basis: 1px; - flex-basis: 1px; - } - `, + .foo { + -ms-flex-preferred-size: 1px; + -webkit-flex-basis: 1px; + flex-basis: 1px; + } + `, { safari: 4 << 16, firefox: 4 << 16, @@ -3730,36 +3734,36 @@ describe("css tests", () => { ); prefix_test( ` - .foo { - -ms-flex-preferred-size: 1px; - -webkit-flex-basis: 1px; - flex-basis: 1px; - } - `, + .foo { + -ms-flex-preferred-size: 1px; + -webkit-flex-basis: 1px; + flex-basis: 1px; + } + `, ` - .foo { - flex-basis: 1px; - } - `, + .foo { + flex-basis: 1px; + } + `, { safari: 11 << 16, }, ); prefix_test( ` - .foo { - flex: 1; - } - `, + .foo { + flex: 1; + } + `, ` - .foo { - -webkit-box-flex: 1; - -moz-box-flex: 1; - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - } - `, + .foo { + -webkit-box-flex: 1; + -moz-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + } + `, { safari: 4 << 16, firefox: 4 << 16, @@ -3768,391 +3772,36 @@ describe("css tests", () => { ); prefix_test( ` - .foo { - -webkit-box-flex: 1; - -moz-box-flex: 1; - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - } - `, + .foo { + -webkit-box-flex: 1; + -moz-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + } + `, ` - .foo { - flex: 1; - } - `, + .foo { + flex: 1; + } + `, { safari: 11 << 16, }, ); - // prefix_test( - // ` - // .foo { - // align-content: space-between; - // } - // `, - // ` - // .foo { - // -ms-flex-line-pack: justify; - // -webkit-align-content: space-between; - // align-content: space-between; - // } - // `, - // { - // safari: 4 << 16, - // firefox: 4 << 16, - // ie: 10 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // -ms-flex-line-pack: justify; - // -webkit-align-content: space-between; - // align-content: space-between; - // } - // `, - // ` - // .foo { - // align-content: space-between; - // } - // `, - // { - // safari: 11 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // justify-content: space-between; - // } - // `, - // ` - // .foo { - // -webkit-box-pack: justify; - // -moz-box-pack: justify; - // -ms-flex-pack: justify; - // -webkit-justify-content: space-between; - // justify-content: space-between; - // } - // `, - // { - // safari: 4 << 16, - // firefox: 4 << 16, - // ie: 10 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // -webkit-box-pack: justify; - // -moz-box-pack: justify; - // -ms-flex-pack: justify; - // -webkit-justify-content: space-between; - // justify-content: space-between; - // } - // `, - // ` - // .foo { - // justify-content: space-between; - // } - // `, - // { - // safari: 11 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // place-content: space-between flex-end; - // } - // `, - // ` - // .foo { - // -ms-flex-line-pack: justify; - // -webkit-box-pack: end; - // -moz-box-pack: end; - // -ms-flex-pack: end; - // -webkit-align-content: space-between; - // align-content: space-between; - // -webkit-justify-content: flex-end; - // justify-content: flex-end; - // } - // `, - // { - // safari: 4 << 16, - // firefox: 4 << 16, - // ie: 10 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // -ms-flex-line-pack: justify; - // -webkit-box-pack: end; - // -moz-box-pack: end; - // -ms-flex-pack: end; - // -webkit-align-content: space-between; - // -webkit-justify-content: flex-end; - // place-content: space-between flex-end; - // } - // `, - // ` - // .foo { - // place-content: space-between flex-end; - // } - // `, - // { - // safari: 11 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // place-content: space-between flex-end; - // } - // `, - // ` - // .foo { - // align-content: space-between; - // justify-content: flex-end; - // } - // `, - // { - // chrome: 30 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // place-content: space-between flex-end; - // } - // `, - // ` - // .foo { - // place-content: space-between flex-end; - // } - // `, - // { - // chrome: 60 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // align-self: flex-end; - // } - // `, - // ` - // .foo { - // -ms-flex-item-align: end; - // -webkit-align-self: flex-end; - // align-self: flex-end; - // } - // `, - // { - // safari: 4 << 16, - // firefox: 4 << 16, - // ie: 10 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // -ms-flex-item-align: end; - // -webkit-align-self: flex-end; - // align-self: flex-end; - // } - // `, - // ` - // .foo { - // align-self: flex-end; - // } - // `, - // { - // safari: 11 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // place-self: center flex-end; - // } - // `, - // ` - // .foo { - // -ms-flex-item-align: center; - // -webkit-align-self: center; - // align-self: center; - // justify-self: flex-end; - // } - // `, - // { - // safari: 4 << 16, - // firefox: 4 << 16, - // ie: 10 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // -ms-flex-item-align: center; - // -webkit-align-self: center; - // place-self: center flex-end; - // } - // `, - // ` - // .foo { - // place-self: center flex-end; - // } - // `, - // { - // safari: 11 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // place-self: center flex-end; - // } - // `, - // ` - // .foo { - // align-self: center; - // justify-self: flex-end; - // } - // `, - // { - // chrome: 57 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // place-self: center flex-end; - // } - // `, - // ` - // .foo { - // place-self: center flex-end; - // } - // `, - // { - // chrome: 59 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // align-items: flex-end; - // } - // `, - // ` - // .foo { - // -webkit-box-align: end; - // -moz-box-align: end; - // -ms-flex-align: end; - // -webkit-align-items: flex-end; - // align-items: flex-end; - // } - // `, - // { - // safari: 4 << 16, - // firefox: 4 << 16, - // ie: 10 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // -webkit-box-align: end; - // -moz-box-align: end; - // -ms-flex-align: end; - // -webkit-align-items: flex-end; - // align-items: flex-end; - // } - // `, - // ` - // .foo { - // align-items: flex-end; - // } - // `, - // { - // safari: 11 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // place-items: flex-end center; - // } - // `, - // ` - // .foo { - // -webkit-box-align: end; - // -moz-box-align: end; - // -ms-flex-align: end; - // -webkit-align-items: flex-end; - // align-items: flex-end; - // justify-items: center; - // } - // `, - // { - // safari: 4 << 16, - // firefox: 4 << 16, - // ie: 10 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // -webkit-box-align: end; - // -moz-box-align: end; - // -ms-flex-align: end; - // -webkit-align-items: flex-end; - // place-items: flex-end center; - // } - // `, - // ` - // .foo { - // place-items: flex-end center; - // } - // `, - // { - // safari: 11 << 16, - // }, - // ); - // prefix_test( - // ` - // .foo { - // place-items: flex-end center; - // } - // `, - // ` - // .foo { - // align-items: flex-end; - // justify-items: center; - // } - // `, - // { - // safari: 10 << 16, - // }, - // ); prefix_test( ` - .foo { - order: 1; - } - `, + .foo { + align-content: space-between; + } + `, ` - .foo { - -webkit-box-ordinal-group: 1; - -moz-box-ordinal-group: 1; - -ms-flex-order: 1; - -webkit-order: 1; - order: 1; - } - `, + .foo { + -ms-flex-line-pack: justify; + -webkit-align-content: space-between; + align-content: space-between; + } + `, { safari: 4 << 16, firefox: 4 << 16, @@ -4161,36 +3810,391 @@ describe("css tests", () => { ); prefix_test( ` - .foo { - -webkit-box-ordinal-group: 1; - -moz-box-ordinal-group: 1; - -ms-flex-order: 1; - -webkit-order: 1; - order: 1; - } - `, + .foo { + -ms-flex-line-pack: justify; + -webkit-align-content: space-between; + align-content: space-between; + } + `, ` - .foo { - order: 1; - } - `, + .foo { + align-content: space-between; + } + `, { safari: 11 << 16, }, ); prefix_test( ` - .foo { - -ms-flex: 0 0 8%; - flex: 0 0 5%; - } - `, + .foo { + justify-content: space-between; + } + `, ` - .foo { - -ms-flex: 0 0 8%; - flex: 0 0 5%; - } - `, + .foo { + -webkit-box-pack: justify; + -moz-box-pack: justify; + -ms-flex-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; + } + `, + { + safari: 4 << 16, + firefox: 4 << 16, + ie: 10 << 16, + }, + ); + prefix_test( + ` + .foo { + -webkit-box-pack: justify; + -moz-box-pack: justify; + -ms-flex-pack: justify; + -webkit-justify-content: space-between; + justify-content: space-between; + } + `, + ` + .foo { + justify-content: space-between; + } + `, + { + safari: 11 << 16, + }, + ); + prefix_test( + ` + .foo { + place-content: space-between flex-end; + } + `, + ` + .foo { + -ms-flex-line-pack: justify; + -webkit-box-pack: end; + -moz-box-pack: end; + -ms-flex-pack: end; + -webkit-align-content: space-between; + align-content: space-between; + -webkit-justify-content: flex-end; + justify-content: flex-end; + } + `, + { + safari: 4 << 16, + firefox: 4 << 16, + ie: 10 << 16, + }, + ); + prefix_test( + ` + .foo { + -ms-flex-line-pack: justify; + -webkit-box-pack: end; + -moz-box-pack: end; + -ms-flex-pack: end; + -webkit-align-content: space-between; + -webkit-justify-content: flex-end; + place-content: space-between flex-end; + } + `, + ` + .foo { + place-content: space-between flex-end; + } + `, + { + safari: 11 << 16, + }, + ); + prefix_test( + ` + .foo { + place-content: space-between flex-end; + } + `, + ` + .foo { + align-content: space-between; + justify-content: flex-end; + } + `, + { + chrome: 30 << 16, + }, + ); + prefix_test( + ` + .foo { + place-content: space-between flex-end; + } + `, + ` + .foo { + place-content: space-between flex-end; + } + `, + { + chrome: 60 << 16, + }, + ); + prefix_test( + ` + .foo { + align-self: flex-end; + } + `, + ` + .foo { + -ms-flex-item-align: end; + -webkit-align-self: flex-end; + align-self: flex-end; + } + `, + { + safari: 4 << 16, + firefox: 4 << 16, + ie: 10 << 16, + }, + ); + prefix_test( + ` + .foo { + -ms-flex-item-align: end; + -webkit-align-self: flex-end; + align-self: flex-end; + } + `, + ` + .foo { + align-self: flex-end; + } + `, + { + safari: 11 << 16, + }, + ); + prefix_test( + ` + .foo { + place-self: center flex-end; + } + `, + ` + .foo { + -ms-flex-item-align: center; + -webkit-align-self: center; + align-self: center; + justify-self: flex-end; + } + `, + { + safari: 4 << 16, + firefox: 4 << 16, + ie: 10 << 16, + }, + ); + prefix_test( + ` + .foo { + -ms-flex-item-align: center; + -webkit-align-self: center; + place-self: center flex-end; + } + `, + ` + .foo { + place-self: center flex-end; + } + `, + { + safari: 11 << 16, + }, + ); + prefix_test( + ` + .foo { + place-self: center flex-end; + } + `, + ` + .foo { + align-self: center; + justify-self: flex-end; + } + `, + { + chrome: 57 << 16, + }, + ); + prefix_test( + ` + .foo { + place-self: center flex-end; + } + `, + ` + .foo { + place-self: center flex-end; + } + `, + { + chrome: 59 << 16, + }, + ); + prefix_test( + ` + .foo { + align-items: flex-end; + } + `, + ` + .foo { + -webkit-box-align: end; + -moz-box-align: end; + -ms-flex-align: end; + -webkit-align-items: flex-end; + align-items: flex-end; + } + `, + { + safari: 4 << 16, + firefox: 4 << 16, + ie: 10 << 16, + }, + ); + prefix_test( + ` + .foo { + -webkit-box-align: end; + -moz-box-align: end; + -ms-flex-align: end; + -webkit-align-items: flex-end; + align-items: flex-end; + } + `, + ` + .foo { + align-items: flex-end; + } + `, + { + safari: 11 << 16, + }, + ); + prefix_test( + ` + .foo { + place-items: flex-end center; + } + `, + ` + .foo { + -webkit-box-align: end; + -moz-box-align: end; + -ms-flex-align: end; + -webkit-align-items: flex-end; + align-items: flex-end; + justify-items: center; + } + `, + { + safari: 4 << 16, + firefox: 4 << 16, + ie: 10 << 16, + }, + ); + prefix_test( + ` + .foo { + -webkit-box-align: end; + -moz-box-align: end; + -ms-flex-align: end; + -webkit-align-items: flex-end; + place-items: flex-end center; + } + `, + ` + .foo { + place-items: flex-end center; + } + `, + { + safari: 11 << 16, + }, + ); + prefix_test( + ` + .foo { + place-items: flex-end center; + } + `, + ` + .foo { + align-items: flex-end; + justify-items: center; + } + `, + { + safari: 10 << 16, + }, + ); + prefix_test( + ` + .foo { + order: 1; + } + `, + ` + .foo { + -webkit-box-ordinal-group: 1; + -moz-box-ordinal-group: 1; + -ms-flex-order: 1; + -webkit-order: 1; + order: 1; + } + `, + { + safari: 4 << 16, + firefox: 4 << 16, + ie: 10 << 16, + }, + ); + prefix_test( + ` + .foo { + -webkit-box-ordinal-group: 1; + -moz-box-ordinal-group: 1; + -ms-flex-order: 1; + -webkit-order: 1; + order: 1; + } + `, + ` + .foo { + order: 1; + } + `, + { + safari: 11 << 16, + }, + ); + prefix_test( + ` + .foo { + -ms-flex: 0 0 8%; + flex: 0 0 5%; + } + `, + ` + .foo { + -ms-flex: 0 0 8%; + flex: 0 0 5%; + } + `, { safari: 11 << 16, }, @@ -6083,4 +6087,815 @@ describe("css tests", () => { error_test("@media (grid: 10) { .foo { color: chartreuse }}", "ParserError::InvalidMediaQuery"); error_test("@media (prefers-color-scheme = dark) { .foo { color: chartreuse }}", "ParserError::InvalidMediaQuery"); }); + + describe("transition", () => { + minify_test(".foo { transition-duration: 500ms }", ".foo{transition-duration:.5s}"); + minify_test(".foo { transition-duration: .5s }", ".foo{transition-duration:.5s}"); + minify_test(".foo { transition-duration: 99ms }", ".foo{transition-duration:99ms}"); + minify_test(".foo { transition-duration: .099s }", ".foo{transition-duration:99ms}"); + minify_test(".foo { transition-duration: 2000ms }", ".foo{transition-duration:2s}"); + minify_test(".foo { transition-duration: 2s }", ".foo{transition-duration:2s}"); + minify_test(".foo { transition-duration: calc(1s - 50ms) }", ".foo{transition-duration:.95s}"); + minify_test(".foo { transition-duration: calc(1s - 50ms + 2s) }", ".foo{transition-duration:2.95s}"); + minify_test(".foo { transition-duration: calc((1s - 50ms) * 2) }", ".foo{transition-duration:1.9s}"); + minify_test(".foo { transition-duration: calc(2 * (1s - 50ms)) }", ".foo{transition-duration:1.9s}"); + minify_test(".foo { transition-duration: calc((2s + 50ms) - (1s - 50ms)) }", ".foo{transition-duration:1.1s}"); + minify_test(".foo { transition-duration: 500ms, 50ms }", ".foo{transition-duration:.5s,50ms}"); + minify_test(".foo { transition-delay: 500ms }", ".foo{transition-delay:.5s}"); + minify_test(".foo { transition-property: background }", ".foo{transition-property:background}"); + minify_test(".foo { transition-property: background, opacity }", ".foo{transition-property:background,opacity}"); + minify_test(".foo { transition-timing-function: linear }", ".foo{transition-timing-function:linear}"); + minify_test(".foo { transition-timing-function: ease }", ".foo{transition-timing-function:ease}"); + minify_test(".foo { transition-timing-function: ease-in }", ".foo{transition-timing-function:ease-in}"); + minify_test(".foo { transition-timing-function: ease-out }", ".foo{transition-timing-function:ease-out}"); + minify_test(".foo { transition-timing-function: ease-in-out }", ".foo{transition-timing-function:ease-in-out}"); + minify_test( + ".foo { transition-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1) }", + ".foo{transition-timing-function:ease}", + ); + minify_test( + ".foo { transition-timing-function: cubic-bezier(0.42, 0, 1, 1) }", + ".foo{transition-timing-function:ease-in}", + ); + minify_test( + ".foo { transition-timing-function: cubic-bezier(0, 0, 0.58, 1) }", + ".foo{transition-timing-function:ease-out}", + ); + minify_test( + ".foo { transition-timing-function: cubic-bezier(0.42, 0, 0.58, 1) }", + ".foo{transition-timing-function:ease-in-out}", + ); + minify_test( + ".foo { transition-timing-function: cubic-bezier(0.58, 0.2, 0.11, 1.2) }", + ".foo{transition-timing-function:cubic-bezier(.58,.2,.11,1.2)}", + ); + minify_test(".foo { transition-timing-function: step-start }", ".foo{transition-timing-function:step-start}"); + minify_test(".foo { transition-timing-function: step-end }", ".foo{transition-timing-function:step-end}"); + minify_test(".foo { transition-timing-function: steps(1, start) }", ".foo{transition-timing-function:step-start}"); + minify_test( + ".foo { transition-timing-function: steps(1, jump-start) }", + ".foo{transition-timing-function:step-start}", + ); + minify_test(".foo { transition-timing-function: steps(1, end) }", ".foo{transition-timing-function:step-end}"); + minify_test(".foo { transition-timing-function: steps(1, jump-end) }", ".foo{transition-timing-function:step-end}"); + minify_test( + ".foo { transition-timing-function: steps(5, jump-start) }", + ".foo{transition-timing-function:steps(5,start)}", + ); + minify_test( + ".foo { transition-timing-function: steps(5, jump-end) }", + ".foo{transition-timing-function:steps(5,end)}", + ); + minify_test( + ".foo { transition-timing-function: steps(5, jump-both) }", + ".foo{transition-timing-function:steps(5,jump-both)}", + ); + minify_test( + ".foo { transition-timing-function: ease-in-out, cubic-bezier(0.42, 0, 1, 1) }", + ".foo{transition-timing-function:ease-in-out,ease-in}", + ); + minify_test( + ".foo { transition-timing-function: cubic-bezier(0.42, 0, 1, 1), cubic-bezier(0.58, 0.2, 0.11, 1.2) }", + ".foo{transition-timing-function:ease-in,cubic-bezier(.58,.2,.11,1.2)}", + ); + minify_test( + ".foo { transition-timing-function: step-start, steps(5, jump-start) }", + ".foo{transition-timing-function:step-start,steps(5,start)}", + ); + minify_test(".foo { transition: width 2s ease }", ".foo{transition:width 2s}"); + minify_test( + ".foo { transition: width 2s ease, height 1000ms cubic-bezier(0.25, 0.1, 0.25, 1) }", + ".foo{transition:width 2s,height 1s}", + ); + minify_test(".foo { transition: width 2s 1s }", ".foo{transition:width 2s 1s}"); + minify_test(".foo { transition: width 2s ease 1s }", ".foo{transition:width 2s 1s}"); + minify_test(".foo { transition: ease-in 1s width 4s }", ".foo{transition:width 1s ease-in 4s}"); + minify_test(".foo { transition: opacity 0s .6s }", ".foo{transition:opacity 0s .6s}"); + cssTest( + ` + .foo { + transition-property: opacity; + transition-duration: 0.09s; + transition-timing-function: ease-in-out; + transition-delay: 500ms; + } + `, + ` + .foo { + transition: opacity 90ms ease-in-out .5s; + } + `, + ); + cssTest( + ` + .foo { + transition: opacity 2s; + transition-timing-function: ease; + transition-delay: 500ms; + } + `, + ` + .foo { + transition: opacity 2s .5s; + } + `, + ); + cssTest( + ` + .foo { + transition: opacity 500ms; + transition-timing-function: var(--ease); + } + `, + ` + .foo { + transition: opacity .5s; + transition-timing-function: var(--ease); + } + `, + ); + cssTest( + ` + .foo { + transition-property: opacity; + transition-duration: 0.09s; + transition-timing-function: ease-in-out; + transition-delay: 500ms; + transition: color 2s; + } + `, + ` + .foo { + transition: color 2s; + } + `, + ); + cssTest( + ` + .foo { + transition-property: opacity, color; + transition-duration: 2s, 4s; + transition-timing-function: ease-in-out, ease-in; + transition-delay: 500ms, 0s; + } + `, + ` + .foo { + transition: opacity 2s ease-in-out .5s, color 4s ease-in; + } + `, + ); + cssTest( + ` + .foo { + transition-property: opacity, color; + transition-duration: 2s; + transition-timing-function: ease-in-out; + transition-delay: 500ms; + } + `, + ` + .foo { + transition: opacity 2s ease-in-out .5s, color 2s ease-in-out .5s; + } + `, + ); + cssTest( + ` + .foo { + transition-property: opacity, color, width, height; + transition-duration: 2s, 4s; + transition-timing-function: ease; + transition-delay: 0s; + } + `, + ` + .foo { + transition: opacity 2s, color 4s, width 2s, height 4s; + } + `, + ); + + cssTest( + ` + .foo { + -webkit-transition-property: opacity, color; + -webkit-transition-duration: 2s, 4s; + -webkit-transition-timing-function: ease-in-out, ease-in; + -webkit-transition-delay: 500ms, 0s; + } + `, + ` + .foo { + -webkit-transition: opacity 2s ease-in-out .5s, color 4s ease-in; + } + `, + ); + + cssTest( + ` + .foo { + -webkit-transition-property: opacity, color; + -webkit-transition-duration: 2s, 4s; + -webkit-transition-timing-function: ease-in-out, ease-in; + -webkit-transition-delay: 500ms, 0s; + -moz-transition-property: opacity, color; + -moz-transition-duration: 2s, 4s; + -moz-transition-timing-function: ease-in-out, ease-in; + -moz-transition-delay: 500ms, 0s; + transition-property: opacity, color; + transition-duration: 2s, 4s; + transition-timing-function: ease-in-out, ease-in; + transition-delay: 500ms, 0s; + } + `, + ` + .foo { + -webkit-transition: opacity 2s ease-in-out .5s, color 4s ease-in; + -moz-transition: opacity 2s ease-in-out .5s, color 4s ease-in; + transition: opacity 2s ease-in-out .5s, color 4s ease-in; + } + `, + ); + + cssTest( + ` + .foo { + -webkit-transition-property: opacity, color; + -moz-transition-property: opacity, color; + transition-property: opacity, color; + -webkit-transition-duration: 2s, 4s; + -moz-transition-duration: 2s, 4s; + transition-duration: 2s, 4s; + -webkit-transition-timing-function: ease-in-out, ease-in; + transition-timing-function: ease-in-out, ease-in; + -moz-transition-timing-function: ease-in-out, ease-in; + -webkit-transition-delay: 500ms, 0s; + -moz-transition-delay: 500ms, 0s; + transition-delay: 500ms, 0s; + } + `, + ` + .foo { + -webkit-transition: opacity 2s ease-in-out .5s, color 4s ease-in; + -moz-transition: opacity 2s ease-in-out .5s, color 4s ease-in; + transition: opacity 2s ease-in-out .5s, color 4s ease-in; + } + `, + ); + + cssTest( + ` + .foo { + -webkit-transition-property: opacity; + -moz-transition-property: color; + transition-property: opacity, color; + -webkit-transition-duration: 2s; + -moz-transition-duration: 4s; + transition-duration: 2s, 4s; + -webkit-transition-timing-function: ease-in-out; + -moz-transition-timing-function: ease-in-out; + transition-timing-function: ease-in-out, ease-in; + -webkit-transition-delay: 500ms; + -moz-transition-delay: 0s; + transition-delay: 500ms, 0s; + } + `, + ` + .foo { + -webkit-transition-property: opacity; + -moz-transition-property: color; + transition-property: opacity, color; + -webkit-transition-duration: 2s; + -moz-transition-duration: 4s; + transition-duration: 2s, 4s; + -webkit-transition-timing-function: ease-in-out; + -moz-transition-timing-function: ease-in-out; + -webkit-transition-delay: .5s; + transition-timing-function: ease-in-out, ease-in; + -moz-transition-delay: 0s; + transition-delay: .5s, 0s; + } + `, + ); + + cssTest( + ` + .foo { + -webkit-transition-property: opacity; + transition-property: opacity, color; + -moz-transition-property: color; + -webkit-transition-duration: 2s; + transition-duration: 2s, 4s; + -moz-transition-duration: 4s; + -webkit-transition-timing-function: ease-in-out; + transition-timing-function: ease-in-out, ease-in; + -moz-transition-timing-function: ease-in-out; + -webkit-transition-delay: 500ms; + transition-delay: 500ms, 0s; + -moz-transition-delay: 0s; + } + `, + ` + .foo { + -webkit-transition-property: opacity; + transition-property: opacity, color; + -moz-transition-property: color; + -webkit-transition-duration: 2s; + transition-duration: 2s, 4s; + -moz-transition-duration: 4s; + -webkit-transition-timing-function: ease-in-out; + transition-timing-function: ease-in-out, ease-in; + -webkit-transition-delay: .5s; + -moz-transition-timing-function: ease-in-out; + transition-delay: .5s, 0s; + -moz-transition-delay: 0s; + } + `, + ); + + cssTest( + ` + .foo { + transition: opacity 2s; + -webkit-transition-duration: 2s; + } + `, + ` + .foo { + transition: opacity 2s; + -webkit-transition-duration: 2s; + } + `, + ); + + prefix_test( + ` + .foo { + transition-property: margin-inline-start; + } + `, + ` + .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) { + transition-property: margin-left; + } + + .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) { + transition-property: margin-left; + } + + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition-property: margin-right; + } + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition-property: margin-right; + } + `, + { + safari: Some(8 << 16), + }, + ); + + prefix_test( + ` + .foo { + transition-property: margin-inline-start, padding-inline-start; + } + `, + ` + .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) { + transition-property: margin-left, padding-left; + } + + .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) { + transition-property: margin-left, padding-left; + } + + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition-property: margin-right, padding-right; + } + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition-property: margin-right, padding-right; + } + `, + { + safari: Some(8 << 16), + }, + ); + + prefix_test( + ` + .foo { + transition-property: margin-inline-start, opacity, padding-inline-start, color; + } + `, + ` + .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) { + transition-property: margin-left, opacity, padding-left, color; + } + + .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) { + transition-property: margin-left, opacity, padding-left, color; + } + + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition-property: margin-right, opacity, padding-right, color; + } + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition-property: margin-right, opacity, padding-right, color; + } + `, + { + safari: Some(8 << 16), + }, + ); + + prefix_test( + ` + .foo { + transition-property: margin-block; + } + `, + ` + .foo { + transition-property: margin-top, margin-bottom; + } + `, + { + safari: Some(8 << 16), + }, + ); + + prefix_test( + ` + .foo { + transition: margin-inline-start 2s; + } + `, + ` + .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) { + transition: margin-left 2s; + } + + .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) { + transition: margin-left 2s; + } + + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition: margin-right 2s; + } + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition: margin-right 2s; + } + `, + { + safari: Some(8 << 16), + }, + ); + + prefix_test( + ` + .foo { + transition: margin-inline-start 2s, padding-inline-start 2s; + } + `, + ` + .foo:not(:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) { + transition: margin-left 2s, padding-left 2s; + } + + .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) { + transition: margin-left 2s, padding-left 2s; + } + + .foo:-webkit-any(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition: margin-right 2s, padding-right 2s; + } + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + transition: margin-right 2s, padding-right 2s; + } + `, + { + safari: Some(8 << 16), + }, + ); + + prefix_test( + ` + .foo { + transition: margin-block-start 2s; + } + `, + ` + .foo { + transition: margin-top 2s; + } + `, + { + safari: Some(8 << 16), + }, + ); + + prefix_test( + ` + .foo { + transition: transform; + } + `, + ` + .foo { + -webkit-transition: -webkit-transform, transform; + transition: -webkit-transform, transform; + } + `, + { + safari: Some(6 << 16), + }, + ); + + prefix_test( + ` + .foo { + transition: border-start-start-radius; + } + `, + ` + .foo:not(:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi))) { + -webkit-transition: -webkit-border-top-left-radius, border-top-left-radius; + transition: -webkit-border-top-left-radius, border-top-left-radius; + } + + .foo:is(:lang(ae), :lang(ar), :lang(arc), :lang(bcc), :lang(bqi), :lang(ckb), :lang(dv), :lang(fa), :lang(glk), :lang(he), :lang(ku), :lang(mzn), :lang(nqo), :lang(pnb), :lang(ps), :lang(sd), :lang(ug), :lang(ur), :lang(yi)) { + -webkit-transition: -webkit-border-top-right-radius, border-top-right-radius; + transition: -webkit-border-top-right-radius, border-top-right-radius; + } + `, + { + safari: Some(4 << 16), + }, + ); + + prefix_test( + ` + .foo { + transition: border-start-start-radius; + } + `, + ` + .foo:not(:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi)) { + transition: border-top-left-radius; + } + + .foo:lang(ae, ar, arc, bcc, bqi, ckb, dv, fa, glk, he, ku, mzn, nqo, pnb, ps, sd, ug, ur, yi) { + transition: border-top-right-radius; + } + `, + { + safari: Some(12 << 16), + }, + ); + + cssTest( + ` + .foo { + -webkit-transition: background 200ms; + -moz-transition: background 200ms; + transition: background 230ms; + } + `, + ` + .foo { + -webkit-transition: background .2s; + -moz-transition: background .2s; + transition: background .23s; + } + `, + ); + + prefix_test( + ` + .foo { + -webkit-transition: background 200ms; + -moz-transition: background 200ms; + transition: background 230ms; + } + `, + ` + .foo { + -webkit-transition: background .2s; + -moz-transition: background .2s; + transition: background .23s; + } + `, + { + chrome: Some(95 << 16), + }, + ); + }); + + describe("transform", () => { + minify_test(".foo { transform: translate(2px, 3px)", ".foo{transform:translate(2px,3px)}"); + minify_test(".foo { transform: translate(2px, 0px)", ".foo{transform:translate(2px)}"); + minify_test(".foo { transform: translate(0px, 2px)", ".foo{transform:translateY(2px)}"); + minify_test(".foo { transform: translateX(2px)", ".foo{transform:translate(2px)}"); + minify_test(".foo { transform: translateY(2px)", ".foo{transform:translateY(2px)}"); + minify_test(".foo { transform: translateZ(2px)", ".foo{transform:translateZ(2px)}"); + minify_test(".foo { transform: translate3d(2px, 3px, 4px)", ".foo{transform:translate3d(2px,3px,4px)}"); + minify_test(".foo { transform: translate3d(10%, 20%, 4px)", ".foo{transform:translate3d(10%,20%,4px)}"); + minify_test(".foo { transform: translate3d(2px, 0px, 0px)", ".foo{transform:translate(2px)}"); + minify_test(".foo { transform: translate3d(0px, 2px, 0px)", ".foo{transform:translateY(2px)}"); + minify_test(".foo { transform: translate3d(0px, 0px, 2px)", ".foo{transform:translateZ(2px)}"); + minify_test(".foo { transform: translate3d(2px, 3px, 0px)", ".foo{transform:translate(2px,3px)}"); + minify_test(".foo { transform: scale(2, 3)", ".foo{transform:scale(2,3)}"); + minify_test(".foo { transform: scale(10%, 20%)", ".foo{transform:scale(.1,.2)}"); + minify_test(".foo { transform: scale(2, 2)", ".foo{transform:scale(2)}"); + minify_test(".foo { transform: scale(2, 1)", ".foo{transform:scaleX(2)}"); + minify_test(".foo { transform: scale(1, 2)", ".foo{transform:scaleY(2)}"); + minify_test(".foo { transform: scaleX(2)", ".foo{transform:scaleX(2)}"); + minify_test(".foo { transform: scaleY(2)", ".foo{transform:scaleY(2)}"); + minify_test(".foo { transform: scaleZ(2)", ".foo{transform:scaleZ(2)}"); + minify_test(".foo { transform: scale3d(2, 3, 4)", ".foo{transform:scale3d(2,3,4)}"); + minify_test(".foo { transform: scale3d(2, 1, 1)", ".foo{transform:scaleX(2)}"); + minify_test(".foo { transform: scale3d(1, 2, 1)", ".foo{transform:scaleY(2)}"); + minify_test(".foo { transform: scale3d(1, 1, 2)", ".foo{transform:scaleZ(2)}"); + minify_test(".foo { transform: scale3d(2, 2, 1)", ".foo{transform:scale(2)}"); + minify_test(".foo { transform: rotate(20deg)", ".foo{transform:rotate(20deg)}"); + minify_test(".foo { transform: rotateX(20deg)", ".foo{transform:rotateX(20deg)}"); + minify_test(".foo { transform: rotateY(20deg)", ".foo{transform:rotateY(20deg)}"); + minify_test(".foo { transform: rotateZ(20deg)", ".foo{transform:rotate(20deg)}"); + minify_test(".foo { transform: rotate(360deg)", ".foo{transform:rotate(360deg)}"); + minify_test(".foo { transform: rotate3d(2, 3, 4, 20deg)", ".foo{transform:rotate3d(2,3,4,20deg)}"); + minify_test(".foo { transform: rotate3d(1, 0, 0, 20deg)", ".foo{transform:rotateX(20deg)}"); + minify_test(".foo { transform: rotate3d(0, 1, 0, 20deg)", ".foo{transform:rotateY(20deg)}"); + minify_test(".foo { transform: rotate3d(0, 0, 1, 20deg)", ".foo{transform:rotate(20deg)}"); + minify_test(".foo { transform: rotate(405deg)}", ".foo{transform:rotate(405deg)}"); + minify_test(".foo { transform: rotateX(405deg)}", ".foo{transform:rotateX(405deg)}"); + minify_test(".foo { transform: rotateY(405deg)}", ".foo{transform:rotateY(405deg)}"); + minify_test(".foo { transform: rotate(-200deg)}", ".foo{transform:rotate(-200deg)}"); + minify_test(".foo { transform: rotate(0)", ".foo{transform:rotate(0)}"); + minify_test(".foo { transform: rotate(0deg)", ".foo{transform:rotate(0)}"); + minify_test(".foo { transform: rotateX(-200deg)}", ".foo{transform:rotateX(-200deg)}"); + minify_test(".foo { transform: rotateY(-200deg)}", ".foo{transform:rotateY(-200deg)}"); + minify_test(".foo { transform: rotate3d(1, 1, 0, -200deg)", ".foo{transform:rotate3d(1,1,0,-200deg)}"); + minify_test(".foo { transform: skew(20deg)", ".foo{transform:skew(20deg)}"); + minify_test(".foo { transform: skew(20deg, 0deg)", ".foo{transform:skew(20deg)}"); + minify_test(".foo { transform: skew(0deg, 20deg)", ".foo{transform:skewY(20deg)}"); + minify_test(".foo { transform: skewX(20deg)", ".foo{transform:skew(20deg)}"); + minify_test(".foo { transform: skewY(20deg)", ".foo{transform:skewY(20deg)}"); + minify_test(".foo { transform: perspective(10px)", ".foo{transform:perspective(10px)}"); + minify_test(".foo { transform: matrix(1, 2, -1, 1, 80, 80)", ".foo{transform:matrix(1,2,-1,1,80,80)}"); + minify_test( + ".foo { transform: matrix3d(1, 0, 0, 0, 0, 1, 6, 0, 0, 0, 1, 0, 50, 100, 0, 1.1)", + ".foo{transform:matrix3d(1,0,0,0,0,1,6,0,0,0,1,0,50,100,0,1.1)}", + ); + // TODO: Re-enable with a better solution + // See: https://github.com/parcel-bundler/lightningcss/issues/288 + // minify_test( + // ".foo{transform:translate(100px,200px) rotate(45deg) skew(10deg) scale(2)}", + // ".foo{transform:matrix(1.41421,1.41421,-1.16485,1.66358,100,200)}", + // ); + // minify_test( + // ".foo{transform:translate(200px,300px) translate(100px,200px) scale(2)}", + // ".foo{transform:matrix(2,0,0,2,300,500)}", + // ); + minify_test( + ".foo{transform:translate(100px,200px) rotate(45deg)}", + ".foo{transform:translate(100px,200px)rotate(45deg)}", + ); + minify_test( + ".foo{transform:rotate3d(1, 1, 1, 45deg) translate3d(100px, 100px, 10px)}", + ".foo{transform:rotate3d(1,1,1,45deg)translate3d(100px,100px,10px)}", + ); + // TODO: Re-enable with a better solution + // See: https://github.com/parcel-bundler/lightningcss/issues/288 + // minify_test( + // ".foo{transform:translate3d(100px, 100px, 10px) skew(10deg) scale3d(2, 3, 4)}", + // ".foo{transform:matrix3d(2,0,0,0,.528981,3,0,0,0,0,4,0,100,100,10,1)}", + // ); + // minify_test( + // ".foo{transform:matrix3d(0.804737854124365, 0.5058793634016805, -0.31061721752604554, 0, -0.31061721752604554, 0.804737854124365, 0.5058793634016805, 0, 0.5058793634016805, -0.31061721752604554, 0.804737854124365, 0, 100, 100, 10, 1)}", + // ".foo{transform:translate3d(100px,100px,10px)rotate3d(1,1,1,45deg)}" + // ); + // minify_test( + // ".foo{transform:matrix3d(1, 0, 0, 0, 0, 0.7071067811865476, 0.7071067811865475, 0, 0, -0.7071067811865475, 0.7071067811865476, 0, 100, 100, 10, 1)}", + // ".foo{transform:translate3d(100px,100px,10px)rotateX(45deg)}" + // ); + // minify_test( + // ".foo{transform:translate3d(100px, 200px, 10px) translate(100px, 100px)}", + // ".foo{transform:translate3d(200px,300px,10px)}", + // ); + // minify_test( + // ".foo{transform:rotate(45deg) rotate(45deg)}", + // ".foo{transform:rotate(90deg)}", + // ); + // minify_test( + // ".foo{transform:matrix(0.7071067811865476, 0.7071067811865475, -0.7071067811865475, 0.7071067811865476, 100, 100)}", + // ".foo{transform:translate(100px,100px)rotate(45deg)}" + // ); + // minify_test( + // ".foo{transform:translateX(2in) translateX(50px)}", + // ".foo{transform:translate(242px)}", + // ); + minify_test(".foo{transform:translateX(calc(2in + 50px))}", ".foo{transform:translate(242px)}"); + minify_test(".foo{transform:translateX(50%)}", ".foo{transform:translate(50%)}"); + minify_test(".foo{transform:translateX(calc(50% - 100px + 20px))}", ".foo{transform:translate(calc(50% - 80px))}"); + minify_test(".foo{transform:rotate(calc(10deg + 20deg))}", ".foo{transform:rotate(30deg)}"); + minify_test(".foo{transform:rotate(calc(10deg + 0.349066rad))}", ".foo{transform:rotate(30deg)}"); + minify_test(".foo{transform:rotate(calc(10deg + 1.5turn))}", ".foo{transform:rotate(550deg)}"); + minify_test(".foo{transform:rotate(calc(10deg * 2))}", ".foo{transform:rotate(20deg)}"); + minify_test(".foo{transform:rotate(calc(-10deg * 2))}", ".foo{transform:rotate(-20deg)}"); + minify_test( + ".foo{transform:rotate(calc(10deg + var(--test)))}", + ".foo{transform:rotate(calc(10deg + var(--test)))}", + ); + minify_test(".foo { transform: scale(calc(10% + 20%))", ".foo{transform:scale(.3)}"); + minify_test(".foo { transform: scale(calc(.1 + .2))", ".foo{transform:scale(.3)}"); + + minify_test(".foo { -webkit-transform: scale(calc(10% + 20%))", ".foo{-webkit-transform:scale(.3)}"); + + minify_test(".foo { translate: 1px 2px 3px }", ".foo{translate:1px 2px 3px}"); + minify_test(".foo { translate: 1px 0px 0px }", ".foo{translate:1px}"); + minify_test(".foo { translate: 1px 2px 0px }", ".foo{translate:1px 2px}"); + minify_test(".foo { translate: 1px 0px 2px }", ".foo{translate:1px 0 2px}"); + minify_test(".foo { translate: none }", ".foo{translate:none}"); + minify_test(".foo { rotate: 10deg }", ".foo{rotate:10deg}"); + minify_test(".foo { rotate: z 10deg }", ".foo{rotate:10deg}"); + minify_test(".foo { rotate: 0 0 1 10deg }", ".foo{rotate:10deg}"); + minify_test(".foo { rotate: x 10deg }", ".foo{rotate:x 10deg}"); + minify_test(".foo { rotate: 1 0 0 10deg }", ".foo{rotate:x 10deg}"); + minify_test(".foo { rotate: y 10deg }", ".foo{rotate:y 10deg}"); + minify_test(".foo { rotate: 0 1 0 10deg }", ".foo{rotate:y 10deg}"); + minify_test(".foo { rotate: 1 1 1 10deg }", ".foo{rotate:1 1 1 10deg}"); + minify_test(".foo { rotate: 0 0 1 0deg }", ".foo{rotate:none}"); + minify_test(".foo { rotate: none }", ".foo{rotate:none}"); + minify_test(".foo { scale: 1 }", ".foo{scale:1}"); + minify_test(".foo { scale: 1 1 }", ".foo{scale:1}"); + minify_test(".foo { scale: 1 1 1 }", ".foo{scale:1}"); + minify_test(".foo { scale: none }", ".foo{scale:none}"); + minify_test(".foo { scale: 1 0 }", ".foo{scale:1 0}"); + minify_test(".foo { scale: 1 0 1 }", ".foo{scale:1 0}"); + minify_test(".foo { scale: 1 0 0 }", ".foo{scale:1 0 0}"); + + // TODO: Re-enable with a better solution + // See: https://github.com/parcel-bundler/lightningcss/issues/288 + // minify_test(".foo { transform: scale(3); scale: 0.5 }", ".foo{transform:scale(1.5)}"); + minify_test(".foo { scale: 0.5; transform: scale(3); }", ".foo{transform:scale(3)}"); + + prefix_test( + ` + .foo { + transform: scale(0.5); + } + `, + ` + .foo { + -webkit-transform: scale(.5); + -moz-transform: scale(.5); + transform: scale(.5); + } + `, + { + firefox: Some(6 << 16), + safari: Some(6 << 16), + }, + ); + + prefix_test( + ` + .foo { + transform: var(--transform); + } + `, + ` + .foo { + -webkit-transform: var(--transform); + -moz-transform: var(--transform); + transform: var(--transform); + } + `, + { + firefox: Some(6 << 16), + safari: Some(6 << 16), + }, + ); + + cssTest( + ` + .foo { + transform: translateX(-50%); + transform: translateX(20px); + } + `, + ` + .foo { + transform: translateX(20px); + } + `, + ); + }); }); diff --git a/test/js/bun/http/bun-serve-html.test.ts b/test/js/bun/http/bun-serve-html.test.ts index 2a8985f287..b6cc4ae3da 100644 --- a/test/js/bun/http/bun-serve-html.test.ts +++ b/test/js/bun/http/bun-serve-html.test.ts @@ -225,6 +225,7 @@ console.log("How...dashing?"); { const css = await (await fetch(cssSrc!)).text(); + /* the order of the properties may change because we made add more handlers to DeclarationHandler which changes the order in which they are flushed, but semantically it should be the same */ expect(css).toMatchInlineSnapshot(` "/* styles.css */ .container { @@ -236,11 +237,11 @@ console.log("How...dashing?"); button { cursor: pointer; - transition: all .2s; background: #fff; border: 2px solid #000; border-radius: .25rem; padding: .5rem 1rem; + transition: all .2s; font-size: 1.25rem; }