styled_yew/
lib.rs

1mod css_rule;
2
3pub use wasm_bindgen;
4pub use web_sys;
5
6use lazy_static::lazy_static;
7use std::{
8	any::TypeId,
9	collections::HashMap,
10	sync::{atomic::AtomicI32, Mutex},
11};
12
13pub static NEXT_ID: AtomicI32 = AtomicI32::new(0);
14lazy_static! {
15	pub static ref STYLE_IDS: Mutex<HashMap<TypeId, i32>> = Mutex::default();
16}
17
18#[macro_export]
19macro_rules! styled {
20	($vis:vis $name:ident : $child:ty { $($rule:ident : $val:expr);*; }) => {
21		$vis struct $name {
22			// TODO: store instance of child component and proxy each method to child, instead of sending props to child
23			// in the view method
24			props: <$child as Component>::Properties,
25			node_ref: ::yew::NodeRef,
26		}
27		impl Component for $name {
28			type Message = ();
29			type Properties = <$child as Component>::Properties;
30
31			fn create(props: Self::Properties, mut link: ComponentLink<Self>) -> Self {
32				use yew::NodeRef;
33
34				Self { props, node_ref: NodeRef::default() }
35			}
36
37			fn update(&mut self, _: Self::Message) -> ShouldRender {
38				false
39			}
40
41			fn change(&mut self, _props: Self::Properties) -> ShouldRender {
42				false
43			}
44
45			fn view(&self) -> Html {
46				use yew::virtual_dom::VChild;
47
48				VChild::<$child>::new(self.props.clone(), self.node_ref.clone(), None).into()
49			}
50
51			fn rendered(&mut self, first_render: bool) {
52				use $crate::{wasm_bindgen::JsCast, web_sys::{self, CssStyleSheet, Element, HtmlStyleElement}, STYLE_IDS};
53				use std::{any::TypeId, sync::atomic::Ordering};
54
55				if first_render {
56					let id = *STYLE_IDS.lock().unwrap().entry(TypeId::of::<Self>()).or_insert_with(|| {
57						let id = $crate::NEXT_ID.fetch_add(1, Ordering::Relaxed);
58						let rules = concat!($( $crate::css_rule!($rule), ":", $val, ";" )*);
59						let window = web_sys::window().expect("no window");
60						let document = window.document().expect("no document");
61						let style = document.create_element("style").unwrap().unchecked_into::<HtmlStyleElement>();
62						document.head().expect("no head").append_child(&style).unwrap();
63						let sheet = style.sheet().expect("no sheet").unchecked_into::<CssStyleSheet>();
64						sheet.insert_rule(&format!(".sc{} {{ {} }}", id, rules)).unwrap();
65						id
66					});
67
68					let el = self.node_ref.cast::<Element>().unwrap();
69					el.class_list().add_1(&format!("sc{}", id)).unwrap();
70				}
71			}
72		}
73	};
74}