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