use crate::*;
impl Attribute {
pub fn as_str(&self) -> String {
match self {
Attribute::AccessKey => "accesskey".to_string(),
Attribute::Action => "action".to_string(),
Attribute::Alt => "alt".to_string(),
Attribute::AriaLabel => "aria-label".to_string(),
Attribute::AutoComplete => "autocomplete".to_string(),
Attribute::AutoFocus => "autofocus".to_string(),
Attribute::Checked => "checked".to_string(),
Attribute::Class => "class".to_string(),
Attribute::Cols => "cols".to_string(),
Attribute::ContentEditable => "contenteditable".to_string(),
Attribute::Data(name) => format!("data-{}", name),
Attribute::Dir => "dir".to_string(),
Attribute::Disabled => "disabled".to_string(),
Attribute::Draggable => "draggable".to_string(),
Attribute::EncType => "enctype".to_string(),
Attribute::For => "for".to_string(),
Attribute::Form => "form".to_string(),
Attribute::Height => "height".to_string(),
Attribute::Hidden => "hidden".to_string(),
Attribute::Href => "href".to_string(),
Attribute::Id => "id".to_string(),
Attribute::Lang => "lang".to_string(),
Attribute::Max => "max".to_string(),
Attribute::MaxLength => "maxlength".to_string(),
Attribute::Method => "method".to_string(),
Attribute::Min => "min".to_string(),
Attribute::MinLength => "minlength".to_string(),
Attribute::Multiple => "multiple".to_string(),
Attribute::Name => "name".to_string(),
Attribute::Pattern => "pattern".to_string(),
Attribute::Placeholder => "placeholder".to_string(),
Attribute::ReadOnly => "readonly".to_string(),
Attribute::Required => "required".to_string(),
Attribute::Rows => "rows".to_string(),
Attribute::Selected => "selected".to_string(),
Attribute::Size => "size".to_string(),
Attribute::SpellCheck => "spellcheck".to_string(),
Attribute::Src => "src".to_string(),
Attribute::Step => "step".to_string(),
Attribute::Style => "style".to_string(),
Attribute::TabIndex => "tabindex".to_string(),
Attribute::Target => "target".to_string(),
Attribute::Title => "title".to_string(),
Attribute::Type => "type".to_string(),
Attribute::Value => "value".to_string(),
Attribute::Width => "width".to_string(),
Attribute::Other(name) => name.clone(),
}
}
}
impl Clone for DynamicNode {
fn clone(&self) -> Self {
DynamicNode {
render_fn: Rc::clone(&self.render_fn),
hook_context: self.hook_context,
}
}
}
impl AsNode for VirtualNode {
fn as_node(&self) -> Option<VirtualNode> {
Some(self.clone())
}
}
impl AsNode for &VirtualNode {
fn as_node(&self) -> Option<VirtualNode> {
Some((*self).clone())
}
}
impl AsNode for String {
fn as_node(&self) -> Option<VirtualNode> {
Some(VirtualNode::Text(TextNode::new(self.clone(), None)))
}
}
impl AsNode for &str {
fn as_node(&self) -> Option<VirtualNode> {
Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
}
}
impl AsNode for i32 {
fn as_node(&self) -> Option<VirtualNode> {
Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
}
}
impl AsNode for i64 {
fn as_node(&self) -> Option<VirtualNode> {
Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
}
}
impl AsNode for usize {
fn as_node(&self) -> Option<VirtualNode> {
Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
}
}
impl AsNode for f32 {
fn as_node(&self) -> Option<VirtualNode> {
Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
}
}
impl AsNode for f64 {
fn as_node(&self) -> Option<VirtualNode> {
Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
}
}
impl AsNode for bool {
fn as_node(&self) -> Option<VirtualNode> {
Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
}
}
impl<T> AsNode for Signal<T>
where
T: Clone + PartialEq + std::fmt::Display + 'static,
{
fn as_node(&self) -> Option<VirtualNode> {
Some(self.as_reactive_text())
}
}
impl IntoNode for VirtualNode {
fn into_node(self) -> VirtualNode {
self
}
}
impl<F> IntoNode for F
where
F: FnMut() -> VirtualNode + 'static,
{
fn into_node(self) -> VirtualNode {
VirtualNode::Dynamic(DynamicNode {
render_fn: Rc::new(RefCell::new(self)),
hook_context: crate::reactive::create_hook_context(),
})
}
}
impl IntoNode for String {
fn into_node(self) -> VirtualNode {
VirtualNode::Text(TextNode::new(self, None))
}
}
impl IntoNode for &str {
fn into_node(self) -> VirtualNode {
VirtualNode::Text(TextNode::new(self.to_string(), None))
}
}
impl IntoNode for i32 {
fn into_node(self) -> VirtualNode {
VirtualNode::Text(TextNode::new(self.to_string(), None))
}
}
impl IntoNode for usize {
fn into_node(self) -> VirtualNode {
VirtualNode::Text(TextNode::new(self.to_string(), None))
}
}
impl IntoNode for bool {
fn into_node(self) -> VirtualNode {
VirtualNode::Text(TextNode::new(self.to_string(), None))
}
}
impl<T> IntoNode for Signal<T>
where
T: Clone + PartialEq + std::fmt::Display + 'static,
{
fn into_node(self) -> VirtualNode {
self.as_reactive_text()
}
}
impl VirtualNode {
pub fn get_element_node(tag_name: &str) -> Self {
VirtualNode::Element {
tag: Tag::Element(tag_name.to_string()),
attributes: Vec::new(),
children: Vec::new(),
key: None,
}
}
pub fn get_text_node(content: &str) -> Self {
VirtualNode::Text(TextNode::new(content.to_string(), None))
}
pub fn with_attribute(mut self, name: &str, value: AttributeValue) -> Self {
if let VirtualNode::Element {
ref mut attributes, ..
} = self
{
attributes.push(AttributeEntry::new(name.to_string(), value));
}
self
}
pub fn with_child(mut self, child: VirtualNode) -> Self {
if let VirtualNode::Element {
ref mut children, ..
} = self
{
children.push(child);
}
self
}
pub fn is_component(&self) -> bool {
matches!(
self,
VirtualNode::Element {
tag: Tag::Component(_),
..
}
)
}
pub fn tag_name(&self) -> Option<String> {
match self {
VirtualNode::Element { tag, .. } => match tag {
Tag::Element(name) => Some(name.clone()),
Tag::Component(name) => Some(name.clone()),
},
_ => None,
}
}
pub fn try_get_prop(&self, name: &Attribute) -> Option<String> {
let name_str: String = name.as_str();
if let VirtualNode::Element { attributes, .. } = self {
for attr in attributes {
if attr.get_name() == &name_str {
match attr.get_value() {
AttributeValue::Text(value) => return Some(value.clone()),
AttributeValue::Signal(signal) => return Some(signal.get()),
_ => {}
}
}
}
}
None
}
pub fn get_children(&self) -> Vec<VirtualNode> {
if let VirtualNode::Element { children, .. } = self {
children.clone()
} else {
Vec::new()
}
}
pub fn try_get_text(&self) -> Option<String> {
match self {
VirtualNode::Text(text_node) => Some(text_node.get_content().clone()),
VirtualNode::Element { children, .. } => {
children.first().and_then(VirtualNode::try_get_text)
}
_ => None,
}
}
pub fn try_get_event(
&self,
name: &NativeEventName,
) -> Option<crate::event::NativeEventHandler> {
let name_str: String = name.as_str();
if let VirtualNode::Element { attributes, .. } = self {
for attr in attributes {
if attr.get_name() == &name_str
&& let AttributeValue::Event(handler) = attr.get_value()
{
return Some(handler.clone());
}
}
}
None
}
pub fn try_get_callback(&self, name: &str) -> Option<crate::event::NativeEventHandler> {
if let VirtualNode::Element { attributes, .. } = self {
for attr in attributes {
if attr.get_name() == name
&& let AttributeValue::Event(handler) = attr.get_value()
{
return Some(handler.clone());
}
}
}
None
}
}
impl<T> AsReactiveText for Signal<T>
where
T: Clone + PartialEq + std::fmt::Display + 'static,
{
fn as_reactive_text(&self) -> VirtualNode {
let signal: Signal<T> = *self;
let initial: String = signal.get().to_string();
let string_signal: Signal<String> = {
let boxed: Box<SignalInner<String>> = Box::new(SignalInner::new(initial.clone()));
Signal::from_inner(Box::leak(boxed) as *mut SignalInner<String>)
};
let source_signal: Signal<T> = *self;
let string_signal_clone: Signal<String> = string_signal;
source_signal.subscribe({
let source_signal: Signal<T> = source_signal;
move || {
let new_value: String = source_signal.get().to_string();
string_signal_clone.set(new_value);
}
});
VirtualNode::Text(TextNode::new(initial, Some(string_signal)))
}
}
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(|p| format!("{}: {};", p.get_name(), p.get_value()))
.collect::<Vec<String>>()
.join(" ")
}
}
impl Default for Style {
fn default() -> Self {
Self::new(Vec::new())
}
}
impl StyleProperty {
pub fn new(name: String, value: String) -> Self {
let mut prop: StyleProperty = StyleProperty::default();
prop.set_name(name);
prop.set_value(value);
prop
}
}
impl CssClass {
pub fn new(name: String, style: String) -> Self {
let mut css_class: CssClass = CssClass::default();
css_class.set_name(name);
css_class.set_style(style);
css_class
}
pub fn inject_style(&self) {
#[cfg(target_arch = "wasm32")]
{
let style_id: &str = "euv-css-injected";
let document: web_sys::Document = web_sys::window()
.expect("no global window exists")
.document()
.expect("no document exists");
let style_element: web_sys::HtmlStyleElement = match document
.get_element_by_id(style_id)
{
Some(el) => el.dyn_into::<web_sys::HtmlStyleElement>().unwrap(),
None => {
let el: web_sys::HtmlStyleElement = document
.create_element("style")
.unwrap()
.dyn_into::<web_sys::HtmlStyleElement>()
.unwrap();
el.set_id(style_id);
let keyframes: &str = "@keyframes euv-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }";
el.set_inner_text(keyframes);
document.head().unwrap().append_child(&el).unwrap();
el
}
};
let existing_css: String = style_element.inner_text();
let class_rule: String = format!(".{} {{ {} }}", self.get_name(), self.get_style());
if !existing_css.contains(&class_rule) {
let new_css: String = if existing_css.is_empty() {
class_rule
} else {
format!("{}\n{}", existing_css, class_rule)
};
style_element.set_inner_text(&new_css);
}
}
}
}