From dcf0b719a50952b5bee0ebce98451c7849de1e6d Mon Sep 17 00:00:00 2001 From: Zack Radisic <56137411+zackradisic@users.noreply.github.com> Date: Tue, 4 Feb 2025 22:50:41 -0800 Subject: [PATCH] CSS Fixes: light dark, color down-leveling bugs, implement minify for box-shadow (#17055) --- packages/bun-native-plugin-rs/Cargo.toml | 6 +- packages/bun-native-plugin-rs/src/lib.rs | 39 ++- src/css/context.zig | 6 +- src/css/declaration.zig | 8 + src/css/properties/box_shadow.zig | 143 ++++++++- src/css/properties/custom.zig | 5 +- src/css/properties/generate_properties.ts | 8 +- src/css/properties/properties_generated.zig | 45 ++- src/css/properties/ui.zig | 119 +++++++ src/css/rules/rules.zig | 4 +- src/css/values/color.zig | 24 +- src/css/values/length.zig | 4 + test/js/bun/css/css.test.ts | 332 +++++++++++++++++++- 13 files changed, 692 insertions(+), 51 deletions(-) diff --git a/packages/bun-native-plugin-rs/Cargo.toml b/packages/bun-native-plugin-rs/Cargo.toml index 8ab8e3fa7fb9b8..b6ebc1206391f9 100644 --- a/packages/bun-native-plugin-rs/Cargo.toml +++ b/packages/bun-native-plugin-rs/Cargo.toml @@ -2,16 +2,12 @@ name = "bun-native-plugin" description = "Rustified wrapper for writing native plugins for Bun." license = "MIT" -version = "0.1.2" +version = "0.2.0" edition = "2021" [dependencies] anyhow = "1.0.94" # use local path in dev and publish to crates.io in prod bun-macro = { path = "./bun-macro", version = "0.1.0" } -napi = { version = "2.14.1", default-features = false, features = ["napi4"] } -[features] -default = ["napi"] -napi = [] diff --git a/packages/bun-native-plugin-rs/src/lib.rs b/packages/bun-native-plugin-rs/src/lib.rs index e477bc5645b7a9..b732578a4c063b 100644 --- a/packages/bun-native-plugin-rs/src/lib.rs +++ b/packages/bun-native-plugin-rs/src/lib.rs @@ -473,6 +473,10 @@ impl<'a> OnBeforeParse<'a> { } } + /// # Safety + /// This is unsafe as you must ensure that no other invocation of the plugin (or JS!) + /// simultaneously holds a mutable reference to the external. + /// /// Get the external object from the `OnBeforeParse` arguments. /// /// The external object is set by the plugin definition inside of JS: @@ -521,42 +525,35 @@ impl<'a> OnBeforeParse<'a> { /// }, /// }; /// ``` - pub unsafe fn external(&self) -> PluginResult> { + pub unsafe fn external<'b, T: 'static + Sync>( + &self, + from_raw: unsafe fn(*mut c_void) -> Option<&'b T>, + ) -> PluginResult> { if unsafe { (*self.args_raw).external.is_null() } { return Ok(None); } - let external: *mut TaggedObject = - unsafe { (*self.args_raw).external as *mut TaggedObject }; - - unsafe { - if (*external).type_id != TypeId::of::() { - return Err(Error::ExternalTypeMismatch); - } + let external = unsafe { from_raw((*self.args_raw).external as *mut _) }; - Ok((*external).object.as_ref()) - } + Ok(external) } /// The same as [`crate::bun_native_plugin::OnBeforeParse::external`], but returns a mutable reference. /// - /// This is unsafe as you must ensure that no other invocation of the plugin + /// # Safety + /// This is unsafe as you must ensure that no other invocation of the plugin (or JS!) /// simultaneously holds a mutable reference to the external. - pub unsafe fn external_mut(&mut self) -> PluginResult> { + pub unsafe fn external_mut<'b, T: 'static + Sync>( + &mut self, + from_raw: unsafe fn(*mut c_void) -> Option<&'b mut T>, + ) -> PluginResult> { if unsafe { (*self.args_raw).external.is_null() } { return Ok(None); } - let external: *mut TaggedObject = - unsafe { (*self.args_raw).external as *mut TaggedObject }; + let external = unsafe { from_raw((*self.args_raw).external as *mut _) }; - unsafe { - if (*external).type_id != TypeId::of::() { - return Err(Error::ExternalTypeMismatch); - } - - Ok((*external).object.as_mut()) - } + Ok(external) } /// Get the input source code for the current file. diff --git a/src/css/context.zig b/src/css/context.zig index 09c9e59373c206..7a76deafbaedc0 100644 --- a/src/css/context.zig +++ b/src/css/context.zig @@ -81,6 +81,10 @@ pub const PropertyHandlerContext = struct { }; } + pub fn addDarkRule(this: *@This(), allocator: Allocator, property: css.Property) void { + this.dark.append(allocator, property) catch bun.outOfMemory(); + } + pub fn addLogicalRule(this: *@This(), allocator: Allocator, ltr: css.Property, rtl: css.Property) void { this.ltr.append(allocator, ltr) catch unreachable; this.rtl.append(allocator, rtl) catch unreachable; @@ -171,7 +175,7 @@ pub const PropertyHandlerContext = struct { .feature = MediaFeature{ .plain = .{ .name = .{ .standard = MediaFeatureId.@"prefers-color-scheme" }, - .value = .{ .ident = .{ .v = "dark " } }, + .value = .{ .ident = .{ .v = "dark" } }, }, }, }, diff --git a/src/css/declaration.zig b/src/css/declaration.zig index 6c09604cd3ad70..c2bb443209248b 100644 --- a/src/css/declaration.zig +++ b/src/css/declaration.zig @@ -27,6 +27,8 @@ 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 ColorSchemeHandler = css.css_properties.ui.ColorSchemeHandler; +const BoxShadowHandler = css.css_properties.box_shadow.BoxShadowHandler; // const GridHandler = css.css_properties.g /// A CSS declaration block. @@ -347,6 +349,8 @@ pub const DeclarationHandler = struct { font: FontHandler = .{}, inset: InsetHandler = .{}, transform: TransformHandler = .{}, + box_shadow: BoxShadowHandler = .{}, + color_scheme: ColorSchemeHandler = .{}, fallback: FallbackHandler = .{}, direction: ?css.css_properties.text.Direction, decls: DeclarationList, @@ -375,6 +379,8 @@ pub const DeclarationHandler = struct { this.font.finalize(&this.decls, context); this.inset.finalize(&this.decls, context); this.transform.finalize(&this.decls, context); + this.box_shadow.finalize(&this.decls, context); + this.color_scheme.finalize(&this.decls, context); this.fallback.finalize(&this.decls, context); } @@ -392,6 +398,8 @@ pub const DeclarationHandler = struct { 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.box_shadow.handleProperty(property, &this.decls, context) or + this.color_scheme.handleProperty(property, &this.decls, context) or this.fallback.handleProperty(property, &this.decls, context); } diff --git a/src/css/properties/box_shadow.zig b/src/css/properties/box_shadow.zig index dea6c1bf535f01..472ca82ee87ab3 100644 --- a/src/css/properties/box_shadow.zig +++ b/src/css/properties/box_shadow.zig @@ -19,10 +19,15 @@ 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 Length = css.css_values.length.Length; const Rect = css.css_values.rect.Rect; const NumberOrPercentage = css.css_values.percentage.NumberOrPercentage; +const VendorPrefix = css.VendorPrefix; +const Property = css.Property; +const PropertyId = css.PropertyId; +const Feature = css.prefixes.Feature; + /// A value for the [box-shadow](https://drafts.csswg.org/css-backgrounds/#box-shadow) property. pub const BoxShadow = struct { /// The color of the box shadow. @@ -128,4 +133,140 @@ pub const BoxShadow = struct { pub fn eql(lhs: *const @This(), rhs: *const @This()) bool { return css.implementEql(@This(), lhs, rhs); } + + pub fn isCompatible(this: *const @This(), browsers: css.targets.Browsers) bool { + return this.color.isCompatible(browsers) and + this.x_offset.isCompatible(browsers) and + this.y_offset.isCompatible(browsers) and + this.blur.isCompatible(browsers) and + this.spread.isCompatible(browsers); + } +}; + +pub const BoxShadowHandler = struct { + box_shadows: ?struct { SmallList(BoxShadow, 1), VendorPrefix } = null, + flushed: bool = false, + + pub fn handleProperty(this: *@This(), property: *const Property, dest: *css.DeclarationList, context: *css.PropertyHandlerContext) bool { + switch (property.*) { + .@"box-shadow" => |*b| { + const box_shadows: *const SmallList(BoxShadow, 1) = &b.*[0]; + const prefix: VendorPrefix = b.*[1]; + if (this.box_shadows != null and context.targets.browsers != null and !box_shadows.isCompatible(context.targets.browsers.?)) { + this.flush(dest, context); + } + + if (this.box_shadows) |*bxs| { + const val: *SmallList(BoxShadow, 1) = &bxs.*[0]; + const prefixes: *VendorPrefix = &bxs.*[1]; + if (!val.eql(box_shadows) and !prefixes.contains(prefix)) { + this.flush(dest, context); + this.box_shadows = .{ + box_shadows.deepClone(context.allocator), + prefix, + }; + } else { + val.* = box_shadows.deepClone(context.allocator); + prefixes.insert(prefix); + } + } else { + this.box_shadows = .{ + box_shadows.deepClone(context.allocator), + prefix, + }; + } + }, + .unparsed => |unp| { + if (unp.property_id == .@"box-shadow") { + this.flush(dest, context); + + var unparsed = unp.deepClone(context.allocator); + context.addUnparsedFallbacks(&unparsed); + dest.append(context.allocator, .{ .unparsed = unparsed }) catch bun.outOfMemory(); + this.flushed = true; + } else return false; + }, + else => return false, + } + + return true; + } + + pub fn finalize(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext) void { + this.flush(dest, context); + this.flushed = false; + } + + pub fn flush(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext) void { + if (this.box_shadows == null) return; + + const box_shadows: SmallList(BoxShadow, 1), const prefixes2: VendorPrefix = bun.take(&this.box_shadows) orelse { + this.flushed = true; + return; + }; + + if (!this.flushed) { + const ColorFallbackKind = css.ColorFallbackKind; + var prefixes = context.targets.prefixes(prefixes2, Feature.box_shadow); + var fallbacks = ColorFallbackKind.empty(); + for (box_shadows.slice()) |*shadow| { + fallbacks.insert(shadow.color.getNecessaryFallbacks(context.targets)); + } + + if (fallbacks.contains(ColorFallbackKind{ .rgb = true })) { + var rgb = SmallList(BoxShadow, 1).initCapacity(context.allocator, box_shadows.len()); + rgb.setLen(box_shadows.len()); + for (box_shadows.slice(), rgb.slice_mut()) |*input, *output| { + output.color = input.color.toRGB(context.allocator) orelse input.color.deepClone(context.allocator); + const fields = std.meta.fields(BoxShadow); + inline for (fields) |field| { + if (comptime std.mem.eql(u8, field.name, "color")) continue; + @field(output, field.name) = css.generic.deepClone(field.type, &@field(input, field.name), context.allocator); + } + } + + dest.append(context.allocator, .{ .@"box-shadow" = .{ rgb, prefixes } }) catch bun.outOfMemory(); + if (prefixes.contains(VendorPrefix.NONE)) { + prefixes = VendorPrefix.NONE; + } else { + // Only output RGB for prefixed property (e.g. -webkit-box-shadow) + return; + } + } + + if (fallbacks.contains(ColorFallbackKind.P3)) { + var p3 = SmallList(BoxShadow, 1).initCapacity(context.allocator, box_shadows.len()); + p3.setLen(box_shadows.len()); + for (box_shadows.slice(), p3.slice_mut()) |*input, *output| { + output.color = input.color.toP3(context.allocator) orelse input.color.deepClone(context.allocator); + const fields = std.meta.fields(BoxShadow); + inline for (fields) |field| { + if (comptime std.mem.eql(u8, field.name, "color")) continue; + @field(output, field.name) = css.generic.deepClone(field.type, &@field(input, field.name), context.allocator); + } + } + dest.append(context.allocator, .{ .@"box-shadow" = .{ p3, VendorPrefix.NONE } }) catch bun.outOfMemory(); + } + + if (fallbacks.contains(ColorFallbackKind.LAB)) { + var lab = SmallList(BoxShadow, 1).initCapacity(context.allocator, box_shadows.len()); + lab.setLen(box_shadows.len()); + for (box_shadows.slice(), lab.slice_mut()) |*input, *output| { + output.color = input.color.toLAB(context.allocator) orelse input.color.deepClone(context.allocator); + const fields = std.meta.fields(BoxShadow); + inline for (fields) |field| { + if (comptime std.mem.eql(u8, field.name, "color")) continue; + @field(output, field.name) = css.generic.deepClone(field.type, &@field(input, field.name), context.allocator); + } + } + dest.append(context.allocator, .{ .@"box-shadow" = .{ lab, VendorPrefix.NONE } }) catch bun.outOfMemory(); + } else { + dest.append(context.allocator, .{ .@"box-shadow" = .{ box_shadows, prefixes } }) catch bun.outOfMemory(); + } + } else { + dest.append(context.allocator, .{ .@"box-shadow" = .{ box_shadows, prefixes2 } }) catch bun.outOfMemory(); + } + + this.flushed = true; + } }; diff --git a/src/css/properties/custom.zig b/src/css/properties/custom.zig index d955f6566704f4..29ac1c3f87a85d 100644 --- a/src/css/properties/custom.zig +++ b/src/css/properties/custom.zig @@ -859,13 +859,12 @@ pub const UnresolvedColor = union(enum) { const dark: *const TokenList = &ld.dark; if (!dest.targets.isCompatible(.light_dark)) { - // TODO(zack): lightningcss -> buncss - try dest.writeStr("var(--lightningcss-light)"); + try dest.writeStr("var(--buncss-light"); try dest.delim(',', false); try light.toCss(W, dest, is_custom_property); try dest.writeChar(')'); try dest.whitespace(); - try dest.writeStr("var(--lightningcss-dark"); + try dest.writeStr("var(--buncss-dark"); try dest.delim(',', false); try dark.toCss(W, dest, is_custom_property); return dest.writeChar(')'); diff --git a/src/css/properties/generate_properties.ts b/src/css/properties/generate_properties.ts index e78ec97844dcd7..2dbe7defa422cf 100644 --- a/src/css/properties/generate_properties.ts +++ b/src/css/properties/generate_properties.ts @@ -1755,9 +1755,9 @@ generateCode({ // "view-transition-name": { // ty: "CustomIdent", // }, - // "color-scheme": { - // ty: "ColorScheme", - // }, + "color-scheme": { + ty: "ColorScheme", + }, }); function prelude() { @@ -1781,6 +1781,8 @@ const CustomProperty = css.css_properties.custom.CustomProperty; const Targets = css.targets.Targets; const Feature = css.prefixes.Feature; +const ColorScheme = css.css_properties.ui.ColorScheme; + const TransformList = css.css_properties.transform.TransformList; const TransformStyle = css.css_properties.transform.TransformStyle; const TransformBox = css.css_properties.transform.TransformBox; diff --git a/src/css/properties/properties_generated.zig b/src/css/properties/properties_generated.zig index 676fe2b96376e7..16e6bc6d97af8e 100644 --- a/src/css/properties/properties_generated.zig +++ b/src/css/properties/properties_generated.zig @@ -17,6 +17,8 @@ const CustomProperty = css.css_properties.custom.CustomProperty; const Targets = css.targets.Targets; const Feature = css.prefixes.Feature; +const ColorScheme = css.css_properties.ui.ColorScheme; + const TransformList = css.css_properties.transform.TransformList; const TransformStyle = css.css_properties.transform.TransformStyle; const TransformBox = css.css_properties.transform.TransformBox; @@ -501,6 +503,7 @@ pub const Property = union(PropertyIdTag) { @"mask-box-image-width": struct { Rect(BorderImageSideWidth), VendorPrefix }, @"mask-box-image-outset": struct { Rect(LengthOrNumber), VendorPrefix }, @"mask-box-image-repeat": struct { BorderImageRepeat, VendorPrefix }, + @"color-scheme": ColorScheme, all: CSSWideKeyword, unparsed: UnparsedProperty, custom: CustomProperty, @@ -4295,6 +4298,22 @@ pub const Property = union(PropertyIdTag) { compile_error = compile_error ++ @typeName(BorderImageRepeat) ++ ": does not have a eql() function.\n"; } + if (!@hasDecl(ColorScheme, "deepClone")) { + compile_error = compile_error ++ @typeName(ColorScheme) ++ ": does not have a deepClone() function.\n"; + } + + if (!@hasDecl(ColorScheme, "parse")) { + compile_error = compile_error ++ @typeName(ColorScheme) ++ ": does not have a parse() function.\n"; + } + + if (!@hasDecl(ColorScheme, "toCss")) { + compile_error = compile_error ++ @typeName(ColorScheme) ++ ": does not have a toCss() function.\n"; + } + + if (!@hasDecl(ColorScheme, "eql")) { + compile_error = compile_error ++ @typeName(ColorScheme) ++ ": does not have a eql() function.\n"; + } + const final_compile_error = compile_error; break :compile_error final_compile_error; }; @@ -6027,6 +6046,13 @@ pub const Property = union(PropertyIdTag) { } } }, + .@"color-scheme" => { + if (css.generic.parseWithOptions(ColorScheme, input, options).asValue()) |c| { + if (input.expectExhausted().isOk()) { + return .{ .result = .{ .@"color-scheme" = c } }; + } + } + }, .all => return .{ .result = .{ .all = switch (CSSWideKeyword.parse(input)) { .result => |v| v, .err => |e| return .{ .err = e }, @@ -6296,6 +6322,7 @@ pub const Property = union(PropertyIdTag) { .@"mask-box-image-width" => |*v| PropertyId{ .@"mask-box-image-width" = v[1] }, .@"mask-box-image-outset" => |*v| PropertyId{ .@"mask-box-image-outset" = v[1] }, .@"mask-box-image-repeat" => |*v| PropertyId{ .@"mask-box-image-repeat" = v[1] }, + .@"color-scheme" => .@"color-scheme", .all => PropertyId.all, .unparsed => |unparsed| unparsed.property_id, .custom => |c| .{ .custom = c.name }, @@ -6549,6 +6576,7 @@ pub const Property = union(PropertyIdTag) { .@"mask-box-image-width" => |*v| .{ .@"mask-box-image-width" = .{ v[0].deepClone(allocator), v[1] } }, .@"mask-box-image-outset" => |*v| .{ .@"mask-box-image-outset" = .{ v[0].deepClone(allocator), v[1] } }, .@"mask-box-image-repeat" => |*v| .{ .@"mask-box-image-repeat" = .{ v[0].deepClone(allocator), v[1] } }, + .@"color-scheme" => |*v| .{ .@"color-scheme" = v.deepClone(allocator) }, .all => |*a| return .{ .all = a.deepClone(allocator) }, .unparsed => |*u| return .{ .unparsed = u.deepClone(allocator) }, .custom => |*c| return .{ .custom = c.deepClone(allocator) }, @@ -6812,6 +6840,7 @@ pub const Property = union(PropertyIdTag) { .@"mask-box-image-width" => |*x| .{ "mask-box-image-width", x.@"1" }, .@"mask-box-image-outset" => |*x| .{ "mask-box-image-outset", x.@"1" }, .@"mask-box-image-repeat" => |*x| .{ "mask-box-image-repeat", x.@"1" }, + .@"color-scheme" => .{ "color-scheme", VendorPrefix{ .none = true } }, .all => .{ "all", VendorPrefix{ .none = true } }, .unparsed => |*unparsed| brk: { var prefix = unparsed.property_id.prefix(); @@ -7072,6 +7101,7 @@ pub const Property = union(PropertyIdTag) { .@"mask-box-image-width" => |*value| value[0].toCss(W, dest), .@"mask-box-image-outset" => |*value| value[0].toCss(W, dest), .@"mask-box-image-repeat" => |*value| value[0].toCss(W, dest), + .@"color-scheme" => |*value| value.toCss(W, dest), .all => |*keyword| keyword.toCss(W, dest), .unparsed => |*unparsed| unparsed.value.toCss(W, dest, false), .custom => |*c| c.value.toCss(W, dest, c.name == .custom), @@ -7402,6 +7432,7 @@ pub const Property = union(PropertyIdTag) { .@"mask-box-image-width" => |*v| css.generic.eql(Rect(BorderImageSideWidth), &v[0], &rhs.@"mask-box-image-width"[0]) and v[1].eq(rhs.@"mask-box-image-width"[1]), .@"mask-box-image-outset" => |*v| css.generic.eql(Rect(LengthOrNumber), &v[0], &rhs.@"mask-box-image-outset"[0]) and v[1].eq(rhs.@"mask-box-image-outset"[1]), .@"mask-box-image-repeat" => |*v| css.generic.eql(BorderImageRepeat, &v[0], &rhs.@"mask-box-image-repeat"[0]) and v[1].eq(rhs.@"mask-box-image-repeat"[1]), + .@"color-scheme" => |*v| css.generic.eql(ColorScheme, v, &rhs.@"color-scheme"), .unparsed => |*u| u.eql(&rhs.unparsed), .all => true, .custom => |*c| c.eql(&rhs.custom), @@ -7654,6 +7685,7 @@ pub const PropertyId = union(PropertyIdTag) { @"mask-box-image-width": VendorPrefix, @"mask-box-image-outset": VendorPrefix, @"mask-box-image-repeat": VendorPrefix, + @"color-scheme", all, unparsed, custom: CustomPropertyName, @@ -7914,12 +7946,13 @@ pub const PropertyId = union(PropertyIdTag) { .@"mask-box-image-width" => |p| p, .@"mask-box-image-outset" => |p| p, .@"mask-box-image-repeat" => |p| p, + .@"color-scheme" => VendorPrefix.empty(), .all, .custom, .unparsed => VendorPrefix.empty(), }; } 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, @"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 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", @"color-scheme" }; const Map = comptime bun.ComptimeEnumMap(Enum); if (Map.getASCIIICaseInsensitive(name1)) |prop| { switch (prop) { @@ -8903,6 +8936,10 @@ pub const PropertyId = union(PropertyIdTag) { const allowed_prefixes = VendorPrefix{ .none = true, .webkit = true }; if (allowed_prefixes.contains(pre)) return .{ .@"mask-box-image-repeat" = pre }; }, + .@"color-scheme" => { + const allowed_prefixes = VendorPrefix{ .none = true }; + if (allowed_prefixes.contains(pre)) return .@"color-scheme"; + }, } } @@ -9156,6 +9193,7 @@ pub const PropertyId = union(PropertyIdTag) { .@"mask-box-image-width" => .{ .@"mask-box-image-width" = pre }, .@"mask-box-image-outset" => .{ .@"mask-box-image-outset" = pre }, .@"mask-box-image-repeat" => .{ .@"mask-box-image-repeat" = pre }, + .@"color-scheme" => .@"color-scheme", else => this.*, }; } @@ -9537,6 +9575,7 @@ pub const PropertyId = union(PropertyIdTag) { .@"mask-box-image-repeat" => |*p| { p.insert(pre); }, + .@"color-scheme" => {}, else => {}, }; } @@ -9937,6 +9976,7 @@ pub const PropertyId = union(PropertyIdTag) { .@"mask-box-image-width" => {}, .@"mask-box-image-outset" => {}, .@"mask-box-image-repeat" => {}, + .@"color-scheme" => {}, else => {}, } } @@ -10187,6 +10227,7 @@ pub const PropertyIdTag = enum(u16) { @"mask-box-image-width", @"mask-box-image-outset", @"mask-box-image-repeat", + @"color-scheme", all, unparsed, custom, @@ -10440,6 +10481,7 @@ pub const PropertyIdTag = enum(u16) { .@"mask-box-image-width" => true, .@"mask-box-image-outset" => true, .@"mask-box-image-repeat" => true, + .@"color-scheme" => false, .unparsed => false, .custom => false, .all => false, @@ -10695,6 +10737,7 @@ pub const PropertyIdTag = enum(u16) { .@"mask-box-image-width" => Rect(BorderImageSideWidth), .@"mask-box-image-outset" => Rect(LengthOrNumber), .@"mask-box-image-repeat" => BorderImageRepeat, + .@"color-scheme" => ColorScheme, .all => CSSWideKeyword, .unparsed => UnparsedProperty, .custom => CustomProperty, diff --git a/src/css/properties/ui.zig b/src/css/properties/ui.zig index 58b7cec84d52c4..3740c9418eef30 100644 --- a/src/css/properties/ui.zig +++ b/src/css/properties/ui.zig @@ -41,6 +41,69 @@ pub const ColorScheme = packed struct(u8) { dark: bool = false, /// Forbids the user agent from overriding the color scheme for the element. only: bool = false, + __unused: u5 = 0, + + pub usingnamespace css.Bitflags(@This()); + + const Map = bun.ComptimeEnumMap(enum { normal, only, light, dark }); + + pub fn parse(input: *css.Parser) css.Result(ColorScheme) { + var res = ColorScheme.empty(); + const ident = switch (input.expectIdent()) { + .result => |ident| ident, + .err => |e| return .{ .err = e }, + }; + + if (Map.get(ident)) |value| switch (value) { + .normal => return .{ .result = res }, + .only => res.insert(ColorScheme{ .only = true }), + .light => res.insert(ColorScheme{ .light = true }), + .dark => res.insert(ColorScheme{ .dark = true }), + }; + + while (input.tryParse(css.Parser.expectIdent, .{}).asValue()) |i| { + if (Map.get(i)) |value| switch (value) { + .normal => return .{ .err = input.newCustomError(css.ParserError.invalid_value) }, + .only => { + // Only must be at the start or the end, not in the middle + if (res.contains(ColorScheme{ .only = true })) { + return .{ .err = input.newCustomError(css.ParserError.invalid_value) }; + } + res.insert(ColorScheme{ .only = true }); + return .{ .result = res }; + }, + .light => res.insert(ColorScheme{ .light = true }), + .dark => res.insert(ColorScheme{ .dark = true }), + }; + } + + return .{ .result = res }; + } + + pub fn toCss(this: *const ColorScheme, comptime W: type, dest: *Printer(W)) css.PrintErr!void { + if (this.isEmpty()) { + return dest.writeStr("normal"); + } + + if (this.contains(ColorScheme{ .light = true })) { + try dest.writeStr("light"); + if (this.contains(ColorScheme{ .dark = true })) { + try dest.writeChar(' '); + } + } + + if (this.contains(ColorScheme{ .dark = true })) { + try dest.writeStr("dark"); + } + + if (this.contains(ColorScheme{ .only = true })) { + try dest.writeStr(" only"); + } + } + + pub fn deepClone(this: *const @This(), allocator: Allocator) @This() { + return css.implementDeepClone(@This(), this, allocator); + } }; /// A value for the [resize](https://www.w3.org/TR/2021/WD-css-ui-4-20210316/#resize) property. @@ -107,3 +170,59 @@ pub const Appearance = union(enum) { textarea, non_standard: []const u8, }; + +pub const ColorSchemeHandler = struct { + pub fn handleProperty(_: *@This(), property: *const css.Property, dest: *css.DeclarationList, context: *css.PropertyHandlerContext) bool { + switch (property.*) { + .@"color-scheme" => |*color_scheme_| { + const color_scheme: *const ColorScheme = color_scheme_; + if (!context.targets.isCompatible(css.compat.Feature.light_dark)) { + if (color_scheme.contains(ColorScheme{ .light = true })) { + dest.append( + context.allocator, + defineVar(context.allocator, "--buncss-light", .{ .ident = "initial" }), + ) catch bun.outOfMemory(); + dest.append( + context.allocator, + defineVar(context.allocator, "--buncss-dark", .{ .whitespace = " " }), + ) catch bun.outOfMemory(); + + if (color_scheme.contains(ColorScheme{ .dark = true })) { + context.addDarkRule( + context.allocator, + defineVar(context.allocator, "--buncss-light", .{ .whitespace = " " }), + ); + context.addDarkRule( + context.allocator, + defineVar(context.allocator, "--buncss-dark", .{ .ident = "initial" }), + ); + } + } else if (color_scheme.contains(ColorScheme{ .dark = true })) { + dest.append(context.allocator, defineVar(context.allocator, "--buncss-light", .{ .whitespace = " " })) catch bun.outOfMemory(); + dest.append(context.allocator, defineVar(context.allocator, "--buncss-dark", .{ .ident = "initial" })) catch bun.outOfMemory(); + } + } + dest.append(context.allocator, property.deepClone(context.allocator)) catch bun.outOfMemory(); + return true; + }, + else => return false, + } + } + + pub fn finalize(_: *@This(), _: *css.DeclarationList, _: *css.PropertyHandlerContext) void {} +}; + +fn defineVar(allocator: Allocator, name: []const u8, value: css.Token) css.Property { + return css.Property{ + .custom = css.css_properties.custom.CustomProperty{ + .name = css.css_properties.custom.CustomPropertyName{ .custom = css.DashedIdent{ .v = name } }, + .value = css.TokenList{ + .v = brk: { + var list = ArrayList(css.css_properties.custom.TokenOrValue){}; + list.append(allocator, css.css_properties.custom.TokenOrValue{ .token = value }) catch bun.outOfMemory(); + break :brk list; + }, + }, + }, + }; +} diff --git a/src/css/rules/rules.zig b/src/css/rules/rules.zig index 25f7bdec6b3c59..191e55240efb6c 100644 --- a/src/css/rules/rules.zig +++ b/src/css/rules/rules.zig @@ -409,7 +409,9 @@ pub fn CssRuleList(comptime AtRule: type) type { } if (logical.items.len > 0) { - debug("Adding logical: {}\n", .{logical.items[0].style.selectors.debug()}); + if (bun.Environment.isDebug and logical.items[0] == .style) { + debug("Adding logical: {}\n", .{logical.items[0].style.selectors.debug()}); + } var log = CssRuleList(AtRule){ .v = logical }; try log.minify(context, parent_is_unused); rules.appendSlice(context.allocator, log.v.items) catch bun.outOfMemory(); diff --git a/src/css/values/color.zig b/src/css/values/color.zig index 01ef1cf9c1c7db..31331bd72aed4d 100644 --- a/src/css/values/color.zig +++ b/src/css/values/color.zig @@ -230,16 +230,14 @@ pub const CssColor = union(enum) { }, .light_dark => |*light_dark| { if (!dest.targets.isCompatible(css.compat.Feature.light_dark)) { - // TODO(zack): lightningcss -> buncss - try dest.writeStr("var(--lightningcss-light"); + try dest.writeStr("var(--buncss-light"); try dest.delim(',', false); try light_dark.light.toCss(W, dest); try dest.writeChar(')'); try dest.whitespace(); - try dest.writeStr("var(--lightningcss-dark"); + try dest.writeStr("var(--buncss-dark"); try dest.delim(',', false); try light_dark.dark.toCss(W, dest); - try light_dark.dark.toCss(W, dest); return dest.writeChar(')'); } @@ -365,7 +363,7 @@ pub const CssColor = union(enum) { if (this.* == .light_dark or other.* == .light_dark) { const this_light_dark = this.toLightDark(allocator); - const other_light_dark = this.toLightDark(allocator); + const other_light_dark = other.toLightDark(allocator); const al = this_light_dark.light_dark.light; const ad = this_light_dark.light_dark.dark; @@ -508,7 +506,8 @@ pub const CssColor = union(enum) { } if (fallbacks.contains(ColorFallbackKind{ .lab = true })) { - this.* = this.toLAB(allocator).?; + const foo = this.toLAB(allocator).?; + this.* = foo; } return res; @@ -532,7 +531,7 @@ pub const CssColor = union(enum) { if (lab.* == .lab or lab.* == .lch and targets.shouldCompileSame(.lab_colors)) break :brk ColorFallbackKind.andBelow(.{ .lab = true }); if (lab.* == .oklab or lab.* == .oklch and targets.shouldCompileSame(.oklab_colors)) - break :brk ColorFallbackKind.andBelow(.{ .lab = true }); + break :brk ColorFallbackKind.andBelow(.{ .oklab = true }); return ColorFallbackKind.empty(); }, .predefined => |predefined| brk: { @@ -2791,6 +2790,7 @@ pub fn parseColorMix(input: *css.Parser) Result(CssColor) { } else .{ .result = HueInterpolationMethod.shorter }; const hue_method = hue_method_.unwrapOr(HueInterpolationMethod.shorter); + if (input.expectComma().asErr()) |e| return .{ .err = e }; const first_percent_ = input.tryParse(css.Parser.expectPercentage, .{}); const first_color = switch (CssColor.parse(input)) { @@ -2820,9 +2820,9 @@ pub fn parseColorMix(input: *css.Parser) Result(CssColor) { }; // https://drafts.csswg.org/css-color-5/#color-mix-percent-norm - const p1, const p2 = if (first_percent == null and second_percent == null) .{ 0.5, 0.5 } else brk: { - const p2 = second_percent orelse (1.0 - first_percent.?); - const p1 = first_percent orelse (1.0 - second_percent.?); + const p1: f32, const p2: f32 = if (first_percent == null and second_percent == null) .{ @as(f32, 0.5), @as(f32, 0.5) } else brk: { + const p2 = second_percent orelse (@as(f32, 1.0) - first_percent.?); + const p1 = first_percent orelse (@as(f32, 1.0) - second_percent.?); break :brk .{ p1, p2 }; }; @@ -4011,7 +4011,7 @@ const color_conversions = struct { const xyz = _xyz.resolveMissing(); const x = xyz.x / D50[0]; const y = xyz.y / D50[1]; - const z = xyz.y / D50[2]; + const z = xyz.z / D50[2]; // now compute f @@ -4023,7 +4023,7 @@ const color_conversions = struct { const l = ((116.0 * f1) - 16.0) / 100.0; const a = 500.0 * (f0 - f1); - const b = 500.0 * (f1 - f2); + const b = 200.0 * (f1 - f2); return LAB{ .l = l, diff --git a/src/css/values/length.zig b/src/css/values/length.zig index b256a0c463700e..2983788a971602 100644 --- a/src/css/values/length.zig +++ b/src/css/values/length.zig @@ -524,6 +524,10 @@ pub const Length = union(enum) { /// A computed length value using `calc()`. calc: *Calc(Length), + pub fn zero() Length { + return .{ .value = LengthValue.zero() }; + } + pub fn deepClone(this: *const Length, allocator: Allocator) Length { return switch (this.*) { .value => |v| .{ .value = v }, diff --git a/test/js/bun/css/css.test.ts b/test/js/bun/css/css.test.ts index 1d60d337f6c627..375e1317272802 100644 --- a/test/js/bun/css/css.test.ts +++ b/test/js/bun/css/css.test.ts @@ -1580,6 +1580,175 @@ describe("css tests", () => { ); }); + describe("box-shadow", () => { + minify_test( + ".foo { box-shadow: 64px 64px 12px 40px rgba(0,0,0,0.4) }", + ".foo{box-shadow:64px 64px 12px 40px #0006}", + ); + minify_test( + ".foo { box-shadow: 12px 12px 0px 8px rgba(0,0,0,0.4) inset }", + ".foo{box-shadow:inset 12px 12px 0 8px #0006}", + ); + minify_test( + ".foo { box-shadow: inset 12px 12px 0px 8px rgba(0,0,0,0.4) }", + ".foo{box-shadow:inset 12px 12px 0 8px #0006}", + ); + minify_test(".foo { box-shadow: 12px 12px 8px 0px rgba(0,0,0,0.4) }", ".foo{box-shadow:12px 12px 8px #0006}"); + minify_test(".foo { box-shadow: 12px 12px 0px 0px rgba(0,0,0,0.4) }", ".foo{box-shadow:12px 12px #0006}"); + minify_test( + ".foo { box-shadow: 64px 64px 12px 40px rgba(0,0,0,0.4), 12px 12px 0px 8px rgba(0,0,0,0.4) inset }", + ".foo{box-shadow:64px 64px 12px 40px #0006,inset 12px 12px 0 8px #0006}", + ); + + prefix_test( + ".foo { box-shadow: 12px 12px lab(40% 56.6 39) }", + `.foo { + box-shadow: 12px 12px #b32323; + box-shadow: 12px 12px lab(40% 56.6 39); + } + `, + { chrome: Some(90 << 16) }, + ); + + prefix_test( + ".foo { box-shadow: 12px 12px lab(40% 56.6 39) }", + `.foo { + -webkit-box-shadow: 12px 12px #b32323; + box-shadow: 12px 12px #b32323; + box-shadow: 12px 12px lab(40% 56.6 39); + } + `, + { chrome: Some(4 << 16) }, + ); + + prefix_test( + ".foo { box-shadow: 12px 12px lab(40% 56.6 39), 12px 12px yellow }", + `.foo { + -webkit-box-shadow: 12px 12px #b32323, 12px 12px #ff0; + box-shadow: 12px 12px #b32323, 12px 12px #ff0; + box-shadow: 12px 12px lab(40% 56.6 39), 12px 12px #ff0; + } + `, + { chrome: Some(4 << 16) }, + ); + + prefix_test( + ".foo { -webkit-box-shadow: 12px 12px #0006 }", + `.foo { + -webkit-box-shadow: 12px 12px rgba(0, 0, 0, .4); + } + `, + { chrome: Some(4 << 16) }, + ); + + prefix_test( + `.foo { + -webkit-box-shadow: 12px 12px #0006; + -moz-box-shadow: 12px 12px #0009; + }`, + `.foo { + -webkit-box-shadow: 12px 12px rgba(0, 0, 0, .4); + -moz-box-shadow: 12px 12px rgba(0, 0, 0, .6); + } + `, + { chrome: Some(4 << 16) }, + ); + + prefix_test( + `.foo { + -webkit-box-shadow: 12px 12px #0006; + -moz-box-shadow: 12px 12px #0006; + box-shadow: 12px 12px #0006; + }`, + `.foo { + box-shadow: 12px 12px #0006; + } + `, + { chrome: Some(95 << 16) }, + ); + + prefix_test( + ".foo { box-shadow: var(--foo) 12px lab(40% 56.6 39) }", + `.foo { + box-shadow: var(--foo) 12px #b32323; + } + + @supports (color: lab(0% 0 0)) { + .foo { + box-shadow: var(--foo) 12px lab(40% 56.6 39); + } + } + `, + { chrome: Some(90 << 16) }, + ); + + prefix_test( + `.foo { + box-shadow: 0px 0px 22px red; + box-shadow: 0px 0px max(2cqw, 22px) red; + } + `, + `.foo { + box-shadow: 0 0 22px red; + box-shadow: 0 0 max(2cqw, 22px) red; + } + `, + { safari: Some(14 << 16) }, + ); + prefix_test( + `.foo { + box-shadow: 0px 0px 22px red; + box-shadow: 0px 0px max(2cqw, 22px) red; + } + `, + `.foo { + box-shadow: 0 0 max(2cqw, 22px) red; + } + `, + { safari: Some(16 << 16) }, + ); + + prefix_test( + `.foo { + box-shadow: 0px 0px 22px red; + box-shadow: 0px 0px 22px lab(40% 56.6 39); + } + `, + `.foo { + box-shadow: 0 0 22px red; + box-shadow: 0 0 22px lab(40% 56.6 39); + } + `, + { safari: Some(14 << 16) }, + ); + prefix_test( + `.foo { + box-shadow: 0px 0px 22px red; + box-shadow: 0px 0px 22px lab(40% 56.6 39); + } + `, + `.foo { + box-shadow: 0 0 22px lab(40% 56.6 39); + } + `, + { safari: Some(16 << 16) }, + ); + + prefix_test( + `.foo { + box-shadow: var(--fallback); + box-shadow: 0px 0px 22px lab(40% 56.6 39); + } + `, + `.foo { + box-shadow: var(--fallback); + box-shadow: 0 0 22px lab(40% 56.6 39); + } + `, + { safari: Some(16 << 16) }, + ); + }); + describe("margin", () => { cssTest( ` @@ -6754,7 +6923,7 @@ describe("css tests", () => { ".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 + // See: https://github.com/parcel-bundler/buncss/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)}", @@ -6772,7 +6941,7 @@ describe("css tests", () => { ".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 + // See: https://github.com/parcel-bundler/buncss/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)}", @@ -6842,7 +7011,7 @@ describe("css tests", () => { 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 + // See: https://github.com/parcel-bundler/buncss/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)}"); @@ -6898,4 +7067,161 @@ describe("css tests", () => { `, ); }); + + describe("color-scheme", () => { + minify_test(".foo { color-scheme: normal; }", ".foo{color-scheme:normal}"); + minify_test(".foo { color-scheme: light; }", ".foo{color-scheme:light}"); + minify_test(".foo { color-scheme: dark; }", ".foo{color-scheme:dark}"); + minify_test(".foo { color-scheme: light dark; }", ".foo{color-scheme:light dark}"); + minify_test(".foo { color-scheme: dark light; }", ".foo{color-scheme:light dark}"); + minify_test(".foo { color-scheme: only light; }", ".foo{color-scheme:light only}"); + minify_test(".foo { color-scheme: only dark; }", ".foo{color-scheme:dark only}"); + minify_test(".foo { color-scheme: dark light only; }", ".foo{color-scheme:light dark only}"); + minify_test(".foo { color-scheme: foo bar light; }", ".foo{color-scheme:light}"); + minify_test(".foo { color-scheme: only foo dark bar; }", ".foo{color-scheme:dark only}"); + prefix_test( + ".foo { color-scheme: dark; }", + `.foo { + --buncss-light: ; + --buncss-dark: initial; + color-scheme: dark; + } + `, + { chrome: Some(90 << 16) }, + ); + prefix_test( + ".foo { color-scheme: light; }", + `.foo { + --buncss-light: initial; + --buncss-dark: ; + color-scheme: light; + } + `, + { chrome: Some(90 << 16) }, + ); + prefix_test( + ".foo { color-scheme: light dark; }", + `.foo { + --buncss-light: initial; + --buncss-dark: ; + color-scheme: light dark; + } + + @media (prefers-color-scheme: dark) { + .foo { + --buncss-light: ; + --buncss-dark: initial; + } + } + `, + { chrome: Some(90 << 16) }, + ); + prefix_test( + ".foo { color-scheme: light dark; }", + `.foo { + color-scheme: light dark; + } + `, + { firefox: Some(120 << 16) }, + ); + + minify_test(".foo { color: light-dark(yellow, red); }", ".foo{color:light-dark(#ff0,red)}"); + minify_test( + ".foo { color: light-dark(light-dark(yellow, red), light-dark(yellow, red)); }", + ".foo{color:light-dark(#ff0,red)}", + ); + minify_test( + ".foo { color: light-dark(rgb(0, 0, 255), hsl(120deg, 50%, 50%)); }", + ".foo{color:light-dark(#00f,#40bf40)}", + ); + prefix_test( + ".foo { color: light-dark(oklch(40% 0.1268735435 34.568626), oklab(59.686% 0.1009 0.1192)); }", + `.foo { + color: var(--buncss-light, #7e250f) var(--buncss-dark, #c65d07); + color: var(--buncss-light, lab(29.2661% 38.2437 35.3889)) var(--buncss-dark, lab(52.2319% 40.1449 59.9171)); + } + `, + { chrome: Some(90 << 16) }, + ); + prefix_test( + ".foo { color: light-dark(oklch(40% 0.1268735435 34.568626), oklab(59.686% 0.1009 0.1192)); }", + `.foo { + color: light-dark(oklch(40% .126874 34.5686), oklab(59.686% .1009 .1192)); + } + `, + { firefox: Some(120 << 16) }, + ); + prefix_test( + ` + .foo { + box-shadow: + oklch(100% 0 0deg / 50%) 0 0.63rem 0.94rem -0.19rem, + currentColor 0 0.44rem 0.8rem -0.58rem; + } + `, + `.foo { + box-shadow: 0 .63rem .94rem -.19rem #ffffff80, 0 .44rem .8rem -.58rem; + box-shadow: 0 .63rem .94rem -.19rem lab(100% 0 0 / .5), 0 .44rem .8rem -.58rem; + } + `, + { chrome: Some(95 << 16) }, + ); + prefix_test( + ` + .foo { + box-shadow: + oklch(100% 0 0deg / 50%) 0 0.63rem 0.94rem -0.19rem, + currentColor 0 0.44rem 0.8rem -0.58rem; + } + `, + `.foo { + box-shadow: 0 .63rem .94rem -.19rem color(display-p3 1 1 1 / .5), 0 .44rem .8rem -.58rem; + box-shadow: 0 .63rem .94rem -.19rem lab(100% 0 0 / .5), 0 .44rem .8rem -.58rem; + } + `, + { safari: Some(14 << 16) }, + ); + + prefix_test( + ".foo { color: light-dark(var(--light), var(--dark)); }", + `.foo { + color: var(--buncss-light, var(--light)) var(--buncss-dark, var(--dark)); + } + `, + { chrome: Some(90 << 16) }, + ); + prefix_test( + ".foo { color: rgb(from light-dark(yellow, red) r g b / 10%); }", + `.foo { + color: var(--buncss-light, #ffff001a) var(--buncss-dark, #ff00001a); + } + `, + { chrome: Some(90 << 16) }, + ); + prefix_test( + ".foo { color: rgb(from light-dark(yellow, red) r g b / var(--alpha)); }", + `.foo { + color: var(--buncss-light, rgb(255 255 0 / var(--alpha))) var(--buncss-dark, rgb(255 0 0 / var(--alpha))); + } + `, + { chrome: Some(90 << 16) }, + ); + prefix_test( + ".foo { color: color(from light-dark(yellow, red) srgb r g b / 10%); }", + `.foo { + color: var(--buncss-light, #ffff001a) var(--buncss-dark, #ff00001a); + color: var(--buncss-light, color(srgb 1 1 0 / .1)) var(--buncss-dark, color(srgb 1 0 0 / .1)); + } + `, + { chrome: Some(90 << 16) }, + ); + prefix_test( + ".foo { color: color-mix(in srgb, light-dark(yellow, red), light-dark(red, pink)); }", + `.foo { + color: var(--buncss-light, #ff8000) var(--buncss-dark, #ff6066); + } + `, + { chrome: Some(90 << 16) }, + ); + }); });