1use crate::compat::Feature;
4use crate::context::PropertyHandlerContext;
5use crate::declaration::{DeclarationBlock, DeclarationList};
6use crate::error::{ParserError, PrinterError};
7use crate::macros::{define_shorthand, enum_property, shorthand_property};
8use crate::printer::Printer;
9use crate::properties::{Property, PropertyId};
10use crate::targets::{Browsers, Targets};
11use crate::traits::{FallbackValues, IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};
12use crate::values::color::CssColor;
13use crate::values::number::CSSNumber;
14use crate::values::string::CowArcStr;
15use crate::values::url::Url;
16#[cfg(feature = "visitor")]
17use crate::visitor::Visit;
18use bitflags::bitflags;
19use cssparser::*;
20use smallvec::SmallVec;
21
22use super::custom::Token;
23use super::{CustomProperty, CustomPropertyName, TokenList, TokenOrValue};
24
25enum_property! {
26 pub enum Resize {
28 None,
30 Both,
32 Horizontal,
34 Vertical,
36 Block,
38 Inline,
40 }
41}
42
43#[derive(Debug, Clone, PartialEq)]
47#[cfg_attr(feature = "visitor", derive(Visit))]
48#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
51pub struct CursorImage<'i> {
52 #[cfg_attr(feature = "serde", serde(borrow))]
54 pub url: Url<'i>,
55 pub hotspot: Option<(CSSNumber, CSSNumber)>,
57}
58
59impl<'i> Parse<'i> for CursorImage<'i> {
60 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
61 let url = Url::parse(input)?;
62 let hotspot = if let Ok(x) = input.try_parse(CSSNumber::parse) {
63 let y = CSSNumber::parse(input)?;
64 Some((x, y))
65 } else {
66 None
67 };
68
69 Ok(CursorImage { url, hotspot })
70 }
71}
72
73impl<'i> ToCss for CursorImage<'i> {
74 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
75 where
76 W: std::fmt::Write,
77 {
78 self.url.to_css(dest)?;
79
80 if let Some((x, y)) = self.hotspot {
81 dest.write_char(' ')?;
82 x.to_css(dest)?;
83 dest.write_char(' ')?;
84 y.to_css(dest)?;
85 }
86 Ok(())
87 }
88}
89
90enum_property! {
91 #[allow(missing_docs)]
96 pub enum CursorKeyword {
97 Auto,
98 Default,
99 None,
100 ContextMenu,
101 Help,
102 Pointer,
103 Progress,
104 Wait,
105 Cell,
106 Crosshair,
107 Text,
108 VerticalText,
109 Alias,
110 Copy,
111 Move,
112 NoDrop,
113 NotAllowed,
114 Grab,
115 Grabbing,
116 EResize,
117 NResize,
118 NeResize,
119 NwResize,
120 SResize,
121 SeResize,
122 SwResize,
123 WResize,
124 EwResize,
125 NsResize,
126 NeswResize,
127 NwseResize,
128 ColResize,
129 RowResize,
130 AllScroll,
131 ZoomIn,
132 ZoomOut,
133 }
134}
135
136#[derive(Debug, Clone, PartialEq)]
138#[cfg_attr(feature = "visitor", derive(Visit))]
139#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
140#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
141#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
142pub struct Cursor<'i> {
143 #[cfg_attr(feature = "serde", serde(borrow))]
145 pub images: SmallVec<[CursorImage<'i>; 1]>,
146 pub keyword: CursorKeyword,
148}
149
150impl<'i> Parse<'i> for Cursor<'i> {
151 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
152 let mut images = SmallVec::new();
153 loop {
154 match input.try_parse(CursorImage::parse) {
155 Ok(image) => images.push(image),
156 Err(_) => break,
157 }
158 input.expect_comma()?;
159 }
160
161 Ok(Cursor {
162 images,
163 keyword: CursorKeyword::parse(input)?,
164 })
165 }
166}
167
168impl<'i> ToCss for Cursor<'i> {
169 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
170 where
171 W: std::fmt::Write,
172 {
173 for image in &self.images {
174 image.to_css(dest)?;
175 dest.delim(',', false)?;
176 }
177 self.keyword.to_css(dest)
178 }
179}
180
181#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
183#[cfg_attr(feature = "visitor", derive(Visit))]
184#[cfg_attr(
185 feature = "serde",
186 derive(serde::Serialize, serde::Deserialize),
187 serde(tag = "type", content = "value", rename_all = "kebab-case")
188)]
189#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
190#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
191pub enum ColorOrAuto {
192 Auto,
194 Color(CssColor),
196}
197
198impl Default for ColorOrAuto {
199 fn default() -> ColorOrAuto {
200 ColorOrAuto::Auto
201 }
202}
203
204impl FallbackValues for ColorOrAuto {
205 fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {
206 match self {
207 ColorOrAuto::Color(color) => color
208 .get_fallbacks(targets)
209 .into_iter()
210 .map(|color| ColorOrAuto::Color(color))
211 .collect(),
212 ColorOrAuto::Auto => Vec::new(),
213 }
214 }
215}
216
217impl IsCompatible for ColorOrAuto {
218 fn is_compatible(&self, browsers: Browsers) -> bool {
219 match self {
220 ColorOrAuto::Color(color) => color.is_compatible(browsers),
221 ColorOrAuto::Auto => true,
222 }
223 }
224}
225
226enum_property! {
227 pub enum CaretShape {
229 Auto,
231 Bar,
233 Block,
235 Underscore,
237 }
238}
239
240impl Default for CaretShape {
241 fn default() -> CaretShape {
242 CaretShape::Auto
243 }
244}
245
246shorthand_property! {
247 pub struct Caret {
249 color: CaretColor(ColorOrAuto),
251 shape: CaretShape(CaretShape),
253 }
254}
255
256impl FallbackValues for Caret {
257 fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {
258 self
259 .color
260 .get_fallbacks(targets)
261 .into_iter()
262 .map(|color| Caret {
263 color,
264 shape: self.shape.clone(),
265 })
266 .collect()
267 }
268}
269
270impl IsCompatible for Caret {
271 fn is_compatible(&self, browsers: Browsers) -> bool {
272 self.color.is_compatible(browsers)
273 }
274}
275
276enum_property! {
277 pub enum UserSelect {
279 Auto,
281 Text,
283 None,
285 Contain,
287 All,
289 }
290}
291
292#[derive(Debug, Clone, PartialEq)]
294#[cfg_attr(feature = "visitor", derive(Visit))]
295#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
296#[allow(missing_docs)]
297pub enum Appearance<'i> {
298 None,
299 Auto,
300 Textfield,
301 MenulistButton,
302 Button,
303 Checkbox,
304 Listbox,
305 Menulist,
306 Meter,
307 ProgressBar,
308 PushButton,
309 Radio,
310 Searchfield,
311 SliderHorizontal,
312 SquareButton,
313 Textarea,
314 NonStandard(CowArcStr<'i>),
315}
316
317impl<'i> Appearance<'i> {
318 fn from_str(name: &str) -> Option<Self> {
319 Some(match_ignore_ascii_case! { &name,
320 "none" => Appearance::None,
321 "auto" => Appearance::Auto,
322 "textfield" => Appearance::Textfield,
323 "menulist-button" => Appearance::MenulistButton,
324 "button" => Appearance::Button,
325 "checkbox" => Appearance::Checkbox,
326 "listbox" => Appearance::Listbox,
327 "menulist" => Appearance::Menulist,
328 "meter" => Appearance::Meter,
329 "progress-bar" => Appearance::ProgressBar,
330 "push-button" => Appearance::PushButton,
331 "radio" => Appearance::Radio,
332 "searchfield" => Appearance::Searchfield,
333 "slider-horizontal" => Appearance::SliderHorizontal,
334 "square-button" => Appearance::SquareButton,
335 "textarea" => Appearance::Textarea,
336 _ => return None
337 })
338 }
339
340 fn to_str(&self) -> &str {
341 match self {
342 Appearance::None => "none",
343 Appearance::Auto => "auto",
344 Appearance::Textfield => "textfield",
345 Appearance::MenulistButton => "menulist-button",
346 Appearance::Button => "button",
347 Appearance::Checkbox => "checkbox",
348 Appearance::Listbox => "listbox",
349 Appearance::Menulist => "menulist",
350 Appearance::Meter => "meter",
351 Appearance::ProgressBar => "progress-bar",
352 Appearance::PushButton => "push-button",
353 Appearance::Radio => "radio",
354 Appearance::Searchfield => "searchfield",
355 Appearance::SliderHorizontal => "slider-horizontal",
356 Appearance::SquareButton => "square-button",
357 Appearance::Textarea => "textarea",
358 Appearance::NonStandard(s) => s.as_ref(),
359 }
360 }
361}
362
363impl<'i> Parse<'i> for Appearance<'i> {
364 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
365 let ident = input.expect_ident()?;
366 Ok(Self::from_str(ident.as_ref()).unwrap_or_else(|| Appearance::NonStandard(ident.into())))
367 }
368}
369
370impl<'i> ToCss for Appearance<'i> {
371 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
372 where
373 W: std::fmt::Write,
374 {
375 dest.write_str(self.to_str())
376 }
377}
378
379#[cfg(feature = "serde")]
380#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
381impl<'i> serde::Serialize for Appearance<'i> {
382 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
383 where
384 S: serde::Serializer,
385 {
386 serializer.serialize_str(self.to_str())
387 }
388}
389
390#[cfg(feature = "serde")]
391#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
392impl<'i, 'de: 'i> serde::Deserialize<'de> for Appearance<'i> {
393 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
394 where
395 D: serde::Deserializer<'de>,
396 {
397 let s = CowArcStr::deserialize(deserializer)?;
398 Ok(Self::from_str(s.as_ref()).unwrap_or_else(|| Appearance::NonStandard(s)))
399 }
400}
401
402#[cfg(feature = "jsonschema")]
403#[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
404impl<'a> schemars::JsonSchema for Appearance<'a> {
405 fn is_referenceable() -> bool {
406 true
407 }
408
409 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
410 str::json_schema(gen)
411 }
412
413 fn schema_name() -> String {
414 "Appearance".into()
415 }
416}
417
418bitflags! {
419 #[cfg_attr(feature = "visitor", derive(Visit))]
421 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(from = "SerializedColorScheme", into = "SerializedColorScheme"))]
422 #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
423 #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
424 pub struct ColorScheme: u8 {
425 const Light = 0b01;
427 const Dark = 0b10;
429 const Only = 0b100;
431 }
432}
433
434impl<'i> Parse<'i> for ColorScheme {
435 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
436 let mut res = ColorScheme::empty();
437 let ident = input.expect_ident()?;
438 match_ignore_ascii_case! { &ident,
439 "normal" => return Ok(res),
440 "only" => res |= ColorScheme::Only,
441 "light" => res |= ColorScheme::Light,
442 "dark" => res |= ColorScheme::Dark,
443 _ => {}
444 };
445
446 while let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
447 match_ignore_ascii_case! { &ident,
448 "normal" => return Err(input.new_custom_error(ParserError::InvalidValue)),
449 "only" => {
450 if res.contains(ColorScheme::Only) {
452 return Err(input.new_custom_error(ParserError::InvalidValue));
453 }
454 res |= ColorScheme::Only;
455 return Ok(res);
456 },
457 "light" => res |= ColorScheme::Light,
458 "dark" => res |= ColorScheme::Dark,
459 _ => {}
460 };
461 }
462
463 Ok(res)
464 }
465}
466
467impl ToCss for ColorScheme {
468 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
469 where
470 W: std::fmt::Write,
471 {
472 if self.is_empty() {
473 return dest.write_str("normal");
474 }
475
476 if self.contains(ColorScheme::Light) {
477 dest.write_str("light")?;
478 if self.contains(ColorScheme::Dark) {
479 dest.write_char(' ')?;
480 }
481 }
482
483 if self.contains(ColorScheme::Dark) {
484 dest.write_str("dark")?;
485 }
486
487 if self.contains(ColorScheme::Only) {
488 dest.write_str(" only")?;
489 }
490
491 Ok(())
492 }
493}
494
495#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
496#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
497struct SerializedColorScheme {
498 light: bool,
499 dark: bool,
500 only: bool,
501}
502
503impl From<ColorScheme> for SerializedColorScheme {
504 fn from(color_scheme: ColorScheme) -> Self {
505 Self {
506 light: color_scheme.contains(ColorScheme::Light),
507 dark: color_scheme.contains(ColorScheme::Dark),
508 only: color_scheme.contains(ColorScheme::Only),
509 }
510 }
511}
512
513impl From<SerializedColorScheme> for ColorScheme {
514 fn from(s: SerializedColorScheme) -> ColorScheme {
515 let mut color_scheme = ColorScheme::empty();
516 color_scheme.set(ColorScheme::Light, s.light);
517 color_scheme.set(ColorScheme::Dark, s.dark);
518 color_scheme.set(ColorScheme::Only, s.only);
519 color_scheme
520 }
521}
522
523#[cfg(feature = "jsonschema")]
524#[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
525impl<'a> schemars::JsonSchema for ColorScheme {
526 fn is_referenceable() -> bool {
527 true
528 }
529
530 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
531 SerializedColorScheme::json_schema(gen)
532 }
533
534 fn schema_name() -> String {
535 "ColorScheme".into()
536 }
537}
538
539#[derive(Default)]
540pub(crate) struct ColorSchemeHandler;
541
542impl<'i> PropertyHandler<'i> for ColorSchemeHandler {
543 fn handle_property(
544 &mut self,
545 property: &Property<'i>,
546 dest: &mut DeclarationList<'i>,
547 context: &mut PropertyHandlerContext<'i, '_>,
548 ) -> bool {
549 match property {
550 Property::ColorScheme(color_scheme) => {
551 if !context.targets.is_compatible(Feature::LightDark) {
552 if color_scheme.contains(ColorScheme::Light) {
553 dest.push(define_var("--lightningcss-light", Token::Ident("initial".into())));
554 dest.push(define_var("--lightningcss-dark", Token::WhiteSpace(" ".into())));
555
556 if color_scheme.contains(ColorScheme::Dark) {
557 context.add_dark_rule(define_var("--lightningcss-light", Token::WhiteSpace(" ".into())));
558 context.add_dark_rule(define_var("--lightningcss-dark", Token::Ident("initial".into())));
559 }
560 } else if color_scheme.contains(ColorScheme::Dark) {
561 dest.push(define_var("--lightningcss-light", Token::WhiteSpace(" ".into())));
562 dest.push(define_var("--lightningcss-dark", Token::Ident("initial".into())));
563 }
564 }
565 dest.push(property.clone());
566 true
567 }
568 _ => false,
569 }
570 }
571
572 fn finalize(&mut self, _: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) {}
573}
574
575#[inline]
576fn define_var<'i>(name: &'static str, value: Token<'static>) -> Property<'i> {
577 Property::Custom(CustomProperty {
578 name: CustomPropertyName::Custom(name.into()),
579 value: TokenList(vec![TokenOrValue::Token(value)]),
580 })
581}