1use cssparser::*;
4
5use super::Location;
6use super::{CssRuleList, MinifyContext};
7use crate::error::{MinifyError, ParserError, PrinterError};
8use crate::media_query::{
9 define_query_features, operation_to_css, parse_query_condition, to_css_with_parens_if_needed, FeatureToCss,
10 MediaFeatureType, Operator, QueryCondition, QueryConditionFlags, QueryFeature, ValueType,
11};
12use crate::parser::{DefaultAtRule, ParserOptions};
13use crate::printer::Printer;
14use crate::properties::custom::TokenList;
15use crate::properties::{Property, PropertyId};
16#[cfg(feature = "serde")]
17use crate::serialization::ValueWrapper;
18use crate::targets::{Features, Targets};
19use crate::traits::{Parse, ParseWithOptions, ToCss};
20use crate::values::ident::CustomIdent;
21#[cfg(feature = "visitor")]
22use crate::visitor::Visit;
23
24#[derive(Debug, PartialEq, Clone)]
26#[cfg_attr(feature = "visitor", derive(Visit))]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
29#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
30pub struct ContainerRule<'i, R = DefaultAtRule> {
31 #[cfg_attr(feature = "serde", serde(borrow))]
33 pub name: Option<ContainerName<'i>>,
34 pub condition: Option<ContainerCondition<'i>>,
36 pub rules: CssRuleList<'i, R>,
38 #[cfg_attr(feature = "visitor", skip_visit)]
40 pub loc: Location,
41}
42
43#[derive(Clone, Debug, PartialEq)]
45#[cfg_attr(feature = "visitor", derive(Visit))]
46#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
47#[cfg_attr(
48 feature = "serde",
49 derive(serde::Serialize, serde::Deserialize),
50 serde(tag = "type", rename_all = "kebab-case")
51)]
52#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
53pub enum ContainerCondition<'i> {
54 #[cfg_attr(feature = "serde", serde(borrow, with = "ValueWrapper::<ContainerSizeFeature>"))]
56 Feature(ContainerSizeFeature<'i>),
57 #[cfg_attr(feature = "visitor", skip_type)]
59 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<Box<ContainerCondition>>"))]
60 Not(Box<ContainerCondition<'i>>),
61 #[cfg_attr(feature = "visitor", skip_type)]
63 Operation {
64 operator: Operator,
66 conditions: Vec<ContainerCondition<'i>>,
68 },
69 #[cfg_attr(feature = "serde", serde(borrow, with = "ValueWrapper::<StyleQuery>"))]
71 Style(StyleQuery<'i>),
72 #[cfg_attr(feature = "serde", serde(borrow, with = "ValueWrapper::<ScrollStateQuery>"))]
74 ScrollState(ScrollStateQuery<'i>),
75 #[cfg_attr(feature = "serde", serde(borrow, with = "ValueWrapper::<TokenList>"))]
77 Unknown(TokenList<'i>),
78}
79
80pub type ContainerSizeFeature<'i> = QueryFeature<'i, ContainerSizeFeatureId>;
82
83define_query_features! {
84 pub enum ContainerSizeFeatureId {
86 "width": Width = Length,
88 "height": Height = Length,
90 "inline-size": InlineSize = Length,
92 "block-size": BlockSize = Length,
94 "aspect-ratio": AspectRatio = Ratio,
96 "orientation": Orientation = Ident,
98 }
99}
100
101impl FeatureToCss for ContainerSizeFeatureId {
102 fn to_css_with_prefix<W>(&self, prefix: &str, dest: &mut Printer<W>) -> Result<(), PrinterError>
103 where
104 W: std::fmt::Write,
105 {
106 dest.write_str(prefix)?;
107 self.to_css(dest)
108 }
109}
110
111#[derive(Clone, Debug, PartialEq)]
113#[cfg_attr(feature = "visitor", derive(Visit))]
114#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
115#[cfg_attr(
116 feature = "serde",
117 derive(serde::Serialize, serde::Deserialize),
118 serde(tag = "type", rename_all = "kebab-case")
119)]
120#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
121pub enum StyleQuery<'i> {
122 #[cfg_attr(feature = "serde", serde(borrow, with = "ValueWrapper::<Property>"))]
124 Declaration(Property<'i>),
125 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<PropertyId>"))]
128 Property(PropertyId<'i>),
129 #[cfg_attr(feature = "visitor", skip_type)]
131 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<Box<StyleQuery>>"))]
132 Not(Box<StyleQuery<'i>>),
133 #[cfg_attr(feature = "visitor", skip_type)]
135 Operation {
136 operator: Operator,
138 conditions: Vec<StyleQuery<'i>>,
140 },
141}
142
143#[derive(Clone, Debug, PartialEq)]
145#[cfg_attr(feature = "visitor", derive(Visit))]
146#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
147#[cfg_attr(
148 feature = "serde",
149 derive(serde::Serialize, serde::Deserialize),
150 serde(tag = "type", rename_all = "kebab-case")
151)]
152#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
153pub enum ScrollStateQuery<'i> {
154 #[cfg_attr(feature = "serde", serde(borrow, with = "ValueWrapper::<ScrollStateFeature>"))]
156 Feature(ScrollStateFeature<'i>),
157 #[cfg_attr(feature = "visitor", skip_type)]
159 #[cfg_attr(feature = "serde", serde(with = "ValueWrapper::<Box<ScrollStateQuery>>"))]
160 Not(Box<ScrollStateQuery<'i>>),
161 #[cfg_attr(feature = "visitor", skip_type)]
163 Operation {
164 operator: Operator,
166 conditions: Vec<ScrollStateQuery<'i>>,
168 },
169}
170
171pub type ScrollStateFeature<'i> = QueryFeature<'i, ScrollStateFeatureId>;
173
174define_query_features! {
175 pub enum ScrollStateFeatureId {
177 "stuck": Stuck = Ident,
179 "snapped": Snapped = Ident,
181 "scrollable": Scrollable = Ident,
183 "scrolled": Scrolled = Ident,
185 }
186}
187
188impl FeatureToCss for ScrollStateFeatureId {
189 fn to_css_with_prefix<W>(&self, prefix: &str, dest: &mut Printer<W>) -> Result<(), PrinterError>
190 where
191 W: std::fmt::Write,
192 {
193 dest.write_str(prefix)?;
194 self.to_css(dest)
195 }
196}
197
198impl<'i> QueryCondition<'i> for ContainerCondition<'i> {
199 #[inline]
200 fn parse_feature<'t>(
201 input: &mut Parser<'i, 't>,
202 options: &ParserOptions<'i>,
203 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
204 let feature = QueryFeature::parse_with_options(input, options)?;
205 Ok(Self::Feature(feature))
206 }
207
208 #[inline]
209 fn create_negation(condition: Box<ContainerCondition<'i>>) -> Self {
210 Self::Not(condition)
211 }
212
213 #[inline]
214 fn create_operation(operator: Operator, conditions: Vec<Self>) -> Self {
215 Self::Operation { operator, conditions }
216 }
217
218 fn parse_style_query<'t>(
219 input: &mut Parser<'i, 't>,
220 options: &ParserOptions<'i>,
221 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
222 input.parse_nested_block(|input| {
223 if let Ok(res) =
224 input.try_parse(|input| parse_query_condition(input, QueryConditionFlags::ALLOW_OR, options))
225 {
226 return Ok(Self::Style(res));
227 }
228
229 Ok(Self::Style(StyleQuery::parse_feature(input, options)?))
230 })
231 }
232
233 fn parse_scroll_state_query<'t>(
234 input: &mut Parser<'i, 't>,
235 options: &ParserOptions<'i>,
236 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
237 input.parse_nested_block(|input| {
238 if let Ok(res) =
239 input.try_parse(|input| parse_query_condition(input, QueryConditionFlags::ALLOW_OR, options))
240 {
241 return Ok(Self::ScrollState(res));
242 }
243
244 Ok(Self::ScrollState(ScrollStateQuery::parse_feature(input, options)?))
245 })
246 }
247
248 fn needs_parens(&self, parent_operator: Option<Operator>, targets: &Targets) -> bool {
249 match self {
250 ContainerCondition::Not(_) => true,
251 ContainerCondition::Operation { operator, .. } => Some(*operator) != parent_operator,
252 ContainerCondition::Feature(f) => f.needs_parens(parent_operator, targets),
253 ContainerCondition::Style(_) => false,
254 ContainerCondition::ScrollState(_) => false,
255 ContainerCondition::Unknown(_) => false,
256 }
257 }
258}
259
260impl<'i> QueryCondition<'i> for ScrollStateQuery<'i> {
261 #[inline]
262 fn parse_feature<'t>(
263 input: &mut Parser<'i, 't>,
264 options: &ParserOptions<'i>,
265 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
266 let feature = QueryFeature::parse_with_options(input, options)?;
267 Ok(Self::Feature(feature))
268 }
269
270 #[inline]
271 fn create_negation(condition: Box<Self>) -> Self {
272 Self::Not(condition)
273 }
274
275 #[inline]
276 fn create_operation(operator: Operator, conditions: Vec<Self>) -> Self {
277 Self::Operation { operator, conditions }
278 }
279
280 fn needs_parens(&self, parent_operator: Option<Operator>, targets: &Targets) -> bool {
281 match self {
282 ScrollStateQuery::Not(_) => true,
283 ScrollStateQuery::Operation { operator, .. } => Some(*operator) != parent_operator,
284 ScrollStateQuery::Feature(f) => f.needs_parens(parent_operator, targets),
285 }
286 }
287}
288
289impl<'i> QueryCondition<'i> for StyleQuery<'i> {
290 #[inline]
291 fn parse_feature<'t>(
292 input: &mut Parser<'i, 't>,
293 options: &ParserOptions<'i>,
294 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
295 let property_id = PropertyId::parse(input)?;
296 if input.try_parse(|input| input.expect_colon()).is_ok() {
297 input.skip_whitespace();
298 let feature = Self::Declaration(Property::parse(property_id, input, options)?);
299 let _ = input.try_parse(|input| parse_important(input));
300 Ok(feature)
301 } else {
302 Ok(Self::Property(property_id))
303 }
304 }
305
306 #[inline]
307 fn create_negation(condition: Box<Self>) -> Self {
308 Self::Not(condition)
309 }
310
311 #[inline]
312 fn create_operation(operator: Operator, conditions: Vec<Self>) -> Self {
313 Self::Operation { operator, conditions }
314 }
315
316 fn needs_parens(&self, parent_operator: Option<Operator>, _targets: &Targets) -> bool {
317 match self {
318 StyleQuery::Not(_) => true,
319 StyleQuery::Operation { operator, .. } => Some(*operator) != parent_operator,
320 StyleQuery::Declaration(_) | StyleQuery::Property(_) => true,
321 }
322 }
323}
324
325impl<'i> ParseWithOptions<'i> for ContainerCondition<'i> {
326 fn parse_with_options<'t>(
327 input: &mut Parser<'i, 't>,
328 options: &ParserOptions<'i>,
329 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
330 input
331 .try_parse(|input| {
332 parse_query_condition(
333 input,
334 QueryConditionFlags::ALLOW_OR
335 | QueryConditionFlags::ALLOW_STYLE
336 | QueryConditionFlags::ALLOW_SCROLL_STATE,
337 options,
338 )
339 })
340 .or_else(|e| {
341 if options.error_recovery {
342 options.warn(e);
343 Ok(ContainerCondition::Unknown(TokenList::parse(input, options, 0)?))
344 } else {
345 Err(e)
346 }
347 })
348 }
349}
350
351impl<'i> ToCss for ContainerCondition<'i> {
352 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
353 where
354 W: std::fmt::Write,
355 {
356 match *self {
357 ContainerCondition::Feature(ref f) => f.to_css(dest),
358 ContainerCondition::Not(ref c) => {
359 dest.write_str("not ")?;
360 to_css_with_parens_if_needed(&**c, dest, c.needs_parens(None, &dest.targets.current))
361 }
362 ContainerCondition::Operation {
363 ref conditions,
364 operator,
365 } => operation_to_css(operator, conditions, dest),
366 ContainerCondition::Style(ref query) => {
367 dest.write_str("style(")?;
368 query.to_css(dest)?;
369 dest.write_char(')')
370 }
371 ContainerCondition::ScrollState(ref query) => {
372 let needs_parens = !matches!(query, ScrollStateQuery::Feature(_));
373 dest.write_str("scroll-state")?;
374 if needs_parens {
375 dest.write_char('(')?;
376 }
377 query.to_css(dest)?;
378 if needs_parens {
379 dest.write_char(')')?;
380 }
381 Ok(())
382 }
383 ContainerCondition::Unknown(ref tokens) => tokens.to_css(dest, false),
384 }
385 }
386}
387
388impl<'i> ToCss for StyleQuery<'i> {
389 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
390 where
391 W: std::fmt::Write,
392 {
393 match *self {
394 StyleQuery::Declaration(ref f) => f.to_css(dest, false),
395 StyleQuery::Property(ref f) => f.to_css(dest),
396 StyleQuery::Not(ref c) => {
397 dest.write_str("not ")?;
398 to_css_with_parens_if_needed(&**c, dest, c.needs_parens(None, &dest.targets.current))
399 }
400 StyleQuery::Operation {
401 ref conditions,
402 operator,
403 } => operation_to_css(operator, conditions, dest),
404 }
405 }
406}
407
408impl<'i> ToCss for ScrollStateQuery<'i> {
409 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
410 where
411 W: std::fmt::Write,
412 {
413 match *self {
414 ScrollStateQuery::Feature(ref f) => f.to_css(dest),
415 ScrollStateQuery::Not(ref c) => {
416 dest.write_str("not ")?;
417 to_css_with_parens_if_needed(&**c, dest, c.needs_parens(None, &dest.targets.current))
418 }
419 ScrollStateQuery::Operation {
420 ref conditions,
421 operator,
422 } => operation_to_css(operator, conditions, dest),
423 }
424 }
425}
426
427#[derive(Debug, Clone, PartialEq)]
429#[cfg_attr(feature = "visitor", derive(Visit))]
430#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
431#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
432#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
433pub struct ContainerName<'i>(#[cfg_attr(feature = "serde", serde(borrow))] pub CustomIdent<'i>);
434
435impl<'i> Parse<'i> for ContainerName<'i> {
436 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
437 let ident = CustomIdent::parse(input)?;
438 match_ignore_ascii_case! { &*ident.0,
439 "none" | "and" | "not" | "or" => Err(input.new_unexpected_token_error(Token::Ident(ident.0.as_ref().to_owned().into()))),
440 _ => Ok(ContainerName(ident))
441 }
442 }
443}
444
445impl<'i> ToCss for ContainerName<'i> {
446 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
447 where
448 W: std::fmt::Write,
449 {
450 self.0.to_css_with_options(
453 dest,
454 match &dest.css_module {
455 Some(css_module) => css_module.config.container,
456 None => false,
457 },
458 )
459 }
460}
461
462impl<'i, T: Clone> ContainerRule<'i, T> {
463 pub(crate) fn minify(
464 &mut self,
465 context: &mut MinifyContext<'_, 'i>,
466 parent_is_unused: bool,
467 ) -> Result<bool, MinifyError> {
468 self.rules.minify(context, parent_is_unused)?;
469 Ok(self.rules.0.is_empty())
470 }
471}
472
473impl<'a, 'i, T: ToCss> ToCss for ContainerRule<'i, T> {
474 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
475 where
476 W: std::fmt::Write,
477 {
478 #[cfg(feature = "sourcemap")]
479 dest.add_mapping(self.loc);
480 dest.write_str("@container ")?;
481 let has_condition = self.condition.is_some();
482
483 if let Some(name) = &self.name {
484 name.to_css(dest)?;
485 if has_condition {
486 dest.write_char(' ')?;
487 }
488 }
489
490 if let Some(condition) = &self.condition {
491 let exclude = dest.targets.current.exclude;
493 dest.targets.current.exclude.insert(Features::MediaQueries);
494 condition.to_css(dest)?;
495 dest.targets.current.exclude = exclude;
496 }
497
498 dest.whitespace()?;
499 dest.write_char('{')?;
500 dest.indent();
501 dest.newline()?;
502 self.rules.to_css(dest)?;
503 dest.dedent();
504 dest.newline()?;
505 dest.write_char('}')
506 }
507}