euv_core/vdom/attribute/
impl.rs1use crate::*;
2
3impl PartialEq for AttributeValue {
10 fn eq(&self, other: &Self) -> bool {
21 match (self, other) {
22 (AttributeValue::Text(a_val), AttributeValue::Text(b_val)) => a_val == b_val,
23 (AttributeValue::Signal(a_sig), AttributeValue::Signal(b_sig)) => {
24 a_sig.get() == b_sig.get()
25 }
26 (AttributeValue::Signal(a_sig), AttributeValue::Text(b_val)) => a_sig.get() == *b_val,
27 (AttributeValue::Text(a_val), AttributeValue::Signal(b_sig)) => *a_val == b_sig.get(),
28 (AttributeValue::Event(_), AttributeValue::Event(_)) => true,
29 (AttributeValue::Css(a_css), AttributeValue::Css(b_css)) => {
30 a_css.get_name() == b_css.get_name()
31 }
32 (AttributeValue::Dynamic(a_dyn), AttributeValue::Dynamic(b_dyn)) => a_dyn == b_dyn,
33 _ => false,
34 }
35 }
36}
37
38impl PartialEq for AttributeEntry {
43 fn eq(&self, other: &Self) -> bool {
54 self.get_name() == other.get_name() && self.get_value() == other.get_value()
55 }
56}
57
58impl PartialEq for CssClass {
63 fn eq(&self, other: &Self) -> bool {
74 self.get_name() == other.get_name()
75 }
76}
77
78impl Style {
80 pub fn property<N, V>(mut self, name: N, value: V) -> Self
94 where
95 N: AsRef<str>,
96 V: AsRef<str>,
97 {
98 self.get_mut_properties().push(StyleProperty::new(
99 name.as_ref().replace('_', "-"),
100 value.as_ref().to_string(),
101 ));
102 self
103 }
104
105 pub fn to_css_string(&self) -> String {
111 self.get_properties()
112 .iter()
113 .map(|style: &StyleProperty| format!("{}: {};", style.get_name(), style.get_value()))
114 .collect::<Vec<String>>()
115 .join(" ")
116 }
117
118 pub fn create_style_string(props: &[(&str, &str)]) -> String {
133 let mut result: String = String::new();
134 for (key, value) in props {
135 if !result.is_empty() {
136 result.push(' ');
137 }
138 result.push_str(&key.replace('_', "-"));
139 result.push_str(": ");
140 result.push_str(value);
141 result.push(';');
142 }
143 result
144 }
145}
146
147impl Default for Style {
149 fn default() -> Self {
155 Self::new(Vec::new())
156 }
157}
158
159impl CssClass {
161 pub fn new(name: String, style: String) -> Self {
174 let mut css_class: CssClass = CssClass::default();
175 css_class.set_name(name);
176 css_class.set_style(style);
177 css_class.inject_style();
178 css_class
179 }
180
181 pub fn inject_style(&self) {
192 #[cfg(target_arch = "wasm32")]
193 {
194 let style_id: &str = "euv-css-injected";
195 let document: Document = window()
196 .expect("no global window exists")
197 .document()
198 .expect("no document exists");
199 let style_element: HtmlStyleElement = match document.get_element_by_id(style_id) {
200 Some(el) => el.dyn_into::<HtmlStyleElement>().unwrap(),
201 None => {
202 let el: HtmlStyleElement = document
203 .create_element("style")
204 .unwrap()
205 .dyn_into::<HtmlStyleElement>()
206 .unwrap();
207 el.set_id(style_id);
208 let keyframes: &str = "@keyframes euv-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @keyframes euv-fade-in { from { opacity: 0; } to { opacity: 1; } } @keyframes euv-scale-in { from { transform: scale(0.9); opacity: 0; } to { transform: scale(1); opacity: 1; } } @keyframes euv-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.2); } } @keyframes euv-slide-up { from { transform: translateY(100%); } to { transform: translateY(0); } } @keyframes euv-slide-left { from { transform: translateX(-100%); } to { transform: translateX(0); } } @keyframes euv-fade-in-up { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }";
209 let global: &str = "html, body, #app { height: 100%; margin: 0; padding: 0; overflow: hidden; } * { -webkit-tap-highlight-color: transparent; }";
210 let media_queries: &str = "@media (max-width: 767px) { .c_app_nav { display: none; } .c_app_main { padding: 20px 16px; max-width: 100%; } .c_page_title { font-size: 22px; } .c_page_subtitle { font-size: 14px; } .c_card { padding: 16px; margin: 12px 0; border-radius: 10px; } .c_card_title { font-size: 16px; } .c_form_grid { grid-template-columns: 1fr; } .c_browser_api_row { grid-template-columns: 1fr; } .c_modal_content { max-width: 100%; width: calc(100% - 32px); border-radius: 16px 16px 0 0; position: fixed; bottom: 0; left: 16px; height: 80vh; animation: euv-slide-up 0.25s ease; } .c_modal_overlay { align-items: flex-end; } .c_event_stats { gap: 12px; flex-wrap: wrap; } .c_event_section_row { gap: 12px; flex-wrap: wrap; } .c_event_section_col { min-width: 100%; } .c_counter_value { font-size: 20px; } .c_timer_value { font-size: 36px; } .c_not_found_code { font-size: 56px; } .c_not_found_container { padding: 40px 20px; } .c_list_input_row { flex-direction: column; } .c_vconsole_button { bottom: 16px; right: 16px; width: 44px; height: 44px; border-radius: 12px; } .c_tab_bar { flex-wrap: wrap; } .c_primary_button { padding: 10px 18px; font-size: 14px; } .c_badge { padding: 4px 10px; font-size: 11px; } .c_badge_outline { padding: 4px 10px; font-size: 11px; } .c_browser_info_grid { grid-template-columns: 1fr; } .c_anim_spin { font-size: 36px; } .c_anim_spin_stopped { font-size: 36px; } .c_anim_pulse { font-size: 36px; } .c_anim_pulse_stopped { font-size: 36px; } }";
211 el.set_inner_text(&format!("{} {} {}", global, keyframes, media_queries));
212 document.head().unwrap().append_child(&el).unwrap();
213 el
214 }
215 };
216 let existing_css: String = style_element.inner_text();
217 let class_rule: String = format!(".{} {{ {} }}", self.get_name(), self.get_style());
218 if !existing_css.contains(&class_rule) {
219 let new_css: String = if existing_css.is_empty() {
220 class_rule
221 } else {
222 format!("{}\n{}", existing_css, class_rule)
223 };
224 style_element.set_inner_text(&new_css);
225 }
226 }
227 }
228}
229
230impl std::fmt::Display for CssClass {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 write!(f, "{}", self.get_name())
246 }
247}