css_in_rust_next/style/
mod.rs1pub mod ast;
4
5use super::parser::Parser;
6use ast::Scope;
7#[cfg(target_arch = "wasm32")]
8use ast::ToCss;
9#[cfg(not(target_arch = "wasm32"))]
10use rand::{distributions::Alphanumeric, rngs::SmallRng, Rng, SeedableRng};
11use std::collections::HashMap;
12use std::sync::{Arc, Mutex};
13#[cfg(target_arch = "wasm32")]
14use web_sys::Element;
15
16#[cfg(target_arch = "wasm32")]
17use wasm_bindgen::prelude::*;
18
19lazy_static! {
20 static ref STYLE_REGISTRY: Arc<Mutex<StyleRegistry>> = Arc::new(Mutex::default());
21}
22
23#[cfg(target_arch = "wasm32")]
24#[wasm_bindgen]
25extern "C" {
26 #[wasm_bindgen(js_namespace = Math)]
27 fn random() -> f64;
28}
29
30#[derive(Clone, Debug, Default)]
33struct StyleRegistry {
34 styles: HashMap<String, Style>,
35}
36
37unsafe impl Send for StyleRegistry {}
38unsafe impl Sync for StyleRegistry {}
39
40#[cfg(all(target_arch = "wasm32"))]
41#[derive(Debug, Clone)]
42pub struct Style {
43 class_name: String,
45 ast: Option<Vec<Scope>>,
47 node: Option<Element>,
49}
50
51#[cfg(not(target_arch = "wasm32"))]
52#[allow(dead_code)]
53#[derive(Debug, Clone)]
54pub struct Style {
55 class_name: String,
57 ast: Option<Vec<Scope>>,
59}
60
61#[cfg(target_arch = "wasm32")]
62impl Style {
63 pub fn create<I1: Into<String>, I2: Into<String>>(
68 class_name: I1,
69 css: I2,
70 ) -> Result<Style, String> {
71 let (class_name, css) = (class_name.into(), css.into());
72 let ast = Parser::parse(css)?;
73 let mut new_style = Self {
74 class_name: format!("{}-{}", class_name, random().to_bits()),
75 ast: Some(ast),
76 node: None,
77 };
78 new_style = new_style.mount();
79 let style_registry_mutex = Arc::clone(&STYLE_REGISTRY);
80 let mut style_registry = match style_registry_mutex.lock() {
81 Ok(guard) => guard,
82 Err(poisoned) => poisoned.into_inner(),
83 };
84 (*style_registry)
85 .styles
86 .insert(new_style.class_name.clone(), new_style.clone());
87 Ok(new_style)
88 }
89
90 pub fn get_class_name(self) -> String {
91 self.class_name
92 }
93
94 fn mount(&mut self) -> Self {
96 let mut style = self.unmount();
97 style.node = self.generate_element().ok();
98 if let Some(node) = style.node {
99 let window = web_sys::window().expect("no global `window` exists");
100 let document = window.document().expect("should have a document on window");
101 let head = document.head().expect("should have a head in document");
102 head.append_child(&node).ok();
103 }
104 self.clone()
105 }
106
107 fn unmount(&mut self) -> Self {
109 if let Some(node) = &self.node {
110 let window = web_sys::window().expect("no global `window` exists");
111 let document = window.document().expect("should have a document on window");
112 let head = document.head().expect("should have a head in document");
113 head.remove_child(node).ok();
114 }
115 self.clone()
116 }
117
118 fn generate_css(&self) -> String {
120 match &self.ast {
121 Some(ast) => ast
122 .clone()
123 .into_iter()
124 .map(|scope| scope.to_css(self.class_name.clone()))
125 .fold(String::new(), |acc, css_part| {
126 format!("{}\n{}", acc, css_part)
127 }),
128 None => String::new(),
129 }
130 }
131
132 fn generate_element(&self) -> Result<Element, JsValue> {
135 let window = web_sys::window().expect("no global `window` exists");
136 let document = window.document().expect("should have a document on window");
137 let style_element = document.create_element("style").unwrap();
138 style_element
139 .set_attribute("data-style", self.class_name.as_str())
140 .ok();
141 style_element.set_text_content(Some(self.generate_css().as_str()));
142 Ok(style_element)
143 }
144}
145
146#[cfg(not(target_arch = "wasm32"))]
148impl Style {
149 pub fn create<I1: Into<String>, I2: Into<String>>(
154 class_name: I1,
155 css: I2,
156 ) -> Result<Style, String> {
157 let (class_name, css) = (class_name.into(), css.into());
158 let small_rng = SmallRng::from_entropy();
159 let new_style = Self {
160 class_name: format!(
161 "{}-{}",
162 class_name,
163 small_rng
164 .sample_iter(Alphanumeric)
165 .take(30)
166 .map(|number| number.to_string())
167 .collect::<String>()
168 ),
169 ast: Parser::parse(css).ok(),
171 };
172 let style_registry_mutex = Arc::clone(&STYLE_REGISTRY);
173 let mut style_registry = match style_registry_mutex.lock() {
174 Ok(guard) => guard,
175 Err(poisoned) => poisoned.into_inner(),
176 };
177 style_registry
178 .styles
179 .insert(new_style.class_name.clone(), new_style.clone());
180 Ok(new_style)
181 }
182
183 pub fn get_class_name(self) -> String {
184 self.class_name
185 }
186}
187
188impl ToString for Style {
189 fn to_string(&self) -> String {
191 self.class_name.clone()
192 }
193}