Skip to main content

hatmil/
css.rs

1// css.rs
2//
3// Copyright (C) 2026  Douglas P Lau
4//
5//! CSS -- _Cascading Style Sheets_
6use std::borrow::Cow;
7use std::fmt;
8use std::fmt::Write;
9
10/// CSS data value
11pub struct Val<'a>(Cow<'a, str>);
12
13// NOTE: corner_shape() is behind `experimental` feature flag
14#[allow(rustdoc::broken_intra_doc_links)]
15/// Property list
16///
17/// ```rust
18/// # use hatmil::css::Prop;
19/// let prop = Prop::new().color("white").background_color("#234");
20/// assert_eq!(String::from(prop), "color: white; background-color: #234;");
21/// ```
22///
23/// Any [\<string\>](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/string)
24/// values must be enclosed in quotes:
25///
26/// ```rust
27/// # use hatmil::css::Prop;
28/// let prop = Prop::new()
29///     .font_family(r#""Liberation""#)
30///     .content("'new\nline'");
31/// assert_eq!(
32///     String::from(prop),
33///     r#"font-family: "Liberation"; content: 'new\A line';"#,
34/// );
35/// ```
36///
37/// ## Property Categories
38///
39/// |                                    |                                |                          |                                   |
40/// |------------------------------------|--------------------------------|--------------------------|-----------------------------------|
41/// | [basic](Prop::all())               | [column](Prop::columns())      | [font](Prop::font())     | [math](Prop::math_depth())        |
42/// | [alignment](Prop::align_content()) | [content area](Prop::height()) | [grid](Prop::grid())     | [padding](Prop::padding())        |
43/// | [animation](Prop::animation())     | [containment](Prop::contain()) | [inset](Prop::inset())   | [scroll](Prop::scroll_behavior()) |
44/// | [background](Prop::background())   | [corner](Prop::corner_shape()) | [margin](Prop::margin()) | [svg](Prop::clip_rule())          |
45/// | [border](Prop::border())           | [flex](Prop::flex())           | [mask](Prop::mask())     | [text](Prop::text_autospace())    |
46#[derive(Debug, Clone)]
47pub struct Prop {
48    /// Property separator
49    sep: &'static str,
50    /// Encoded values
51    val: String,
52}
53
54/// CSS selector
55#[derive(Clone, Debug, PartialEq, Eq)]
56pub struct Sel {
57    /// Encoded value
58    val: String,
59}
60
61/// Rule containing a selector and property list
62#[derive(Clone, Debug)]
63pub struct Rule {
64    /// Selector pattern
65    sel: Sel,
66    /// Property list
67    prop: Prop,
68}
69
70impl Val<'_> {
71    /// Get character iterator
72    pub(crate) fn chars(&'_ self) -> impl Iterator<Item = char> {
73        match &self.0 {
74            Cow::Borrowed(s) => s.chars(),
75            Cow::Owned(s) => s.chars(),
76        }
77    }
78
79    /// Return `<string>` value (double quotes)
80    fn string_dbl(&self) -> Option<&str> {
81        match &self.0 {
82            Cow::Borrowed(s) => {
83                if let Some(("", s)) = s.split_once('"')
84                    && let Some((s, "")) = s.rsplit_once('"')
85                {
86                    return Some(s);
87                }
88            }
89            Cow::Owned(s) => {
90                if let Some(("", s)) = s.split_once('"')
91                    && let Some((s, "")) = s.rsplit_once('"')
92                {
93                    return Some(s);
94                }
95            }
96        }
97        None
98    }
99
100    /// Return `<string>` value (single quotes)
101    fn string_sng(&self) -> Option<&str> {
102        match &self.0 {
103            Cow::Borrowed(s) => {
104                if let Some(("", s)) = s.split_once('\'')
105                    && let Some((s, "")) = s.rsplit_once('\'')
106                {
107                    return Some(s);
108                }
109            }
110            Cow::Owned(s) => {
111                if let Some(("", s)) = s.split_once('\'')
112                    && let Some((s, "")) = s.rsplit_once('\'')
113                {
114                    return Some(s);
115                }
116            }
117        }
118        None
119    }
120
121    /// Format a `<string>` value in double quotes
122    fn fmt_string_dbl(&self, f: &mut fmt::Formatter, s: &str) -> fmt::Result {
123        write!(f, "\"")?;
124        for c in s.chars() {
125            match c {
126                // NULL => REPLACEMENT CHARACTER
127                '\0' => write!(f, "\u{FFFD}")?,
128                '\u{0001}'..='\u{001f}' | '\u{007f}' => {
129                    if let Ok(v) = u16::try_from(c) {
130                        write!(f, "\\{v:X} ")?;
131                    }
132                }
133                '"' => write!(f, r#"\""#)?,
134                '\\' => write!(f, r"\\")?,
135                _ => write!(f, "{c}")?,
136            }
137        }
138        write!(f, "\"")
139    }
140
141    /// Format a `<string>` value in single quotes
142    fn fmt_string_sng(&self, f: &mut fmt::Formatter, s: &str) -> fmt::Result {
143        write!(f, "'")?;
144        for c in s.chars() {
145            match c {
146                // NULL => REPLACEMENT CHARACTER
147                '\0' => write!(f, "\u{FFFD}")?,
148                '\u{0001}'..='\u{001f}' | '\u{007f}' => {
149                    if let Ok(v) = u16::try_from(c) {
150                        write!(f, "\\{v:X} ")?;
151                    }
152                }
153                '\'' => write!(f, r"\'")?,
154                '\\' => write!(f, r"\\")?,
155                _ => write!(f, "{c}")?,
156            }
157        }
158        write!(f, "'")
159    }
160
161    /// Format any non-`<string>` and non-`<ident>` value
162    fn fmt_other(&self, f: &mut fmt::Formatter) -> fmt::Result {
163        for c in self.chars() {
164            match c {
165                // NULL => REPLACEMENT CHARACTER
166                '\0' => write!(f, "\u{FFFD}")?,
167                '\u{0001}'..='\u{001f}' | '\u{007f}' => {
168                    if let Ok(v) = u16::try_from(c) {
169                        write!(f, "\\{v:X} ")?;
170                    }
171                }
172                '\\' => write!(f, r"\\")?,
173                _ => write!(f, "{c}")?,
174            }
175        }
176        Ok(())
177    }
178
179    /// Add a character to an identifier value
180    fn ident_character(val: &mut String, c: char) {
181        match c {
182            // NULL => REPLACEMENT CHARACTER
183            '\0' => val.push('\u{FFFD}'),
184            '\u{0001}'..='\u{001f}' | '\u{007f}' => {
185                if let Ok(v) = u16::try_from(c) {
186                    val.push_str(&format!("\\{v:X} "));
187                }
188            }
189            '-' | '_' | '0'..='9' | 'A'..='Z' | 'a'..='z' | '\u{0080}'.. => {
190                val.push(c)
191            }
192            _ => {
193                val.push('\\');
194                val.push(c);
195            }
196        }
197    }
198
199    /// Make identifier value
200    fn to_ident(&self) -> String {
201        let mut val = String::new();
202        let mut chars = self.chars();
203        if let Some(c) = chars.next() {
204            match c {
205                // first char is a number, escape it
206                '0'..='9' => {
207                    if let Ok(v) = u16::try_from(c) {
208                        val.push_str(&format!("\\{v:X} "));
209                    }
210                }
211                '-' => {
212                    // first char is a hyphen, check second char
213                    if let Some(c) = chars.next() {
214                        match c {
215                            '0'..='9' => {
216                                if let Ok(v) = u16::try_from(c) {
217                                    val.push_str(&format!("\\{v:X} "));
218                                }
219                            }
220                            _ => Self::ident_character(&mut val, c),
221                        }
222                    } else {
223                        // "only" char is a hyphen, escape it
224                        val.push_str(r"\-");
225                    }
226                }
227                _ => Self::ident_character(&mut val, c),
228            }
229        }
230        for c in chars {
231            Self::ident_character(&mut val, c);
232        }
233        val
234    }
235}
236
237impl<'a> fmt::Display for Val<'a> {
238    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
239        if let Some(s) = self.string_dbl() {
240            return self.fmt_string_dbl(f, s);
241        }
242        if let Some(s) = self.string_sng() {
243            return self.fmt_string_sng(f, s);
244        }
245        self.fmt_other(f)
246    }
247}
248
249impl<'c> From<&'c str> for Val<'c> {
250    fn from(v: &'c str) -> Self {
251        Val(Cow::Borrowed(v))
252    }
253}
254
255impl From<String> for Val<'_> {
256    fn from(v: String) -> Self {
257        Val(Cow::Owned(v))
258    }
259}
260
261impl<'c> From<&'c String> for Val<'c> {
262    fn from(v: &'c String) -> Self {
263        Val(Cow::Borrowed(v))
264    }
265}
266
267impl<'c> From<Cow<'c, str>> for Val<'c> {
268    fn from(v: Cow<'c, str>) -> Self {
269        Val(v)
270    }
271}
272
273impl From<char> for Val<'_> {
274    fn from(v: char) -> Self {
275        Val(Cow::Owned(v.to_string()))
276    }
277}
278
279impl From<bool> for Val<'_> {
280    fn from(v: bool) -> Self {
281        Val(Cow::Owned(v.to_string()))
282    }
283}
284
285impl From<i8> for Val<'_> {
286    fn from(v: i8) -> Self {
287        Val(Cow::Owned(v.to_string()))
288    }
289}
290
291impl From<u8> for Val<'_> {
292    fn from(v: u8) -> Self {
293        Val(Cow::Owned(v.to_string()))
294    }
295}
296
297impl From<i16> for Val<'_> {
298    fn from(v: i16) -> Self {
299        Val(Cow::Owned(v.to_string()))
300    }
301}
302
303impl From<u16> for Val<'_> {
304    fn from(v: u16) -> Self {
305        Val(Cow::Owned(v.to_string()))
306    }
307}
308
309impl From<i32> for Val<'_> {
310    fn from(v: i32) -> Self {
311        Val(Cow::Owned(v.to_string()))
312    }
313}
314
315impl From<u32> for Val<'_> {
316    fn from(v: u32) -> Self {
317        Val(Cow::Owned(v.to_string()))
318    }
319}
320
321impl From<i64> for Val<'_> {
322    fn from(v: i64) -> Self {
323        Val(Cow::Owned(v.to_string()))
324    }
325}
326
327impl From<u64> for Val<'_> {
328    fn from(v: u64) -> Self {
329        Val(Cow::Owned(v.to_string()))
330    }
331}
332
333impl From<i128> for Val<'_> {
334    fn from(v: i128) -> Self {
335        Val(Cow::Owned(v.to_string()))
336    }
337}
338
339impl From<u128> for Val<'_> {
340    fn from(v: u128) -> Self {
341        Val(Cow::Owned(v.to_string()))
342    }
343}
344
345impl From<isize> for Val<'_> {
346    fn from(v: isize) -> Self {
347        Val(Cow::Owned(v.to_string()))
348    }
349}
350
351impl From<usize> for Val<'_> {
352    fn from(v: usize) -> Self {
353        Val(Cow::Owned(v.to_string()))
354    }
355}
356
357impl From<f32> for Val<'_> {
358    fn from(v: f32) -> Self {
359        Val(Cow::Owned(v.to_string()))
360    }
361}
362
363impl From<f64> for Val<'_> {
364    fn from(v: f64) -> Self {
365        Val(Cow::Owned(v.to_string()))
366    }
367}
368
369impl fmt::Display for Prop {
370    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
371        if self.val.is_empty() {
372            Ok(())
373        } else {
374            write!(f, "{};", self.val)
375        }
376    }
377}
378
379impl From<Prop> for String {
380    fn from(mut prop: Prop) -> Self {
381        // zero-copy alternative to fmt::Display
382        if !prop.val.is_empty() {
383            prop.val.push(';');
384        }
385        prop.val
386    }
387}
388
389impl Default for Prop {
390    fn default() -> Self {
391        Self::new()
392    }
393}
394
395impl Prop {
396    /// Make a new property list
397    pub fn new() -> Self {
398        Prop {
399            sep: " ",
400            val: String::with_capacity(16),
401        }
402    }
403
404    /// Push property separator
405    fn push_sep(&mut self) {
406        if !self.val.is_empty() {
407            self.val.push(';');
408            self.val.push_str(self.sep);
409        }
410    }
411
412    /// Add a [custom] property (variable)
413    ///
414    /// ```rust
415    /// # use hatmil::css::Prop;
416    /// let prop = Prop::new().custom("variable", "value");
417    /// assert_eq!(String::from(prop), "--variable: value;");
418    /// ```
419    ///
420    /// [custom]: https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties
421    pub fn custom<'a, V>(mut self, name: &str, v: V) -> Self
422    where
423        V: Into<Val<'a>>,
424    {
425        self.push_sep();
426        self.val.push_str("--");
427        // TODO: check name validity
428        self.val.push_str(name);
429        self.val.push_str(": ");
430        let _ = write!(self.val, "{}", v.into());
431        self
432    }
433
434    /// Add [!important] flag to the previous property
435    ///
436    /// ```rust
437    /// # use hatmil::css::Prop;
438    /// let prop = Prop::new().color("rebeccapurple").important();
439    /// assert_eq!(String::from(prop), "color: rebeccapurple !important;");
440    /// ```
441    ///
442    /// [!important]: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/important
443    pub fn important(mut self) -> Self {
444        if !self.val.is_empty() && !self.val.ends_with("!important") {
445            self.val.push_str(" !important");
446        }
447        self
448    }
449}
450
451/// **Basic Properties**
452///
453/// ---
454impl Prop {
455    css_prop!(all, "all");
456    #[cfg(feature = "limited-availability")]
457    css_prop!(accent_color, "accent-color");
458    css_prop!(anchor_name, "anchor-name");
459    css_prop!(anchor_scope, "anchor-scope");
460    css_prop!(appearance, "appearance");
461    css_prop!(aspect_ratio, "aspect-ratio");
462    css_prop!(backdrop_filter, "backdrop-filter");
463    css_prop!(backface_visibility, "backface-visibility");
464    css_prop!(block_size, "block-size");
465    #[cfg(feature = "limited-availability")]
466    css_prop!(box_decoration_break, "box-decoration-break");
467    css_prop!(box_shadow, "box-shadow");
468    css_prop!(box_sizing, "box-sizing");
469    css_prop!(break_after, "break-after");
470    css_prop!(break_before, "break-before");
471    css_prop!(break_inside, "break-inside");
472    css_prop!(caption_side, "caption-side");
473    #[cfg(feature = "experimental")]
474    css_prop!(caret, "caret");
475    #[cfg(feature = "experimental")]
476    css_prop!(caret_animation, "caret-animation");
477    css_prop!(caret_color, "caret-color");
478    #[cfg(feature = "experimental")]
479    css_prop!(caret_shape, "caret-shape");
480    css_prop!(clear, "clear");
481    css_prop!(clip_path, "clip-path");
482    css_prop!(color, "color");
483    css_prop!(color_scheme, "color-scheme");
484    css_prop!(content, "content");
485    css_prop!(counter_increment, "counter-increment");
486    css_prop!(counter_reset, "counter-reset");
487    css_prop!(counter_set, "counter-set");
488    css_prop!(cursor, "cursor");
489    css_prop!(direction, "direction");
490    css_prop!(display, "display");
491    #[cfg(feature = "limited-availability")]
492    css_prop!(dynamic_range_limit, "dynamic-range-limit");
493    css_prop!(empty_cells, "empty-cells");
494    #[cfg(feature = "limited-availability")]
495    css_prop!(field_sizing, "field-sizing");
496    css_prop!(filter, "filter");
497    css_prop!(float, "float");
498    #[cfg(feature = "limited-availability")]
499    css_prop!(forced_color_adjust, "forced-color-adjust");
500    css_prop!(gap, "gap");
501    #[cfg(feature = "limited-availability")]
502    css_prop!(hanging_punctuation, "hanging-punctuation");
503    css_prop!(hyphenate_character, "hyphenate-character");
504    #[cfg(feature = "limited-availability")]
505    css_prop!(hyphenate_limit_chars, "hyphenate-limit-chars");
506    css_prop!(hyphens, "hyphens");
507    css_prop!(image_orientation, "image-orientation");
508    css_prop!(image_rendering, "image-rendering");
509    #[cfg(feature = "experimental")]
510    css_prop!(image_resolution, "image-resolution");
511    #[cfg(feature = "limited-availability")]
512    css_prop!(initial_letter, "initial-letter");
513    css_prop!(inline_size, "inline-size");
514    #[cfg(feature = "experimental")]
515    css_prop!(interactivity, "interactivity");
516    #[cfg(feature = "experimental")]
517    css_prop!(interest_delay, "interest-delay");
518    #[cfg(feature = "experimental")]
519    css_prop!(interest_delay_start, "interest-delay-start");
520    #[cfg(feature = "experimental")]
521    css_prop!(interest_delay_end, "interest-delay-end");
522    #[cfg(feature = "experimental")]
523    css_prop!(interpolate_size, "interpolate-size");
524    css_prop!(isolation, "isolation");
525    css_prop!(letter_spacing, "letter-spacing");
526    css_prop!(line_break, "line-break");
527    #[cfg(feature = "limited-availability")]
528    css_prop!(line_clamp, "line-clamp");
529    css_prop!(line_height, "line-height");
530    #[cfg(feature = "experimental")]
531    css_prop!(line_height_step, "line-height-step");
532    css_prop!(list_style, "list-style");
533    css_prop!(list_style_image, "list-style-image");
534    css_prop!(list_style_position, "list-style-position");
535    css_prop!(list_style_type, "list-style-type");
536    css_prop!(max_block_size, "max-block-size");
537    css_prop!(max_inline_size, "max-inline-size");
538    css_prop!(min_block_size, "min-block-size");
539    css_prop!(min_inline_size, "min-inline-size");
540    css_prop!(mix_blend_mode, "mix-blend-mode");
541    css_prop!(object_fit, "object-fit");
542    css_prop!(object_position, "object-position");
543    #[cfg(feature = "experimental")]
544    css_prop!(object_view_box, "object-view-box");
545    css_prop!(opacity, "opacity");
546    css_prop!(order, "order");
547    #[cfg(feature = "limited-availability")]
548    css_prop!(orphans, "orphans");
549    css_prop!(overflow, "overflow");
550    #[cfg(feature = "limited-availability")]
551    css_prop!(overflow_anchor, "overflow-anchor");
552    css_prop!(overflow_block, "overflow-block");
553    #[cfg(feature = "limited-availability")]
554    css_prop!(overflow_clip_margin, "overflow-clip-margin");
555    css_prop!(overflow_inline, "overflow-inline");
556    css_prop!(overflow_wrap, "overflow-wrap");
557    css_prop!(overflow_x, "overflow-x");
558    css_prop!(overflow_y, "overflow-y");
559    #[cfg(feature = "experimental")]
560    css_prop!(overlay, "overlay");
561    css_prop!(page, "page");
562    css_prop!(paint_order, "paint-order");
563    css_prop!(perspective, "perspective");
564    css_prop!(perspective_origin, "perspective-origin");
565    css_prop!(pointer_events, "pointer-events");
566    css_prop!(position, "position");
567    #[cfg(feature = "limited-availability")]
568    css_prop!(position_anchor, "position-anchor");
569    css_prop!(position_area, "position-area");
570    css_prop!(position_try, "position-try");
571    css_prop!(position_try_fallbacks, "position-try-fallbacks");
572    css_prop!(position_try_order, "position-try-order");
573    css_prop!(position_visibility, "position-visibility");
574    css_prop!(print_color_adjust, "print-color-adjust");
575    css_prop!(quotes, "quotes");
576    #[cfg(feature = "experimental")]
577    css_prop!(reading_flow, "reading-flow");
578    #[cfg(feature = "experimental")]
579    css_prop!(reading_order, "reading-order");
580    #[cfg(feature = "limited-availability")]
581    css_prop!(resize, "resize");
582    css_prop!(rotate, "rotate");
583    css_prop!(row_gap, "row-gap");
584    css_prop!(ruby_align, "ruby-align");
585    #[cfg(feature = "limited-availability")]
586    css_prop!(ruby_overhang, "ruby-overhang");
587    css_prop!(ruby_position, "ruby-position");
588    css_prop!(scale, "scale");
589    css_prop!(scrollbar_color, "scrollbar-color");
590    css_prop!(scrollbar_gutter, "scrollbar-gutter");
591    css_prop!(scrollbar_width, "scrollbar-width");
592    css_prop!(shape_image_threshold, "shape-image-threshold");
593    css_prop!(shape_margin, "shape-margin");
594    css_prop!(shape_outside, "shape-outside");
595    #[cfg(feature = "experimental")]
596    css_prop!(speak_as, "speak-as");
597    css_prop!(tab_size, "tab-size");
598    css_prop!(table_layout, "table-layout");
599    css_prop!(touch_action, "touch-action");
600    css_prop!(transform, "transform");
601    css_prop!(transform_box, "transform-box");
602    css_prop!(transform_origin, "transform-origin");
603    css_prop!(transform_style, "transform-style");
604    css_prop!(translate, "translate");
605    css_prop!(unicode_bidi, "unicode-bidi");
606    #[cfg(feature = "limited-availability")]
607    css_prop!(user_select, "user-select");
608    css_prop!(visibility, "visibility");
609    css_prop!(white_space, "white-space");
610    css_prop!(white_space_collapse, "white-space-collapse");
611    #[cfg(feature = "limited-availability")]
612    css_prop!(widows, "widows");
613    css_prop!(will_change, "will-change");
614    css_prop!(word_break, "word-break");
615    css_prop!(word_spacing, "word-spacing");
616    css_prop!(writing_mode, "writing-mode");
617    css_prop!(z_index, "z-index");
618    css_prop!(zoom, "zoom");
619}
620
621/// **Alignment and Justification**
622///
623/// ---
624impl Prop {
625    css_prop!(align_content, "align-content");
626    css_prop!(align_items, "align-items");
627    css_prop!(align_self, "align-self");
628    css_prop!(justify_content, "justify-content");
629    css_prop!(justify_items, "justify-items");
630    css_prop!(justify_self, "justify-self");
631    css_prop!(place_content, "place-content");
632    css_prop!(place_items, "place-items");
633    css_prop!(place_self, "place-self");
634    css_prop!(text_align, "text-align");
635    css_prop!(text_align_last, "text-align-last");
636    #[cfg(feature = "limited-availability")]
637    css_prop!(text_justify, "text-justify");
638    css_prop!(vertical_align, "vertical-align");
639    css_prop!(alignment_baseline, "alignment-baseline");
640    css_prop!(baseline_shift, "baseline-shift");
641    #[cfg(feature = "limited-availability")]
642    css_prop!(baseline_source, "baseline-source");
643    css_prop!(dominant_baseline, "dominant-baseline");
644}
645
646/// **Animation**
647///
648/// ---
649impl Prop {
650    css_prop!(animation, "animation");
651    css_prop!(animation_composition, "animation-composition");
652    css_prop!(animation_delay, "animation-delay");
653    css_prop!(animation_direction, "animation-direction");
654    css_prop!(animation_duration, "animation-duration");
655    css_prop!(animation_fill_mode, "animation-fill-mode");
656    css_prop!(animation_iteration_count, "animation-iteration-count");
657    css_prop!(animation_name, "animation-name");
658    css_prop!(animation_play_state, "animation-play-state");
659    #[cfg(feature = "limited-availability")]
660    css_prop!(animation_range_start, "animation-range-start");
661    #[cfg(feature = "limited-availability")]
662    css_prop!(animation_range_end, "animation-range-end");
663    #[cfg(feature = "limited-availability")]
664    css_prop!(animation_range, "animation-range");
665    #[cfg(feature = "limited-availability")]
666    css_prop!(animation_timeline, "animation-timeline");
667    css_prop!(animation_timing_function, "animation-timing-function");
668    css_prop!(offset, "offset");
669    css_prop!(offset_anchor, "offset-anchor");
670    css_prop!(offset_distance, "offset-distance");
671    css_prop!(offset_path, "offset-path");
672    css_prop!(offset_position, "offset-position");
673    css_prop!(offset_rotate, "offset-rotate");
674    #[cfg(feature = "limited-availability")]
675    css_prop!(timeline_scope, "timeline-scope");
676    css_prop!(transition, "transition");
677    css_prop!(transition_behavior, "transition-behavior");
678    css_prop!(transition_delay, "transition-delay");
679    css_prop!(transition_duration, "transition-duration");
680    css_prop!(transition_property, "transition-property");
681    css_prop!(transition_timing_function, "transition-timing-function");
682    #[cfg(feature = "limited-availability")]
683    css_prop!(view_timeline, "view-timeline");
684    #[cfg(feature = "limited-availability")]
685    css_prop!(view_timeline_axis, "view-timeline-axis");
686    #[cfg(feature = "limited-availability")]
687    css_prop!(view_timeline_inset, "view-timeline-inset");
688    #[cfg(feature = "limited-availability")]
689    css_prop!(view_timeline_name, "view-timeline-name");
690    css_prop!(view_transition_class, "view-transition-class");
691    css_prop!(view_transition_name, "view-transition-name");
692}
693
694/// **Background**
695///
696/// ---
697impl Prop {
698    css_prop!(background, "background");
699    css_prop!(background_attachment, "background-attachment");
700    css_prop!(background_blend_mode, "background-blend-mode");
701    css_prop!(background_clip, "background-clip");
702    css_prop!(background_color, "background-color");
703    css_prop!(background_image, "background-image");
704    css_prop!(background_origin, "background-origin");
705    css_prop!(background_position, "background-position");
706    css_prop!(background_position_x, "background-position-x");
707    css_prop!(background_position_y, "background-position-y");
708    css_prop!(background_repeat, "background-repeat");
709    #[cfg(feature = "experimental")]
710    css_prop!(background_repeat_x, "background-repeat-x");
711    #[cfg(feature = "experimental")]
712    css_prop!(background_repeat_y, "background-repeat-y");
713    css_prop!(background_size, "background-size");
714}
715
716/// **Border and Outline**
717///
718/// ---
719impl Prop {
720    css_prop!(border, "border");
721    css_prop!(border_collapse, "border-collapse");
722    css_prop!(border_color, "border-color");
723    css_prop!(border_style, "border-style");
724    css_prop!(border_spacing, "border-spacing");
725    css_prop!(border_width, "border-width");
726    css_prop!(border_bottom, "border-bottom");
727    css_prop!(border_bottom_color, "border-bottom-color");
728    css_prop!(border_bottom_style, "border-bottom-style");
729    css_prop!(border_bottom_width, "border-bottom-width");
730    css_prop!(border_left, "border-left");
731    css_prop!(border_left_color, "border-left-color");
732    css_prop!(border_left_style, "border-left-style");
733    css_prop!(border_left_width, "border-left-width");
734    css_prop!(border_right, "border-right");
735    css_prop!(border_right_color, "border-right-color");
736    css_prop!(border_right_style, "border-right-style");
737    css_prop!(border_right_width, "border-right-width");
738    css_prop!(border_top, "border-top");
739    css_prop!(border_top_color, "border-top-color");
740    css_prop!(border_top_style, "border-top-style");
741    css_prop!(border_top_width, "border-top-width");
742    css_prop!(border_radius, "border-radius");
743    css_prop!(border_bottom_left_radius, "border-bottom-left-radius");
744    css_prop!(border_bottom_right_radius, "border-bottom-right-radius");
745    css_prop!(border_top_left_radius, "border-top-left-radius");
746    css_prop!(border_top_right_radius, "border-top-right-radius");
747    css_prop!(border_start_start_radius, "border-start-start-radius");
748    css_prop!(border_start_end_radius, "border-start-end-radius");
749    css_prop!(border_end_start_radius, "border-end-start-radius");
750    css_prop!(border_end_end_radius, "border-end-end-radius");
751    css_prop!(border_block, "border-block");
752    css_prop!(border_block_color, "border-block-color");
753    css_prop!(border_block_style, "border-block-style");
754    css_prop!(border_block_width, "border-block-width");
755    css_prop!(border_block_start, "border-block-start");
756    css_prop!(border_block_start_color, "border-block-start-color");
757    css_prop!(border_block_start_style, "border-block-start-style");
758    css_prop!(border_block_start_width, "border-block-start-width");
759    css_prop!(border_block_end, "border-block-end");
760    css_prop!(border_block_end_color, "border-block-end-color");
761    css_prop!(border_block_end_style, "border-block-end-style");
762    css_prop!(border_block_end_width, "border-block-end-width");
763    css_prop!(border_image, "border-image");
764    css_prop!(border_image_outset, "border-image-outset");
765    css_prop!(border_image_repeat, "border-image-repeat");
766    css_prop!(border_image_slice, "border-image-slice");
767    css_prop!(border_image_source, "border-image-source");
768    css_prop!(border_image_width, "border-image-width");
769    css_prop!(border_inline, "border-inline");
770    css_prop!(border_inline_color, "border-inline-color");
771    css_prop!(border_inline_style, "border-inline-style");
772    css_prop!(border_inline_width, "border-inline-width");
773    css_prop!(border_inline_start, "border-inline-start");
774    css_prop!(border_inline_start_color, "border-inline-start-color");
775    css_prop!(border_inline_start_style, "border-inline-start-style");
776    css_prop!(border_inline_start_width, "border-inline-start-width");
777    css_prop!(border_inline_end, "border-inline-end");
778    css_prop!(border_inline_end_color, "border-inline-end-color");
779    css_prop!(border_inline_end_style, "border-inline-end-style");
780    css_prop!(border_inline_end_width, "border-inline-end-width");
781    css_prop!(outline, "outline");
782    css_prop!(outline_color, "outline-color");
783    css_prop!(outline_offset, "outline-offset");
784    css_prop!(outline_style, "outline-style");
785    css_prop!(outline_width, "outline-width");
786}
787
788/// **Column**
789///
790/// ---
791impl Prop {
792    css_prop!(columns, "columns");
793    css_prop!(column_count, "column-count");
794    css_prop!(column_fill, "column-fill");
795    css_prop!(column_gap, "column-gap");
796    #[cfg(feature = "experimental")]
797    css_prop!(column_height, "column-height");
798    css_prop!(column_rule, "column-rule");
799    css_prop!(column_rule_color, "column-rule-color");
800    css_prop!(column_rule_style, "column-rule-style");
801    css_prop!(column_rule_width, "column-rule-width");
802    css_prop!(column_span, "column-span");
803    css_prop!(column_width, "column-width");
804    #[cfg(feature = "experimental")]
805    css_prop!(column_wrap, "column-wrap");
806}
807
808/// **Content Area**
809///
810/// ---
811impl Prop {
812    css_prop!(height, "height");
813    css_prop!(min_height, "min-height");
814    css_prop!(max_height, "max-height");
815    css_prop!(width, "width");
816    css_prop!(max_width, "max-width");
817    css_prop!(min_width, "min-width");
818}
819
820/// **Containment**
821///
822/// ---
823impl Prop {
824    css_prop!(contain, "contain");
825    css_prop!(contain_intrinsic_block_size, "contain-intrinsic-block-size");
826    css_prop!(contain_intrinsic_height, "contain-intrinsic-height");
827    css_prop!(
828        contain_intrinsic_inline_size,
829        "contain-intrinsic-inline-size"
830    );
831    css_prop!(contain_intrinsic_size, "contain-intrinsic-size");
832    css_prop!(contain_intrinsic_width, "contain-intrinsic-width");
833    css_prop!(container, "container");
834    css_prop!(container_name, "container-name");
835    css_prop!(container_type, "container-type");
836    css_prop!(content_visibility, "content-visibility");
837}
838
839#[cfg(feature = "experimental")]
840/// **Corner**
841///
842/// ---
843impl Prop {
844    css_prop!(corner_shape, "corner-shape");
845    css_prop!(corner_bottom_shape, "corner-bottom-shape");
846    css_prop!(corner_left_shape, "corner-left-shape");
847    css_prop!(corner_right_shape, "corner-right-shape");
848    css_prop!(corner_top_shape, "corner-top-shape");
849    css_prop!(corner_bottom_left_shape, "corner-bottom-left-shape");
850    css_prop!(corner_bottom_right_shape, "corner-bottom-right-shape");
851    css_prop!(corner_top_left_shape, "corner-top-left-shape");
852    css_prop!(corner_top_right_shape, "corner-top-right-shape");
853    css_prop!(corner_block_start_shape, "corner-block-start-shape");
854    css_prop!(corner_block_end_shape, "corner-block-end-shape");
855    css_prop!(corner_inline_end_shape, "corner-inline-end-shape");
856    css_prop!(corner_inline_start_shape, "corner-inline-start-shape");
857    css_prop!(corner_start_start_shape, "corner-start-start-shape");
858    css_prop!(corner_start_end_shape, "corner-start-end-shape");
859    css_prop!(corner_end_start_shape, "corner-end-start-shape");
860    css_prop!(corner_end_end_shape, "corner-end-end-shape");
861}
862
863/// **Flex**
864///
865/// ---
866impl Prop {
867    css_prop!(flex, "flex");
868    css_prop!(flex_basis, "flex-basis");
869    css_prop!(flex_direction, "flex-direction");
870    css_prop!(flex_flow, "flex-flow");
871    css_prop!(flex_grow, "flex-grow");
872    css_prop!(flex_shrink, "flex-shrink");
873    css_prop!(flex_wrap, "flex-wrap");
874}
875
876/// **Font**
877///
878/// ---
879impl Prop {
880    css_prop!(font, "font");
881    css_prop!(font_family, "font-family");
882    css_prop!(font_feature_settings, "font-feature-settings");
883    css_prop!(font_kerning, "font-kerning");
884    #[cfg(feature = "limited-availability")]
885    css_prop!(font_language_override, "font-language-override");
886    css_prop!(font_optical_sizing, "font-optical-sizing");
887    css_prop!(font_palette, "font-palette");
888    css_prop!(font_size, "font-size");
889    css_prop!(font_size_adjust, "font-size-adjust");
890    css_prop!(font_stretch, "font-stretch");
891    css_prop!(font_style, "font-style");
892    css_prop!(font_synthesis, "font-synthesis");
893    #[cfg(feature = "experimental")]
894    css_prop!(font_synthesis_position, "font-synthesis-position");
895    css_prop!(font_synthesis_small_caps, "font-synthesis-small-caps");
896    css_prop!(font_synthesis_style, "font-synthesis-style");
897    css_prop!(font_synthesis_weight, "font-synthesis-weight");
898    css_prop!(font_variant, "font-variant");
899    css_prop!(font_variant_alternates, "font-variant-alternates");
900    css_prop!(font_variant_caps, "font-variant-caps");
901    css_prop!(font_variant_east_asian, "font-variant-east-asian");
902    #[cfg(feature = "limited-availability")]
903    css_prop!(font_variant_emoji, "font-variant-emoji");
904    css_prop!(font_variant_ligatures, "font-variant-ligatures");
905    css_prop!(font_variant_numeric, "font-variant-numeric");
906    css_prop!(font_variant_position, "font-variant-position");
907    css_prop!(font_variation_settings, "font-variation-settings");
908    css_prop!(font_weight, "font-weight");
909    #[cfg(feature = "experimental")]
910    css_prop!(font_width, "font-width");
911}
912
913/// **Grid**
914///
915/// ---
916impl Prop {
917    css_prop!(grid, "grid");
918    css_prop!(grid_area, "grid-area");
919    css_prop!(grid_auto_columns, "grid-auto-columns");
920    css_prop!(grid_auto_flow, "grid-auto-flow");
921    css_prop!(grid_auto_rows, "grid-auto-rows");
922    css_prop!(grid_column, "grid-column");
923    css_prop!(grid_column_start, "grid-column-start");
924    css_prop!(grid_column_end, "grid-column-end");
925    css_prop!(grid_row, "grid-row");
926    css_prop!(grid_row_start, "grid-row-start");
927    css_prop!(grid_row_end, "grid-row-end");
928    css_prop!(grid_template, "grid-template");
929    css_prop!(grid_template_areas, "grid-template-areas");
930    css_prop!(grid_template_columns, "grid-template-columns");
931    css_prop!(grid_template_rows, "grid-template-rows");
932}
933
934/// **Inset**
935///
936/// ---
937impl Prop {
938    css_prop!(inset, "inset");
939    css_prop!(bottom, "bottom");
940    css_prop!(left, "left");
941    css_prop!(right, "right");
942    css_prop!(top, "top");
943    css_prop!(inset_block, "inset-block");
944    css_prop!(inset_block_start, "inset-block-start");
945    css_prop!(inset_block_end, "inset-block-end");
946    css_prop!(inset_inline, "inset-inline");
947    css_prop!(inset_inline_start, "inset-inline-start");
948    css_prop!(inset_inline_end, "inset-inline-end");
949}
950
951/// **Margin**
952///
953/// ---
954impl Prop {
955    css_prop!(margin, "margin");
956    css_prop!(margin_bottom, "margin-bottom");
957    css_prop!(margin_left, "margin-left");
958    css_prop!(margin_right, "margin-right");
959    css_prop!(margin_top, "margin-top");
960    css_prop!(margin_block, "margin-block");
961    css_prop!(margin_block_start, "margin-block-start");
962    css_prop!(margin_block_end, "margin-block-end");
963    css_prop!(margin_inline, "margin-inline");
964    css_prop!(margin_inline_start, "margin-inline-start");
965    css_prop!(margin_inline_end, "margin-inline-end");
966    #[cfg(feature = "experimental")]
967    css_prop!(margin_trim, "margin-trim");
968}
969
970/// **Mask**
971///
972/// ---
973impl Prop {
974    css_prop!(mask, "mask");
975    #[cfg(feature = "limited-availability")]
976    css_prop!(mask_border, "mask-border");
977    #[cfg(feature = "limited-availability")]
978    css_prop!(mask_border_mode, "mask-border-mode");
979    #[cfg(feature = "limited-availability")]
980    css_prop!(mask_border_outset, "mask-border-outset");
981    #[cfg(feature = "limited-availability")]
982    css_prop!(mask_border_repeat, "mask-border-repeat");
983    #[cfg(feature = "limited-availability")]
984    css_prop!(mask_border_slice, "mask-border-slice");
985    #[cfg(feature = "limited-availability")]
986    css_prop!(mask_border_source, "mask-border-source");
987    #[cfg(feature = "limited-availability")]
988    css_prop!(mask_border_width, "mask-border-width");
989    css_prop!(mask_clip, "mask-clip");
990    css_prop!(mask_composite, "mask-composite");
991    css_prop!(mask_image, "mask-image");
992    css_prop!(mask_mode, "mask-mode");
993    css_prop!(mask_origin, "mask-origin");
994    css_prop!(mask_position, "mask-position");
995    css_prop!(mask_repeat, "mask-repeat");
996    css_prop!(mask_size, "mask-size");
997}
998
999/// **MathML**
1000///
1001/// ---
1002impl Prop {
1003    css_prop!(math_depth, "math-depth");
1004    css_prop!(math_shift, "math-shift");
1005    css_prop!(math_style, "math-style");
1006}
1007
1008/// **Padding**
1009///
1010/// ---
1011impl Prop {
1012    css_prop!(padding, "padding");
1013    css_prop!(padding_bottom, "padding-bottom");
1014    css_prop!(padding_left, "padding-left");
1015    css_prop!(padding_right, "padding-right");
1016    css_prop!(padding_top, "padding-top");
1017    css_prop!(padding_block, "padding-block");
1018    css_prop!(padding_block_start, "padding-block-start");
1019    css_prop!(padding_block_end, "padding-block-end");
1020    css_prop!(padding_inline, "padding-inline");
1021    css_prop!(padding_inline_start, "padding-inline-start");
1022    css_prop!(padding_inline_end, "padding-inline-end");
1023}
1024
1025/// **Scrolling Area**
1026///
1027/// ---
1028impl Prop {
1029    css_prop!(scroll_behavior, "scroll-behavior");
1030    #[cfg(feature = "experimental")]
1031    css_prop!(scroll_initial_target, "scroll-initial-target");
1032    #[cfg(feature = "experimental")]
1033    css_prop!(scroll_marker_group, "scroll-marker-group");
1034    css_prop!(scroll_margin, "scroll-margin");
1035    css_prop!(scroll_margin_bottom, "scroll-margin-bottom");
1036    css_prop!(scroll_margin_left, "scroll-margin-left");
1037    css_prop!(scroll_margin_right, "scroll-margin-right");
1038    css_prop!(scroll_margin_top, "scroll-margin-top");
1039    css_prop!(scroll_margin_block, "scroll-margin-block");
1040    css_prop!(scroll_margin_block_start, "scroll-margin-block-start");
1041    css_prop!(scroll_margin_block_end, "scroll-margin-block-end");
1042    css_prop!(scroll_margin_inline, "scroll-margin-inline");
1043    css_prop!(scroll_margin_inline_start, "scroll-margin-inline-start");
1044    css_prop!(scroll_margin_inline_end, "scroll-margin-inline-end");
1045    css_prop!(scroll_padding, "scroll-padding");
1046    css_prop!(scroll_padding_bottom, "scroll-padding-bottom");
1047    css_prop!(scroll_padding_left, "scroll-padding-left");
1048    css_prop!(scroll_padding_right, "scroll-padding-right");
1049    css_prop!(scroll_padding_top, "scroll-padding-top");
1050    css_prop!(scroll_padding_block, "scroll-padding-block");
1051    css_prop!(scroll_padding_block_start, "scroll-padding-block-start");
1052    css_prop!(scroll_padding_block_end, "scroll-padding-block-end");
1053    css_prop!(scroll_padding_inline, "scroll-padding-inline");
1054    css_prop!(scroll_padding_inline_start, "scroll-padding-inline-start");
1055    css_prop!(scroll_padding_inline_end, "scroll-padding-inline-end");
1056    css_prop!(scroll_snap_align, "scroll-snap-align");
1057    css_prop!(scroll_snap_stop, "scroll-snap-stop");
1058    css_prop!(scroll_snap_type, "scroll-snap-type");
1059    #[cfg(feature = "experimental")]
1060    css_prop!(scroll_target_group, "scroll-target-group");
1061    #[cfg(feature = "limited-availability")]
1062    css_prop!(scroll_timeline, "scroll-timeline");
1063    #[cfg(feature = "limited-availability")]
1064    css_prop!(scroll_timeline_axis, "scroll-timeline-axis");
1065    #[cfg(feature = "limited-availability")]
1066    css_prop!(scroll_timeline_name, "scroll-timeline-name");
1067    #[cfg(feature = "limited-availability")]
1068    css_prop!(overscroll_behavior, "overscroll-behavior");
1069    #[cfg(feature = "limited-availability")]
1070    css_prop!(overscroll_behavior_block, "overscroll-behavior-block");
1071    #[cfg(feature = "limited-availability")]
1072    css_prop!(overscroll_behavior_inline, "overscroll-behavior-inline");
1073    #[cfg(feature = "limited-availability")]
1074    css_prop!(overscroll_behavior_x, "overscroll-behavior-x");
1075    #[cfg(feature = "limited-availability")]
1076    css_prop!(overscroll_behavior_y, "overscroll-behavior-y");
1077}
1078
1079/// **SVG**
1080///
1081/// ---
1082impl Prop {
1083    css_prop!(clip_rule, "clip-rule");
1084    css_prop!(color_interpolation, "color-interpolation");
1085    css_prop!(color_interpolation_filters, "color-interpolation-filters");
1086    css_prop!(cx, "cx");
1087    css_prop!(cy, "cy");
1088    #[cfg(feature = "limited-availability")]
1089    css_prop!(d, "d");
1090    css_prop!(fill, "fill");
1091    css_prop!(fill_opacity, "fill-opacity");
1092    css_prop!(fill_rule, "fill-rule");
1093    css_prop!(flood_color, "flood-color");
1094    css_prop!(flood_opacity, "flood-opacity");
1095    css_prop!(lighting_color, "lighting-color");
1096    css_prop!(marker, "marker");
1097    css_prop!(marker_start, "marker-start");
1098    css_prop!(marker_mid, "marker-mid");
1099    css_prop!(marker_end, "marker-end");
1100    css_prop!(mask_type, "mask-type");
1101    css_prop!(r, "r");
1102    css_prop!(rx, "rx");
1103    css_prop!(ry, "ry");
1104    css_prop!(shape_rendering, "shape-rendering");
1105    css_prop!(stop_color, "stop-color");
1106    css_prop!(stop_opacity, "stop-opacity");
1107    css_prop!(stroke, "stroke");
1108    css_prop!(stroke_dasharray, "stroke-dasharray");
1109    css_prop!(stroke_dashoffset, "stroke-dashoffset");
1110    css_prop!(stroke_linecap, "stroke-linecap");
1111    css_prop!(stroke_linejoin, "stroke-linejoin");
1112    css_prop!(stroke_miterlimit, "stroke-miterlimit");
1113    css_prop!(stroke_opacity, "stroke-opacity");
1114    css_prop!(stroke_width, "stroke-width");
1115    css_prop!(text_anchor, "text-anchor");
1116    css_prop!(vector_effect, "vector-effect");
1117    css_prop!(x, "x");
1118    css_prop!(y, "y");
1119}
1120
1121/// **Text**
1122///
1123/// ---
1124impl Prop {
1125    css_prop!(text_autospace, "text-autospace");
1126    #[cfg(feature = "limited-availability")]
1127    css_prop!(text_box, "text-box");
1128    #[cfg(feature = "limited-availability")]
1129    css_prop!(text_box_edge, "text-box-edge");
1130    #[cfg(feature = "limited-availability")]
1131    css_prop!(text_box_trim, "text-box-trim");
1132    css_prop!(text_combine_upright, "text-combine-upright");
1133    css_prop!(text_decoration, "text-decoration");
1134    css_prop!(text_decoration_color, "text-decoration-color");
1135    #[cfg(feature = "experimental")]
1136    css_prop!(text_decoration_inset, "text-decoration-inset");
1137    css_prop!(text_decoration_line, "text-decoration-line");
1138    css_prop!(text_decoration_skip_ink, "text-decoration-skip-ink");
1139    css_prop!(text_decoration_style, "text-decoration-style");
1140    css_prop!(text_decoration_thickness, "text-decoration-thickness");
1141    css_prop!(text_emphasis, "text-emphasis");
1142    css_prop!(text_emphasis_color, "text-emphasis-color");
1143    css_prop!(text_emphasis_position, "text-emphasis-position");
1144    css_prop!(text_emphasis_style, "text-emphasis-style");
1145    css_prop!(text_indent, "text-indent");
1146    css_prop!(text_orientation, "text-orientation");
1147    css_prop!(text_overflow, "text-overflow");
1148    css_prop!(text_rendering, "text-rendering");
1149    css_prop!(text_shadow, "text-shadow");
1150    #[cfg(feature = "experimental")]
1151    css_prop!(text_size_adjust, "text-size-adjust");
1152    #[cfg(feature = "experimental")]
1153    css_prop!(text_spacing_trim, "text-spacing-trim");
1154    css_prop!(text_transform, "text-transform");
1155    css_prop!(text_underline_offset, "text-underline-offset");
1156    css_prop!(text_underline_position, "text-underline-position");
1157    css_prop!(text_wrap, "text-wrap");
1158    css_prop!(text_wrap_mode, "text-wrap-mode");
1159    css_prop!(text_wrap_style, "text-wrap-style");
1160}
1161
1162impl fmt::Display for Sel {
1163    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1164        write!(f, "{}", self.val)
1165    }
1166}
1167
1168impl Sel {
1169    /// Make a universal selector `*`
1170    pub fn any() -> Self {
1171        Sel {
1172            val: "*".to_string(),
1173        }
1174    }
1175
1176    /// Make a new [type selector]
1177    ///
1178    /// [type selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/Type_selectors
1179    pub fn tp<'a, V>(tp: V) -> Self
1180    where
1181        V: Into<Val<'a>>,
1182    {
1183        let val = tp.into().to_ident();
1184        Sel { val }
1185    }
1186
1187    /// Make a new [class selector]
1188    ///
1189    /// [class selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/Class_selectors
1190    pub fn cls<'a, V>(cls: V) -> Self
1191    where
1192        V: Into<Val<'a>>,
1193    {
1194        let mut val = cls.into().to_ident();
1195        val.insert(0, '.');
1196        Sel { val }
1197    }
1198
1199    /// Make a new [ID selector]
1200    ///
1201    /// [ID selector]: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/ID_selectors
1202    pub fn ident<'a, V>(ident: V) -> Self
1203    where
1204        V: Into<Val<'a>>,
1205    {
1206        let mut val = ident.into().to_ident();
1207        val.insert(0, '#');
1208        Sel { val }
1209    }
1210
1211    /// Combine with another selector
1212    pub fn with(mut self, other: Self) -> Self {
1213        self.val.push_str(&other.val);
1214        self
1215    }
1216
1217    /// Combine using list combinator `,`
1218    pub fn list(mut self, other: Self) -> Self {
1219        self.val.push_str(", ");
1220        self.val.push_str(&other.val);
1221        self
1222    }
1223
1224    /// Combine using descendant combinator ` `
1225    pub fn descendant(mut self, other: Self) -> Self {
1226        self.val.push(' ');
1227        self.val.push_str(&other.val);
1228        self
1229    }
1230
1231    /// Combine using child combinator `>`
1232    pub fn child(mut self, other: Self) -> Self {
1233        self.val.push_str(" > ");
1234        self.val.push_str(&other.val);
1235        self
1236    }
1237
1238    /// Combine using next sibling combinator `+`
1239    pub fn next_sibling(mut self, other: Self) -> Self {
1240        self.val.push_str(" + ");
1241        self.val.push_str(&other.val);
1242        self
1243    }
1244
1245    /// Combine using subsequent sibling combinator `+`
1246    pub fn subsequent_sibling(mut self, other: Self) -> Self {
1247        self.val.push_str(" ~ ");
1248        self.val.push_str(&other.val);
1249        self
1250    }
1251}
1252
1253impl fmt::Display for Rule {
1254    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1255        writeln!(f, "{} {{\n{}\n}}", self.sel, self.prop)
1256    }
1257}
1258
1259impl Rule {
1260    /// Create a CSS rule
1261    pub fn new(sel: Sel, mut prop: Prop) -> Self {
1262        prop.sep = "\n";
1263        Rule { sel, prop }
1264    }
1265
1266    /// Get selector
1267    pub fn selector(&self) -> &Sel {
1268        &self.sel
1269    }
1270
1271    /// Get property list
1272    pub fn prop(&self) -> &Prop {
1273        &self.prop
1274    }
1275}
1276
1277#[cfg(test)]
1278mod test {
1279    use super::*;
1280
1281    #[test]
1282    fn sel() {
1283        let sel = Sel::tp("td").list(Sel::tp("th"));
1284        assert_eq!(sel.to_string(), "td, th");
1285    }
1286
1287    #[test]
1288    fn rule() {
1289        let prop = Prop::new().color("rebeccapurple");
1290        let rule = Rule::new(Sel::any(), prop);
1291        assert_eq!(rule.to_string(), "* {\ncolor: rebeccapurple;\n}\n");
1292    }
1293}