//! The `@container` rule. use cssparser::*; use super::Location; use super::{CssRuleList, MinifyContext}; use crate::error::{MinifyError, ParserError, PrinterError}; use crate::media_query::{ define_query_features, operation_to_css, parse_query_condition, to_css_with_parens_if_needed, FeatureToCss, MediaFeatureType, Operator, QueryCondition, QueryConditionFlags, QueryFeature, ValueType, }; use crate::parser::{DefaultAtRule, ParserOptions}; use crate::printer::Printer; use crate::properties::{Property, PropertyId}; #[cfg(feature = "serde")] use crate::serialization::ValueWrapper; use crate::targets::{Features, Targets}; use crate::traits::{Parse, ParseWithOptions, ToCss}; use crate::values::ident::CustomIdent; #[cfg(feature = "visitor")] use crate::visitor::Visit; /// A [@container](https://drafts.csswg.org/css-contain-3/#container-rule) rule. #[derive(Debug, PartialEq, Clone)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] pub struct ContainerRule<'i, R = DefaultAtRule> { /// The name of the container. #[cfg_attr(feature = "serde", serde(borrow))] pub name: Option>, /// The container condition. pub condition: ContainerCondition<'i>, /// The rules within the `@container` rule. pub rules: CssRuleList<'i, R>, /// The location of the rule in the source file. #[cfg_attr(feature = "visitor", skip_visit)] pub loc: Location, } /// Represents a container condition. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub enum ContainerCondition<'i> { /// A size container feature, implicitly parenthesized. #[cfg_attr(feature = "serde", serde(borrow, with = "ValueWrapper::"))] Feature(ContainerSizeFeature<'i>), /// A negation of a condition. #[cfg_attr(feature = "visitor", skip_type)] #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::>"))] Not(Box>), /// A set of joint operations. #[cfg_attr(feature = "visitor", skip_type)] Operation { /// The operator for the conditions. operator: Operator, /// The conditions for the operator. conditions: Vec>, }, /// A style query. #[cfg_attr(feature = "serde", serde(borrow, with = "ValueWrapper::"))] Style(StyleQuery<'i>), } /// A container query size feature. pub type ContainerSizeFeature<'i> = QueryFeature<'i, ContainerSizeFeatureId>; define_query_features! { /// A container query size feature identifier. pub enum ContainerSizeFeatureId { /// The [width](https://w3c.github.io/csswg-drafts/css-contain-3/#width) size container feature. "width": Width = Length, /// The [height](https://w3c.github.io/csswg-drafts/css-contain-3/#height) size container feature. "height": Height = Length, /// The [inline-size](https://w3c.github.io/csswg-drafts/css-contain-3/#inline-size) size container feature. "inline-size": InlineSize = Length, /// The [block-size](https://w3c.github.io/csswg-drafts/css-contain-3/#block-size) size container feature. "block-size": BlockSize = Length, /// The [aspect-ratio](https://w3c.github.io/csswg-drafts/css-contain-3/#aspect-ratio) size container feature. "aspect-ratio": AspectRatio = Ratio, /// The [orientation](https://w3c.github.io/csswg-drafts/css-contain-3/#orientation) size container feature. "orientation": Orientation = Ident, } } impl FeatureToCss for ContainerSizeFeatureId { fn to_css_with_prefix(&self, prefix: &str, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { dest.write_str(prefix)?; self.to_css(dest) } } /// Represents a style query within a container condition. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr( feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "type", rename_all = "kebab-case") )] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub enum StyleQuery<'i> { /// A property declaration. #[cfg_attr(feature = "serde", serde(borrow, with = "ValueWrapper::"))] Declaration(Property<'i>), /// A property name, without a value. /// This matches if the property value is different from the initial value. #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::"))] Property(PropertyId<'i>), /// A negation of a condition. #[cfg_attr(feature = "visitor", skip_type)] #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::>"))] Not(Box>), /// A set of joint operations. #[cfg_attr(feature = "visitor", skip_type)] Operation { /// The operator for the conditions. operator: Operator, /// The conditions for the operator. conditions: Vec>, }, } impl<'i> QueryCondition<'i> for ContainerCondition<'i> { #[inline] fn parse_feature<'t>( input: &mut Parser<'i, 't>, options: &ParserOptions<'_, 'i>, ) -> Result>> { let feature = QueryFeature::parse_with_options(input, options)?; Ok(Self::Feature(feature)) } #[inline] fn create_negation(condition: Box>) -> Self { Self::Not(condition) } #[inline] fn create_operation(operator: Operator, conditions: Vec) -> Self { Self::Operation { operator, conditions } } fn parse_style_query<'t>( input: &mut Parser<'i, 't>, options: &ParserOptions<'_, 'i>, ) -> Result>> { input.parse_nested_block(|input| { if let Ok(res) = input.try_parse(|input| parse_query_condition(input, QueryConditionFlags::ALLOW_OR, options)) { return Ok(Self::Style(res)); } Ok(Self::Style(StyleQuery::parse_feature(input, options)?)) }) } fn needs_parens(&self, parent_operator: Option, targets: &Targets) -> bool { match self { ContainerCondition::Not(_) => true, ContainerCondition::Operation { operator, .. } => Some(*operator) != parent_operator, ContainerCondition::Feature(f) => f.needs_parens(parent_operator, targets), ContainerCondition::Style(_) => false, } } } impl<'i> QueryCondition<'i> for StyleQuery<'i> { #[inline] fn parse_feature<'t>( input: &mut Parser<'i, 't>, options: &ParserOptions<'_, 'i>, ) -> Result>> { let property_id = PropertyId::parse(input)?; if input.try_parse(|input| input.expect_colon()).is_ok() { input.skip_whitespace(); let feature = Self::Declaration(Property::parse(property_id, input, options)?); let _ = input.try_parse(|input| parse_important(input)); Ok(feature) } else { Ok(Self::Property(property_id)) } } #[inline] fn create_negation(condition: Box) -> Self { Self::Not(condition) } #[inline] fn create_operation(operator: Operator, conditions: Vec) -> Self { Self::Operation { operator, conditions } } fn needs_parens(&self, parent_operator: Option, _targets: &Targets) -> bool { match self { StyleQuery::Not(_) => true, StyleQuery::Operation { operator, .. } => Some(*operator) != parent_operator, StyleQuery::Declaration(_) | StyleQuery::Property(_) => true, } } } impl<'i> ParseWithOptions<'i> for ContainerCondition<'i> { fn parse_with_options<'t>( input: &mut Parser<'i, 't>, options: &ParserOptions<'_, 'i>, ) -> Result>> { parse_query_condition( input, QueryConditionFlags::ALLOW_OR | QueryConditionFlags::ALLOW_STYLE, options, ) } } impl<'i> ToCss for ContainerCondition<'i> { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { match *self { ContainerCondition::Feature(ref f) => f.to_css(dest), ContainerCondition::Not(ref c) => { dest.write_str("not ")?; to_css_with_parens_if_needed(&**c, dest, c.needs_parens(None, &dest.targets.current)) } ContainerCondition::Operation { ref conditions, operator, } => operation_to_css(operator, conditions, dest), ContainerCondition::Style(ref query) => { dest.write_str("style(")?; query.to_css(dest)?; dest.write_char(')') } } } } impl<'i> ToCss for StyleQuery<'i> { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { match *self { StyleQuery::Declaration(ref f) => f.to_css(dest, false), StyleQuery::Property(ref f) => f.to_css(dest), StyleQuery::Not(ref c) => { dest.write_str("not ")?; to_css_with_parens_if_needed(&**c, dest, c.needs_parens(None, &dest.targets.current)) } StyleQuery::Operation { ref conditions, operator, } => operation_to_css(operator, conditions, dest), } } } /// A [``](https://drafts.csswg.org/css-contain-3/#typedef-container-name) in a `@container` rule. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "visitor", derive(Visit))] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub struct ContainerName<'i>(#[cfg_attr(feature = "serde", serde(borrow))] pub CustomIdent<'i>); impl<'i> Parse<'i> for ContainerName<'i> { fn parse<'t>(input: &mut Parser<'i, 't>) -> Result>> { let ident = CustomIdent::parse(input)?; match_ignore_ascii_case! { &*ident.0, "none" | "and" | "not" | "or" => Err(input.new_unexpected_token_error(Token::Ident(ident.0.as_ref().to_owned().into()))), _ => Ok(ContainerName(ident)) } } } impl<'i> ToCss for ContainerName<'i> { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { // Container name should not be hashed // https://github.com/vercel/next.js/issues/71233 self.0.to_css_with_options( dest, match &dest.css_module { Some(css_module) => css_module.config.container, None => false, }, ) } } impl<'i, T: Clone> ContainerRule<'i, T> { pub(crate) fn minify( &mut self, context: &mut MinifyContext<'_, 'i>, parent_is_unused: bool, ) -> Result { self.rules.minify(context, parent_is_unused)?; Ok(self.rules.0.is_empty()) } } impl<'a, 'i, T: ToCss> ToCss for ContainerRule<'i, T> { fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write, { #[cfg(feature = "sourcemap")] dest.add_mapping(self.loc); dest.write_str("@container ")?; if let Some(name) = &self.name { name.to_css(dest)?; dest.write_char(' ')?; } // Don't downlevel range syntax in container queries. let exclude = dest.targets.current.exclude; dest.targets.current.exclude.insert(Features::MediaQueries); self.condition.to_css(dest)?; dest.targets.current.exclude = exclude; dest.whitespace()?; dest.write_char('{')?; dest.indent(); dest.newline()?; self.rules.to_css(dest)?; dest.dedent(); dest.newline()?; dest.write_char('}') } }