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 || {
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(CHAR_SPACE);
}
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(CHAR_SPACE);
}
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(CHAR_SPACE);
}
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(CHAR_SPACE);
}
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(CHAR_UNDERSCORE, STR_HYPHEN),
value.as_ref().to_string(),
));
self
}
pub fn to_css_string(&self) -> String {
self.get_properties()
.iter()
.map(|style: &StyleProperty| {
format!(
"{name}{CSS_PROP_SEPARATOR}{value}{CHAR_CSS_DECL_TERMINATOR}",
name = style.get_name(),
value = 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(CHAR_SPACE);
}
result.push_str(&key.replace(CHAR_UNDERSCORE, STR_HYPHEN));
result.push_str(CSS_PROP_SEPARATOR);
result.push_str(value);
result.push(CHAR_CSS_DECL_TERMINATOR);
}
result
}
}
impl Default for Style {
fn default() -> Self {
Self::new(Vec::new())
}
}
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 class_rule: String = format!(
"{CHAR_CSS_CLASS_PREFIX}{} {{ {} }}",
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!(
"{CHAR_CSS_CLASS_PREFIX}{}{} {{ {} }}",
self.get_name(),
pseudo_rule.get_selector(),
pseudo_rule.get_style()
);
css_text = format!("{css_text}\n{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 {} {{ {CHAR_CSS_CLASS_PREFIX}{} {{ {} }} }}",
media_rule.get_query(),
self.get_name(),
media_rule.get_style()
);
css_text = format!("{css_text}\n{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_ID;
let window_value: Window = match window() {
Some(w) => w,
None => return,
};
let document: Document = match window_value.document() {
Some(d) => d,
None => return,
};
let style_element: HtmlStyleElement = match document.get_element_by_id(style_id) {
Some(existing_element) => match existing_element.dyn_into::<HtmlStyleElement>() {
Ok(el) => el,
Err(_err) => return,
},
None => {
let created: Element = match document.create_element(STYLE_TAG) {
Ok(el) => el,
Err(_err) => return,
};
let style_element_from_id: HtmlStyleElement =
match created.dyn_into::<HtmlStyleElement>() {
Ok(el) => el,
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 inject_css(css_text: &str) {
Self::append_css(css_text);
}
}
impl Display for Css {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(formatter, "{class_name}", class_name = self.get_name())
}
}