use crate::*;
unsafe impl Sync for InjectedClassesCell {}
impl AttributeValue {
pub fn create_reactive_style<F>(compute: F) -> Self
where
F: Fn() -> String + 'static,
{
let attr_signal: Signal<String> = Signal::create(compute());
subscribe_attr_signal(attr_signal, compute);
Self::Signal(attr_signal)
}
pub fn create_reactive_signal<F>(compute: F) -> Self
where
F: Fn() -> String + 'static,
{
let attr_signal: Signal<String> =
Signal::create(IntoReactiveString::into_reactive_string(compute()));
subscribe_attr_signal(attr_signal, move || {
IntoReactiveString::into_reactive_string(compute())
});
Self::Signal(attr_signal)
}
pub fn merge_class(values: &[Self]) -> Self {
let has_signal: bool = values
.iter()
.any(|value: &Self| matches!(value, Self::Signal(_)));
if has_signal {
let owned_values: Vec<Self> = values.to_vec();
let compute = move || {
let mut result: String = String::new();
for value in &owned_values {
let class_segment: String = match value {
Self::Css(css) => {
css.inject_style();
css.get_name().to_string()
}
Self::Text(text_value) => text_value.clone(),
Self::Signal(signal) => signal.get(),
_ => String::new(),
};
if !class_segment.is_empty() {
if !result.is_empty() {
result.push(' ');
}
result.push_str(&class_segment);
}
}
result
};
let attr_signal: Signal<String> = Signal::create(compute());
subscribe_attr_signal(attr_signal, compute);
Self::Signal(attr_signal)
} else {
let mut result: String = String::new();
for value in values {
let class_segment: String = match value {
Self::Css(css) => {
css.inject_style();
css.get_name().to_string()
}
Self::Text(text_value) => text_value.clone(),
_ => String::new(),
};
if !class_segment.is_empty() {
if !result.is_empty() {
result.push(' ');
}
result.push_str(&class_segment);
}
}
Self::Text(result)
}
}
pub fn merge_style(values: &[Self]) -> Self {
let has_signal: bool = values
.iter()
.any(|value: &Self| matches!(value, Self::Signal(_)));
if has_signal {
let owned_values: Vec<Self> = values.to_vec();
let compute = move || {
let mut result: String = String::new();
for value in &owned_values {
let style_segment: String = match value {
Self::Text(text_value) => text_value.clone(),
Self::Signal(signal) => signal.get(),
_ => String::new(),
};
if !style_segment.is_empty() {
if !result.is_empty() {
result.push(' ');
}
result.push_str(&style_segment);
}
}
result
};
let attr_signal: Signal<String> = Signal::create(compute());
subscribe_attr_signal(attr_signal, compute);
Self::Signal(attr_signal)
} else {
let mut result: String = String::new();
for value in values {
let style_segment: String = match value {
Self::Text(text_value) => text_value.clone(),
_ => String::new(),
};
if !style_segment.is_empty() {
if !result.is_empty() {
result.push(' ');
}
result.push_str(&style_segment);
}
}
Self::Text(result)
}
}
}
impl PartialEq for AttributeValue {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Text(old_value), Self::Text(new_value)) => old_value == new_value,
(Self::Signal(old_signal), Self::Signal(new_signal)) => {
if old_signal.get_inner_addr() == new_signal.get_inner_addr() {
return false;
}
old_signal.get() == new_signal.get()
}
(Self::Signal(old_signal), Self::Text(new_value)) => old_signal.get() == *new_value,
(Self::Text(old_value), Self::Signal(new_signal)) => *old_value == new_signal.get(),
(Self::Event(_), Self::Event(_)) => true,
(Self::Css(old_class), Self::Css(new_class)) => {
old_class.get_name() == new_class.get_name()
}
(Self::Dynamic(old_dynamic), Self::Dynamic(new_dynamic)) => old_dynamic == new_dynamic,
_ => false,
}
}
}
impl PartialEq for AttributeEntry {
fn eq(&self, other: &Self) -> bool {
self.get_name() == other.get_name() && self.get_value() == other.get_value()
}
}
impl PartialEq for Css {
fn eq(&self, other: &Self) -> bool {
self.get_name() == other.get_name()
}
}
impl Style {
pub fn property<N, V>(mut self, name: N, value: V) -> Self
where
N: AsRef<str>,
V: AsRef<str>,
{
self.get_mut_properties().push(StyleProperty::new(
name.as_ref().replace('_', "-"),
value.as_ref().to_string(),
));
self
}
pub fn to_css_string(&self) -> String {
self.get_properties()
.iter()
.map(|style: &StyleProperty| format!("{}: {};", style.get_name(), style.get_value()))
.collect::<Vec<String>>()
.join(" ")
}
pub fn create_style_string(props: &[(&str, &str)]) -> String {
let mut result: String = String::new();
for (key, value) in props {
if !result.is_empty() {
result.push(' ');
}
result.push_str(&key.replace('_', "-"));
result.push_str(": ");
result.push_str(value);
result.push(';');
}
result
}
}
impl Default for Style {
fn default() -> Self {
Self::new(Vec::new())
}
}
impl Css {
pub fn new(name: String, style: String) -> Self {
let mut css: Self = Self::default();
css.set_name(name);
css.set_style(style);
css.inject_style();
css
}
pub fn new_with_rules(
name: String,
style: String,
pseudo_rules: Vec<PseudoRule>,
media_rules: Vec<MediaRule>,
) -> Self {
let mut css: Self = Self::default();
css.set_name(name);
css.set_style(style);
css.set_pseudo_rules(pseudo_rules);
css.set_media_rules(media_rules);
css.inject_style();
css
}
pub fn parse_pseudo_rules(input: &str) -> Vec<PseudoRule> {
let mut rules: Vec<PseudoRule> = Vec::new();
let mut remaining: &str = input;
while !remaining.is_empty() {
let selector_end: Option<usize> = remaining.find(" { ");
let Some(selector_end_index) = selector_end else {
break;
};
let selector: &str = &remaining[..selector_end_index];
let after_selector: &str = remaining[selector_end_index..]
.strip_prefix(" { ")
.unwrap_or("");
let style_end: Option<usize> = after_selector.find('}');
let Some(style_end_index) = style_end else {
break;
};
let style: &str = &after_selector[..style_end_index];
if !selector.is_empty() && !style.is_empty() {
rules.push(PseudoRule::new(selector.to_string(), style.to_string()));
}
remaining = after_selector[style_end_index..]
.strip_prefix('}')
.unwrap_or("");
}
rules
}
pub fn parse_media_rules(input: &str) -> Vec<MediaRule> {
let mut rules: Vec<MediaRule> = Vec::new();
let mut remaining: &str = input;
while !remaining.is_empty() {
if !remaining.starts_with("@media ") {
break;
}
let after_prefix: &str = remaining.strip_prefix("@media ").unwrap_or("");
let query_end: Option<usize> = after_prefix.find(" { ");
let Some(query_end_index) = query_end else {
break;
};
let query: &str = &after_prefix[..query_end_index];
let after_query: &str = after_prefix[query_end_index..]
.strip_prefix(" { ")
.unwrap_or("");
let style_end: Option<usize> = after_query.find('}');
let Some(style_end_index) = style_end else {
break;
};
let style: &str = &after_query[..style_end_index];
if !query.is_empty() && !style.is_empty() {
rules.push(MediaRule::new(query.to_string(), style.to_string()));
}
remaining = after_query[style_end_index..]
.strip_prefix('}')
.unwrap_or("");
}
rules
}
pub fn inject_style(&self) {
if !Self::mark_injected(self.get_name().clone()) {
return;
}
let class_rule: String = format!(".{} {{ {} }}", self.get_name(), self.get_style());
let mut css_text: String = class_rule;
for pseudo_rule in self.get_pseudo_rules() {
if !pseudo_rule.get_style().is_empty() {
let pseudo_rule_str: String = format!(
".{}{} {{ {} }}",
self.get_name(),
pseudo_rule.get_selector(),
pseudo_rule.get_style()
);
css_text = format!("{}\n{}", css_text, pseudo_rule_str);
}
}
for media_rule in self.get_media_rules() {
if !media_rule.get_query().is_empty() {
let media_rule_str: String = format!(
"@media {} {{ .{} {{ {} }} }}",
media_rule.get_query(),
self.get_name(),
media_rule.get_style()
);
css_text = format!("{}\n{}", css_text, media_rule_str);
}
}
Self::append_css(&css_text);
}
fn mark_injected(class_name: String) -> bool {
get_injected_classes_mut().insert(class_name)
}
fn append_css(css_text: &str) {
let style_id: &str = "euv-css-injected";
let document: Document = window()
.expect("no global window exists")
.document()
.expect("no document exists");
let style_element: HtmlStyleElement = match document.get_element_by_id(style_id) {
Some(existing_element) => existing_element.dyn_into::<HtmlStyleElement>().unwrap(),
None => {
let style_element_from_id: HtmlStyleElement = document
.create_element("style")
.unwrap()
.dyn_into::<HtmlStyleElement>()
.unwrap();
style_element_from_id.set_id(style_id);
document
.head()
.unwrap()
.append_child(&style_element_from_id)
.unwrap();
style_element_from_id
}
};
if !css_text.is_empty() {
let text_node: Text = document.create_text_node(css_text);
style_element.append_child(&text_node).unwrap();
}
}
pub fn inject_css(css_text: &str) {
Self::append_css(css_text);
}
}
impl std::fmt::Display for Css {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.get_name())
}
}