use crate::*;
unsafe impl Sync for InjectedClassesCell {}
impl AttributeValue {
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 || {
owned_values
.iter()
.filter_map(|value: &Self| match value {
Self::Css(css) => {
css.inject_style();
Some(css.get_name().to_string())
}
Self::Text(text_value) => Some(text_value.clone()),
Self::Signal(signal) => Some(signal.get()),
_ => None,
})
.filter(|segment: &String| !segment.is_empty())
.collect::<Vec<String>>()
.join(&CHAR_SPACE.to_string())
};
let attr_signal: Signal<String> = Signal::create(compute());
subscribe_attr_signal(attr_signal, compute);
Self::Signal(attr_signal)
} else {
let result: String = values
.iter()
.filter_map(|value: &Self| match value {
Self::Css(css) => {
css.inject_style();
Some(css.get_name().to_string())
}
Self::Text(text_value) => Some(text_value.clone()),
_ => None,
})
.filter(|segment: &String| !segment.is_empty())
.collect::<Vec<String>>()
.join(&CHAR_SPACE.to_string());
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 || {
owned_values
.iter()
.filter_map(|value: &Self| match value {
Self::Text(text_value) => Some(text_value.clone()),
Self::Signal(signal) => Some(signal.get()),
_ => None,
})
.filter(|segment: &String| !segment.is_empty())
.collect::<Vec<String>>()
.join(&CHAR_SPACE.to_string())
};
let attr_signal: Signal<String> = Signal::create(compute());
subscribe_attr_signal(attr_signal, compute);
Self::Signal(attr_signal)
} else {
let result: String = values
.iter()
.filter_map(|value: &Self| match value {
Self::Text(text_value) => Some(text_value.clone()),
_ => None,
})
.filter(|segment: &String| !segment.is_empty())
.collect::<Vec<String>>()
.join(&CHAR_SPACE.to_string());
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() == new_signal.get_inner() {
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 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(CSS_RULE_OPEN);
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(CSS_RULE_OPEN)
.unwrap_or_default();
let style_end: Option<usize> = after_selector.find(CHAR_CSS_RULE_CLOSE);
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(CHAR_CSS_RULE_CLOSE)
.unwrap_or_default();
}
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(CSS_MEDIA_PREFIX) {
break;
}
let after_prefix: &str = remaining.strip_prefix(CSS_MEDIA_PREFIX).unwrap_or_default();
let query_end: Option<usize> = after_prefix.find(CSS_RULE_OPEN);
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(CSS_RULE_OPEN)
.unwrap_or_default();
let style_end: Option<usize> = after_query.find(CHAR_CSS_RULE_CLOSE);
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(CHAR_CSS_RULE_CLOSE)
.unwrap_or_default();
}
rules
}
pub fn inject_style(&self) {
if !Self::mark_injected(self.get_name().clone()) {
return;
}
let mut css_text: String = format!(
"{CHAR_CSS_CLASS_PREFIX}{}{CSS_RULE_OPEN_FORMAT}{}{CSS_RULE_CLOSE_FORMAT}",
self.get_name(),
self.get_style()
);
for pseudo_rule in self.get_pseudo_rules() {
if !pseudo_rule.get_style().is_empty() {
css_text = format!(
"{css_text}{CHAR_CSS_RULE_SEPARATOR}{CHAR_CSS_CLASS_PREFIX}{}{}{CSS_RULE_OPEN_FORMAT}{}{CSS_RULE_CLOSE_FORMAT}",
self.get_name(),
pseudo_rule.get_selector(),
pseudo_rule.get_style()
);
}
}
for media_rule in self.get_media_rules() {
if !media_rule.get_query().is_empty() {
css_text = format!(
"{css_text}{CHAR_CSS_RULE_SEPARATOR}{CSS_MEDIA_PREFIX}{}{CSS_RULE_OPEN_FORMAT}{CHAR_CSS_CLASS_PREFIX}{}{CSS_RULE_OPEN_FORMAT}{}{CSS_RULE_CLOSE_FORMAT}{CSS_RULE_CLOSE_FORMAT}",
media_rule.get_query(),
self.get_name(),
media_rule.get_style()
);
}
}
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_ID;
let window_value: Window = match window() {
Some(window_instance) => window_instance,
None => return,
};
let document: Document = match window_value.document() {
Some(document_instance) => document_instance,
None => return,
};
let style_element: HtmlStyleElement = match document.get_element_by_id(style_id) {
Some(existing_element) => match existing_element.dyn_into::<HtmlStyleElement>() {
Ok(element) => element,
Err(_err) => return,
},
None => {
let created: Element = match document.create_element(STYLE_TAG) {
Ok(element) => element,
Err(_err) => return,
};
let style_element_from_id: HtmlStyleElement =
match created.dyn_into::<HtmlStyleElement>() {
Ok(element) => element,
Err(_err) => return,
};
style_element_from_id.set_id(style_id);
if let Some(head) = document.head() {
let _ = head.append_child(&style_element_from_id);
}
style_element_from_id
}
};
if !css_text.is_empty() {
let text_node: Text = document.create_text_node(css_text);
let _ = style_element.append_child(&text_node);
}
}
pub fn create_style_string(props: &[(&str, &str)]) -> String {
props
.iter()
.map(|(key, value): &(&str, &str)| {
format!("{key}{CSS_PROP_SEPARATOR}{value}{CHAR_CSS_DECL_TERMINATOR}")
})
.collect::<Vec<String>>()
.join(&CHAR_SPACE.to_string())
}
pub fn create_style_string_owned(props: &[(String, String)]) -> String {
props
.iter()
.map(|(key, value): &(String, String)| {
format!("{key}{CSS_PROP_SEPARATOR}{value}{CHAR_CSS_DECL_TERMINATOR}")
})
.collect::<Vec<String>>()
.join(&CHAR_SPACE.to_string())
}
pub fn inject_css(css_text: &str) {
Self::append_css(css_text);
}
}
impl Display for Css {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(formatter, "{}", self.get_name())
}
}