1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! CSS properties related to positioning.

use super::Property;
use crate::context::PropertyHandlerContext;
use crate::declaration::DeclarationList;
use crate::error::{ParserError, PrinterError};
use crate::prefixes::Feature;
use crate::printer::Printer;
use crate::targets::Browsers;
use crate::traits::{Parse, PropertyHandler, ToCss};
use crate::values::number::CSSInteger;
use crate::vendor_prefix::VendorPrefix;
use cssparser::*;

/// A value for the [position](https://www.w3.org/TR/css-position-3/#position-property) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
  feature = "serde",
  derive(serde::Serialize, serde::Deserialize),
  serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
pub enum Position {
  /// The box is laid in the document flow.
  Static,
  /// The box is laid out in the document flow and offset from the resulting position.
  Relative,
  /// The box is taken out of document flow and positioned in reference to its relative ancestor.
  Absolute,
  /// Similar to relative but adjusted according to the ancestor scrollable element.
  Sticky(VendorPrefix),
  /// The box is taken out of the document flow and positioned in reference to the page viewport.
  Fixed,
}

impl<'i> Parse<'i> for Position {
  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
    let location = input.current_source_location();
    let ident = input.expect_ident()?;
    match_ignore_ascii_case! { &*ident,
      "static" => Ok(Position::Static),
      "relative" => Ok(Position::Relative),
      "absolute" => Ok(Position::Absolute),
      "fixed" => Ok(Position::Fixed),
      "sticky" => Ok(Position::Sticky(VendorPrefix::None)),
      "-webkit-sticky" => Ok(Position::Sticky(VendorPrefix::WebKit)),
      _ => Err(location.new_unexpected_token_error(
        cssparser::Token::Ident(ident.clone())
      ))
    }
  }
}

impl ToCss for Position {
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
  where
    W: std::fmt::Write,
  {
    match self {
      Position::Static => dest.write_str("static"),
      Position::Relative => dest.write_str("relative"),
      Position::Absolute => dest.write_str("absolute"),
      Position::Fixed => dest.write_str("fixed"),
      Position::Sticky(prefix) => {
        prefix.to_css(dest)?;
        dest.write_str("sticky")
      }
    }
  }
}

/// A value for the [z-index](https://drafts.csswg.org/css2/#z-index) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
  feature = "serde",
  derive(serde::Serialize, serde::Deserialize),
  serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
pub enum ZIndex {
  /// The `auto` keyword.
  Auto,
  /// An integer value.
  Integer(CSSInteger),
}

impl<'i> Parse<'i> for ZIndex {
  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
    if let Ok(value) = input.expect_integer() {
      return Ok(ZIndex::Integer(value));
    }

    input.expect_ident_matching("auto")?;
    Ok(ZIndex::Auto)
  }
}

impl ToCss for ZIndex {
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
  where
    W: std::fmt::Write,
  {
    match self {
      ZIndex::Auto => dest.write_str("auto"),
      ZIndex::Integer(value) => value.to_css(dest),
    }
  }
}

#[derive(Default)]
pub(crate) struct PositionHandler {
  targets: Option<Browsers>,
  position: Option<Position>,
}

impl PositionHandler {
  pub fn new(targets: Option<Browsers>) -> PositionHandler {
    PositionHandler {
      targets,
      ..PositionHandler::default()
    }
  }
}

impl<'i> PropertyHandler<'i> for PositionHandler {
  fn handle_property(
    &mut self,
    property: &Property<'i>,
    _: &mut DeclarationList<'i>,
    _: &mut PropertyHandlerContext<'i, '_>,
  ) -> bool {
    if let Property::Position(position) = property {
      if let (Some(Position::Sticky(cur)), Position::Sticky(new)) = (&mut self.position, position) {
        *cur |= *new;
      } else {
        self.position = Some(position.clone());
      }

      return true;
    }

    false
  }

  fn finalize(&mut self, dest: &mut DeclarationList, _: &mut PropertyHandlerContext<'i, '_>) {
    if self.position.is_none() {
      return;
    }

    if let Some(position) = std::mem::take(&mut self.position) {
      match position {
        Position::Sticky(mut prefix) => {
          if prefix.contains(VendorPrefix::None) {
            if let Some(targets) = self.targets {
              prefix = Feature::Sticky.prefixes_for(targets)
            }
          }

          if prefix.contains(VendorPrefix::WebKit) {
            dest.push(Property::Position(Position::Sticky(VendorPrefix::WebKit)))
          }

          if prefix.contains(VendorPrefix::None) {
            dest.push(Property::Position(Position::Sticky(VendorPrefix::None)))
          }
        }
        _ => dest.push(Property::Position(position)),
      }
    }
  }
}