@@ -30,6 +30,9 @@ const ATTR_RENAMES: &[(&str, &str)] = &[
30 ("animate", "animation"),
30 ("animate", "animation"),
31 ("inset-area", "position-area"),
31 ("inset-area", "position-area"),
32 ("align-center", "center-x"),
32 ("align-center", "center-x"),
33+ ("gap-x", "column-gap"),
34+ ("gap-y", "row-gap"),
35+ ("shadow", "box-shadow"),
35/// Variable filter aliases (`$x|upper`) and their canonical names.
38/// Variable filter aliases (`$x|upper`) and their canonical names.
@@ -718,21 +721,48 @@ fn rewrite_attr_list(list: &str, element_attrs: bool) -> String {
719 let mut out = String::with_capacity(list.len());
722 let mut out = String::with_capacity(list.len());
720 let mut first = true;
723 let mut first = true;
724+ let mut dropped_leading = false;
721 for segment in split_top_level_commas(list) {
725 for segment in split_top_level_commas(list) {
726+ // `critical` (inline styles) was removed without a replacement
727+ if segment.trim() == "critical" {
728+ dropped_leading |= first;
731+ let lead = segment.len() - segment.trim_start().len();
732+ let (ws, body) = segment.split_at(lead);
722 if !first {
733 if !first {
723 out.push(',');
734 out.push(',');
736+ } else if !dropped_leading || ws.contains('\n') {
725 first = false;
739 first = false;
726− let lead = segment.len() - segment.trim_start().len();
727− let (ws, body) = segment.split_at(lead);
729 // `...$bundle` spread → `$bundle`
740 // `...$bundle` spread → `$bundle`
730 let body = body.strip_prefix("...$").map_or(body.to_string(), |b| format!("${b}"));
741 let body = body.strip_prefix("...$").map_or(body.to_string(), |b| format!("${b}"));
731 // Built-in style attributes that became standard-library bundles
742 // Built-in style attributes that became standard-library bundles
732 let body = match body.trim_end() {
743 let body = match body.trim_end() {
733− "skeleton" | "no-scrollbar" => format!("${}", body),
744+ "skeleton" | "no-scrollbar" | "truncate" => format!("${}", body),
734 _ => body,
745 _ => body,
747+ // `blur N` / `backdrop-blur N` → the filter they generated
749+ let (key, value) = body.split_once(' ').unwrap_or((&body, ""));
750+ let (prefix, base) = key.rsplit_once(':').map_or(("", key), |(p, b)| (p, b));
751+ let prefix = if prefix.is_empty() { String::new() } else { format!("{prefix}:") };
752+ let px = |v: &str| {
754+ if !v.is_empty() && v.parse::<f64>().is_ok() && v != "0" {
761+ "blur" => format!("{prefix}filter blur({})", px(value)),
762+ "backdrop-blur" => format!("{prefix}backdrop-filter blur({})", px(value)),
736 // `gradient A B [ANGLE]` → the `background` it generated
766 // `gradient A B [ANGLE]` → the `background` it generated
737 let body = match body.strip_prefix("gradient ") {
767 let body = match body.strip_prefix("gradient ") {
738 Some(v) => {
768 Some(v) => {
@@ -1095,6 +1125,15 @@ mod tests {
1095 assert_eq!(upgrade("@fetch $d http://x").manual.len(), 1);
1125 assert_eq!(upgrade("@fetch $d http://x").manual.len(), 1);
1129+ fn css_alias_attributes() {
1131+ up("@el [gap-x 4, hover:shadow 0 1px red, blur 4, backdrop-blur 2px, truncate, critical]"),
1132+ "@el [column-gap 4, hover:box-shadow 0 1px red, filter blur(4px), backdrop-filter blur(2px), $truncate]"
1134+ assert_eq!(up("@el [critical, padding 4]"), "@el [padding 4]");
1099 fn verbatim_regions_are_untouched() {
1138 fn verbatim_regions_are_untouched() {
1100 let src = "@script\n for (x of y) {}\n@raw \"\"\"\n@unless\n\"\"\"\n@style\n .p { }";
1139 let src = "@script\n for (x of y) {}\n@raw \"\"\"\n@unless\n\"\"\"\n@style\n .p { }";