encre_css/utils/
value_matchers.rs

1//! Define some utility functions used to detect the [CSS type](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Types) of a CSS value.
2use super::shadow::ShadowList;
3
4const LENGTH_UNITS: [&str; 16] = [
5    "cm", "mm", "Q", "in", "pc", "pt", "px", "em", "ex", "ch", "rem", "lh", "vw", "vh", "vmin",
6    "vmax",
7];
8const LINE_WIDTHS: [&str; 3] = ["thin", "medium", "thick"];
9const LINE_STYLES: [&str; 10] = [
10    "solid", "dashed", "dotted", "double", "groove", "ridge", "inset", "outset", "hidden", "none",
11];
12const ANGLES: [&str; 4] = ["deg", "grad", "rad", "turn"];
13const GRADIENT_TYPES: [&str; 5] = [
14    "linear-gradient",
15    "radial-gradient",
16    "repeating-linear-gradient",
17    "repeating-radial-gradient",
18    "conic-gradient",
19];
20const VALID_POSITIONS: [&str; 5] = ["center", "top", "right", "bottom", "left"];
21const ABSOLUTE_SIZES: [&str; 8] = [
22    "xx-small",
23    "x-small",
24    "small",
25    "medium",
26    "large",
27    "x-large",
28    "xx-large",
29    "xxx-large",
30];
31const RELATIVE_SIZES: [&str; 2] = ["larger", "smaller"];
32const NAMED_COLORS: [&str; 150] = [
33    "transparent",
34    "currentColor",
35    "antiquewhite",
36    "aliceblue",
37    "aqua",
38    "aquamarine",
39    "azure",
40    "beige",
41    "bisque",
42    "black",
43    "blanchedalmond",
44    "blue",
45    "blueviolet",
46    "brown",
47    "burlywood",
48    "cadetblue",
49    "chartreuse",
50    "chocolate",
51    "coral",
52    "cornflowerblue",
53    "cornsilk",
54    "crimson",
55    "cyan",
56    "darkblue",
57    "darkcyan",
58    "darkgoldenrod",
59    "darkgray",
60    "darkgreen",
61    "darkgrey",
62    "darkkhaki",
63    "darkmagenta",
64    "darkolivegreen",
65    "darkorange",
66    "darkorchid",
67    "darkred",
68    "darksalmon",
69    "darkseagreen",
70    "darkslateblue",
71    "darkslategray",
72    "darkslategrey",
73    "darkturquoise",
74    "darkviolet",
75    "deeppink",
76    "deepskyblue",
77    "dimgray",
78    "dimgrey",
79    "dodgerblue",
80    "firebrick",
81    "floralwhite",
82    "forestgreen",
83    "fuchsia",
84    "gainsboro",
85    "ghostwhite",
86    "gold",
87    "goldenrod",
88    "gray",
89    "green",
90    "greenyellow",
91    "grey",
92    "honeydew",
93    "hotpink",
94    "indianred",
95    "indigo",
96    "ivory",
97    "khaki",
98    "lavender",
99    "lavenderblush",
100    "lawngreen",
101    "lemonchiffon",
102    "lightblue",
103    "lightcoral",
104    "lightcyan",
105    "lightgoldenrodyellow",
106    "lightgray",
107    "lightgreen",
108    "lightgrey",
109    "lightpink",
110    "lightsalmon",
111    "lightseagreen",
112    "lightskyblue",
113    "lightslategray",
114    "lightslategrey",
115    "lightsteelblue",
116    "lightyellow",
117    "lime",
118    "limegreen",
119    "linen",
120    "magenta",
121    "maroon",
122    "mediumaquamarine",
123    "mediumblue",
124    "mediumorchid",
125    "mediumpurple",
126    "mediumseagreen",
127    "mediumslateblue",
128    "mediumspringgreen",
129    "mediumturquoise",
130    "mediumvioletred",
131    "midnightblue",
132    "mintcream",
133    "mistyrose",
134    "moccasin",
135    "navajowhite",
136    "navy",
137    "oldlace",
138    "olive",
139    "olivedrab",
140    "orange",
141    "orangered",
142    "orchid",
143    "palegoldenrod",
144    "palegreen",
145    "paleturquoise",
146    "palevioletred",
147    "papayawhip",
148    "peachpuff",
149    "peru",
150    "pink",
151    "plum",
152    "powderblue",
153    "purple",
154    "rebeccapurple",
155    "red",
156    "rosybrown",
157    "royalblue",
158    "saddlebrown",
159    "salmon",
160    "sandybrown",
161    "seagreen",
162    "seashell",
163    "sienna",
164    "silver",
165    "skyblue",
166    "slateblue",
167    "slategray",
168    "slategrey",
169    "snow",
170    "springgreen",
171    "steelblue",
172    "tan",
173    "teal",
174    "thistle",
175    "tomato",
176    "turquoise",
177    "violet",
178    "wheat",
179    "white",
180    "whitesmoke",
181    "yellow",
182    "yellowgreen",
183];
184
185fn is_matching_base(value: &str) -> bool {
186    is_matching_var(value)
187        || [
188            "inherit",
189            "initial",
190            "revert",
191            "revert-layer",
192            "unset",
193            "fill",
194            "max-content",
195            "min-content",
196            "fit-content",
197        ]
198        .contains(&value)
199}
200
201/// Match all CSS types.
202pub fn is_matching_all(_value: &str) -> bool {
203    true
204}
205
206/// Returns whether the CSS value is an [`url()`](https://developer.mozilla.org/en-US/docs/Web/CSS/url).
207///
208/// # Example
209///
210/// ```
211/// use encre_css::utils::value_matchers::is_matching_url;
212/// assert!(is_matching_url("url('/hello/world.png')"));
213/// ```
214pub fn is_matching_url(value: &str) -> bool {
215    value.starts_with("url(")
216}
217
218/// Returns whether the CSS value is a [`var()`](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties).
219///
220/// # Example
221///
222/// ```
223/// use encre_css::utils::value_matchers::is_matching_var;
224/// assert!(is_matching_var("var(--bg-blue)"));
225/// ```
226pub fn is_matching_var(value: &str) -> bool {
227    value.starts_with("var(")
228}
229
230/// Returns whether the CSS value is a [`shadow`](https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow#values).
231///
232/// # Example
233///
234/// ```
235/// use encre_css::utils::value_matchers::is_matching_shadow;
236/// assert!(is_matching_shadow("1px 2rem 10px 10px rgb(12,12,12)"));
237/// ```
238pub fn is_matching_shadow(value: &str) -> bool {
239    ShadowList::parse(value).is_some()
240}
241
242/// Returns whether the CSS value is an [`absolute size`](https://developer.mozilla.org/en-US/docs/Web/CSS/font-size#values).
243///
244/// # Example
245///
246/// ```
247/// use encre_css::utils::value_matchers::is_matching_absolute_size;
248/// assert!(is_matching_absolute_size("xx-small"));
249/// ```
250pub fn is_matching_absolute_size(value: &str) -> bool {
251    ABSOLUTE_SIZES.contains(&value)
252}
253
254/// Returns whether the CSS value is a [`relative size`](https://developer.mozilla.org/en-US/docs/Web/CSS/font-size#values).
255///
256/// # Example
257///
258/// ```
259/// use encre_css::utils::value_matchers::is_matching_relative_size;
260/// assert!(is_matching_relative_size("larger"));
261/// ```
262pub fn is_matching_relative_size(value: &str) -> bool {
263    RELATIVE_SIZES.contains(&value)
264}
265
266/// Returns whether the CSS value is a [`line width`](https://developer.mozilla.org/en-US/docs/Web/CSS/border-width#values).
267///
268/// # Example
269///
270/// ```
271/// use encre_css::utils::value_matchers::is_matching_line_width;
272/// assert!(is_matching_line_width("thin"));
273/// ```
274pub fn is_matching_line_width(value: &str) -> bool {
275    LINE_WIDTHS.contains(&value)
276}
277
278/// Returns whether the CSS value is a [`line style`](https://developer.mozilla.org/en-US/docs/Web/CSS/border-style#values).
279///
280/// # Example
281///
282/// ```
283/// use encre_css::utils::value_matchers::is_matching_line_style;
284/// assert!(is_matching_line_style("solid"));
285/// ```
286pub fn is_matching_line_style(value: &str) -> bool {
287    LINE_STYLES.contains(&value)
288}
289
290/// Returns whether the CSS value is a computational CSS function like:
291///
292/// - [`min()`](https://developer.mozilla.org/en-US/docs/Web/CSS/min)
293/// - [`max()`](https://developer.mozilla.org/en-US/docs/Web/CSS/max)
294/// - [`clamp()`](https://developer.mozilla.org/en-US/docs/Web/CSS/clamp)
295/// - [`calc()`](https://developer.mozilla.org/en-US/docs/Web/CSS/calc)
296///
297/// # Example
298///
299/// ```
300/// use encre_css::utils::value_matchers::is_matching_computational_css_function;
301/// assert!(is_matching_computational_css_function("min(12px,10%)"));
302/// ```
303pub fn is_matching_computational_css_function(value: &str) -> bool {
304    value.starts_with("min(")
305        || value.starts_with("max(")
306        || value.starts_with("clamp(")
307        || value.starts_with("calc(")
308}
309
310/// Returns whether the CSS value has the [`<color>`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value) type.
311///
312/// # Example
313///
314/// ```
315/// use encre_css::utils::value_matchers::is_matching_color;
316/// assert!(is_matching_color("blue"));
317/// assert!(is_matching_color("#333"));
318/// ```
319pub fn is_matching_color(value: &str) -> bool {
320    (value.starts_with('#') && (value.len() == 4 || value.len() == 7))
321        || ["rgb(", "rgba(", "hsl(", "hsla(", "hwb(", "lch(", "lab("]
322            .iter()
323            .any(|e| value.starts_with(e))
324        || NAMED_COLORS.iter().any(|c| &value == c)
325        || is_matching_base(value)
326}
327
328/// Returns whether the CSS value has the [`<length>`](https://developer.mozilla.org/en-US/docs/Web/CSS/length) type.
329///
330/// # Example
331///
332/// ```
333/// use encre_css::utils::value_matchers::is_matching_length;
334/// assert!(is_matching_length("300px"));
335/// ```
336pub fn is_matching_length(value: &str) -> bool {
337    value.split(' ').all(|v| {
338        v == "0"
339            || LENGTH_UNITS.iter().any(|u| v.ends_with(u))
340            || is_matching_computational_css_function(value)
341    }) || is_matching_base(value)
342}
343
344/// Returns whether the CSS value has the [`<number>`](https://developer.mozilla.org/en-US/docs/Web/CSS/number) type.
345///
346/// # Example
347///
348/// ```
349/// use encre_css::utils::value_matchers::is_matching_number;
350/// assert!(is_matching_number("42.12"));
351/// ```
352pub fn is_matching_number(value: &str) -> bool {
353    value.parse::<f32>().is_ok()
354        || is_matching_computational_css_function(value)
355        || is_matching_base(value)
356}
357
358/// Returns whether the CSS value has the [`<percentage>`](https://developer.mozilla.org/en-US/docs/Web/CSS/percentage) type.
359///
360/// # Example
361///
362/// ```
363/// use encre_css::utils::value_matchers::is_matching_percentage;
364/// assert!(is_matching_percentage("10%"));
365/// ```
366pub fn is_matching_percentage(value: &str) -> bool {
367    value.ends_with('%') || is_matching_computational_css_function(value) || is_matching_base(value)
368}
369
370/// Returns whether the CSS value has the [`<time>`](https://developer.mozilla.org/en-US/docs/Web/CSS/time) type.
371///
372/// # Example
373///
374/// ```
375/// use encre_css::utils::value_matchers::is_matching_time;
376/// assert!(is_matching_time("0.5s"));
377/// assert!(is_matching_time("10ms"));
378/// ```
379pub fn is_matching_time(value: &str) -> bool {
380    value.ends_with('s')
381        || value.ends_with("ms")
382        || is_matching_computational_css_function(value)
383        || is_matching_base(value)
384}
385
386/// Returns whether the CSS value has the [`<gradient>`](https://developer.mozilla.org/en-US/docs/Web/CSS/gradient) type.
387///
388/// # Example
389///
390/// ```
391/// use encre_css::utils::value_matchers::is_matching_gradient;
392/// assert!(is_matching_gradient("linear-gradient(45deg, blue, red);"));
393/// ```
394pub fn is_matching_gradient(value: &str) -> bool {
395    GRADIENT_TYPES.iter().any(|t| value.starts_with(t)) || is_matching_base(value)
396}
397
398/// Returns whether the CSS value has the [`<position>`](https://developer.mozilla.org/en-US/docs/Web/CSS/position_value) type.
399///
400/// # Example
401///
402/// ```
403/// use encre_css::utils::value_matchers::is_matching_position;
404/// assert!(is_matching_position("right"));
405/// assert!(is_matching_position("12px"));
406/// assert!(is_matching_position("42%"));
407/// ```
408pub fn is_matching_position(value: &str) -> bool {
409    value
410        .split(' ')
411        .all(|v| VALID_POSITIONS.contains(&v) || is_matching_length(v) || is_matching_percentage(v))
412        || is_matching_base(value)
413}
414
415/// Returns whether the CSS value has the [`<angle>`](https://developer.mozilla.org/en-US/docs/Web/CSS/angle) type.
416///
417/// # Example
418///
419/// ```
420/// use encre_css::utils::value_matchers::is_matching_angle;
421/// assert!(is_matching_angle("0.2turn"));
422/// ```
423pub fn is_matching_angle(value: &str) -> bool {
424    ANGLES.iter().any(|a| value.ends_with(a))
425        || is_matching_computational_css_function(value)
426        || is_matching_base(value)
427}
428
429/// Returns whether the CSS value has the [`<image>`](https://developer.mozilla.org/en-US/docs/Web/CSS/image) type.
430///
431/// # Example
432///
433/// ```
434/// use encre_css::utils::value_matchers::is_matching_image;
435/// assert!(is_matching_image("linear-gradient(to_right,red,orange,yellow,green,blue,indigo,violet)"));
436/// ```
437pub fn is_matching_image(value: &str) -> bool {
438    is_matching_url(value)
439        || is_matching_gradient(value)
440        || ["element(", "image(", "cross-fade(", "image-set("]
441            .iter()
442            .any(|e| value.starts_with(e))
443        || is_matching_base(value)
444}
445
446#[cfg(test)]
447mod tests {
448    use super::*;
449
450    #[test]
451    fn is_matching_color_test() {
452        assert!(is_matching_color("blue"));
453        assert!(is_matching_color("#333"));
454        assert!(is_matching_color("#121212"));
455        assert!(is_matching_color("rgb(12.12,12,12)"));
456        assert!(is_matching_color("rgb(12 12 12)"));
457        assert!(is_matching_color("rgb(12 12 12/0.1)"));
458        assert!(is_matching_color("rgb(12 12 12 / 0.1)"));
459        assert!(is_matching_color("rgb(var(--blue),12,12)"));
460        assert!(is_matching_color("rgb(12 12 12 / var(--opacity))"));
461        assert!(is_matching_color("rgba(12,12,12,0.12)"));
462        assert!(is_matching_color("hsl(360,100%,50%)"));
463        assert!(is_matching_color("hsl(3.14rad,100%,50%)"));
464        assert!(is_matching_color("hsl(3.14rad 100% 50%/0.42)"));
465        assert!(is_matching_color("hsl(var(--hue) 12% 42%/var(--opacity))"));
466        assert!(is_matching_color("hsla(360,100%,50%,0.12)"));
467    }
468
469    #[test]
470    fn is_matching_length_test() {
471        assert!(is_matching_length("300px"));
472        assert!(!is_matching_length("50%"));
473        assert!(is_matching_length("30vw"));
474        assert!(is_matching_length("min(10%,10px)"));
475        assert!(is_matching_length("0"));
476    }
477
478    #[test]
479    fn is_matching_shadow_with_functions_test() {
480        assert!(is_matching_shadow("10px 10px min(1px,2px) 10px rgb(1,1,1)"));
481        assert!(is_matching_shadow("inset 0 -3em 3em rgba(0,0,0,0.1),0 0 0 2px rgb(255,255,255),0.3em 0.3em 1em rgba(0,0,0,0.3)"));
482        assert!(is_matching_shadow(
483            "var(--a, 0 0 1px rgb(0, 0, 0)), 0 0 1px rgb(0, 0, 0)"
484        ));
485    }
486}