Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 67 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6806,7 +6806,7 @@ mod tests {
}
"#},
Browsers {
firefox: Some(60 << 16),
firefox: Some(62 << 16),
..Browsers::default()
},
);
Expand All @@ -6827,7 +6827,28 @@ mod tests {
}
"#},
Browsers {
firefox: Some(85 << 16),
firefox: Some(62 << 16),
..Browsers::default()
},
);

prefix_test(
r#"
@media (100px <= width <= 200px) {
.foo {
color: chartreuse;
}
}
"#,
indoc! { r#"
@media (width >= 100px) and (width <= 200px) {
.foo {
color: #7fff00;
}
}
"#},
Browsers {
firefox: Some(63 << 16),
..Browsers::default()
},
);
Expand All @@ -6848,7 +6869,28 @@ mod tests {
}
"#},
Browsers {
firefox: Some(85 << 16),
firefox: Some(62 << 16),
..Browsers::default()
},
);

prefix_test(
r#"
@media (100px < width < 200px) {
.foo {
color: chartreuse;
}
}
"#,
indoc! { r#"
@media (width > 100px) and (width < 200px) {
.foo {
color: #7fff00;
}
}
"#},
Browsers {
firefox: Some(63 << 16),
..Browsers::default()
},
);
Expand All @@ -6869,7 +6911,28 @@ mod tests {
}
"#},
Browsers {
firefox: Some(85 << 16),
firefox: Some(62 << 16),
..Browsers::default()
},
);

prefix_test(
r#"
@media (200px >= width >= 100px) {
.foo {
color: chartreuse;
}
}
"#,
indoc! { r#"
@media (width <= 200px) and (width >= 100px) {
.foo {
color: #7fff00;
}
}
"#},
Browsers {
firefox: Some(63 << 16),
..Browsers::default()
},
);
Expand Down
113 changes: 99 additions & 14 deletions src/media_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,12 +447,73 @@ impl<'i> ToCss for MediaCondition<'i> {
where
W: std::fmt::Write,
{
match *self {
MediaCondition::Feature(ref f) => f.to_css(dest),
MediaCondition::Not(ref c) => {
dest.write_str("not ")?;
c.to_css(dest)
enum ExpandableMediaIntervalFeatureCondition<'a, 'i> {
Original(&'a MediaCondition<'i>),
Expanded(MediaCondition<'i>),
}

fn expand_media_feature_interval<'a, 'i, W>(
c: &'a MediaCondition<'i>,
dest: &mut Printer<W>,
) -> ExpandableMediaIntervalFeatureCondition<'a, 'i>
where
W: std::fmt::Write,
{
// Media interval syntax is expanded to an `and` operation of two parenthesized plain media features,
// which has an effect on negated vs plain syntax on root level.
// The operation must be enclosed in parens when negated, but left plain otherwise.
// Positive syntax: `@media screen and (...) and (...)`
// Negated syntax: `@media screen and not ((...) and (...))`
if let MediaCondition::Feature(MediaFeature::Interval {
ref name,
ref start,
ref start_operator,
ref end,
ref end_operator,
}) = *c
{
if let Some(targets) = dest.targets {
if !Feature::MediaIntervalSyntax.is_compatible(targets) {
let range_op = MediaCondition::Operation(
vec![
MediaCondition::Feature(MediaFeature::Range {
name: name.clone(),
operator: start_operator.opposite(),
value: start.clone(),
}),
MediaCondition::Feature(MediaFeature::Range {
name: name.clone(),
operator: *end_operator,
value: end.clone(),
}),
],
Operator::And,
);
return ExpandableMediaIntervalFeatureCondition::Expanded(range_op);
}
}
}

ExpandableMediaIntervalFeatureCondition::Original(c)
}

match *self {
ref c @ MediaCondition::Feature(_) => match expand_media_feature_interval(c, dest) {
ExpandableMediaIntervalFeatureCondition::Original(&MediaCondition::Feature(ref f)) => f.to_css(dest),
ExpandableMediaIntervalFeatureCondition::Original(c) => c.to_css(dest),
ExpandableMediaIntervalFeatureCondition::Expanded(c) => c.to_css(dest),
},
MediaCondition::Not(ref c) => match expand_media_feature_interval(&**c, dest) {
ExpandableMediaIntervalFeatureCondition::Original(c) => {
dest.write_str("not ")?;
c.to_css(dest)
}
ExpandableMediaIntervalFeatureCondition::Expanded(c) => {
dest.write_str("not (")?;
c.to_css(dest)?;
dest.write_char(')')
}
},
MediaCondition::InParens(ref c) => {
dest.write_char('(')?;
c.to_css(dest)?;
Expand Down Expand Up @@ -670,14 +731,6 @@ impl<'i> ToCss for MediaFeature<'i> {
end,
end_operator,
} => {
if let Some(targets) = dest.targets {
if !Feature::MediaIntervalSyntax.is_compatible(targets) {
write_min_max(&start_operator.opposite(), name, start, dest)?;
dest.write_str(" and (")?;
return write_min_max(end_operator, name, end, dest);
}
}

start.to_css(dest)?;
start_operator.to_css(dest)?;
serialize_identifier(name, dest)?;
Expand Down Expand Up @@ -991,7 +1044,7 @@ fn process_condition<'i>(
#[cfg(test)]
mod tests {
use super::*;
use crate::stylesheet::PrinterOptions;
use crate::{stylesheet::PrinterOptions, targets::Browsers};

fn parse(s: &str) -> MediaQuery {
let mut input = ParserInput::new(&s);
Expand Down Expand Up @@ -1038,4 +1091,36 @@ mod tests {
assert_eq!(and("only screen", "all"), "only screen");
assert_eq!(and("print", "print"), "print");
}

#[test]
fn test_negated_interval_parens_before_range_syntax() {
let media_query = parse("screen and not (200px <= width < 500px)");
let printer_options = PrinterOptions {
targets: Some(Browsers {
firefox: Some(62 << 16),
..Browsers::default()
}),
..PrinterOptions::default()
};
assert_eq!(
media_query.to_css_string(printer_options).unwrap(),
"screen and not ((min-width: 200px) and (max-width: 499.999px))"
);
}

#[test]
fn test_negated_interval_parens_with_range_syntax() {
let media_query = parse("screen and not (200px <= width < 500px)");
let printer_options = PrinterOptions {
targets: Some(Browsers {
firefox: Some(63 << 16),
..Browsers::default()
}),
..PrinterOptions::default()
};
assert_eq!(
media_query.to_css_string(printer_options).unwrap(),
"screen and not ((width >= 200px) and (width < 500px))"
);
}
}