Skip to main content

dioxus_document/elements/
mod.rs

1#![doc = include_str!("../../docs/head.md")]
2
3use std::{cell::RefCell, collections::HashSet, rc::Rc};
4
5use dioxus_core::{Attribute, DynamicNode, Element, RenderError, Runtime, ScopeId, VNodeChild};
6use dioxus_core_macro::*;
7use dioxus_html::extensions::*;
8mod link;
9pub use link::*;
10mod stylesheet;
11pub use stylesheet::*;
12mod meta;
13pub use meta::*;
14mod script;
15pub use script::*;
16mod style;
17pub use style::*;
18mod title;
19pub use title::*;
20
21/// Warn the user if they try to change props on a element that is injected into the head
22#[allow(unused)]
23fn use_update_warning<T: PartialEq + Clone + 'static>(value: &T, name: &'static str) {
24    #[cfg(debug_assertions)]
25    {
26        use dioxus_core::use_hook;
27
28        let cloned_value = value.clone();
29        let initial = use_hook(move || value.clone());
30
31        if initial != cloned_value {
32            tracing::warn!("Changing the props of `{name}` is not supported ");
33        }
34    }
35}
36
37/// An error that can occur when extracting a single text node from a component
38#[derive(Debug)]
39pub enum ExtractSingleTextNodeError<'a> {
40    /// The node contained an render error, so we can't extract the text node
41    RenderError(&'a RenderError),
42    /// There was only one child, but it wasn't a text node
43    NonTextNode,
44    /// There is multiple child nodes
45    NonTemplate,
46}
47
48impl ExtractSingleTextNodeError<'_> {
49    /// Log a warning depending on the error
50    pub fn log(&self, component: &str) {
51        match self {
52            ExtractSingleTextNodeError::RenderError(err) => {
53                tracing::error!("Error while rendering {component}: {err}");
54            }
55            ExtractSingleTextNodeError::NonTextNode => {
56                tracing::error!(
57                    "Error while rendering {component}: The children of {component} must be a single text node"
58                );
59            }
60            ExtractSingleTextNodeError::NonTemplate => {
61                tracing::error!(
62                    "Error while rendering {component}: The children of {component} must be a single text node"
63                );
64            }
65        }
66    }
67}
68
69fn extract_single_text_node(children: &Element) -> Result<String, ExtractSingleTextNodeError<'_>> {
70    let vnode = match children {
71        Element::Ok(vnode) => vnode,
72        Element::Err(err) => {
73            return Err(ExtractSingleTextNodeError::RenderError(err));
74        }
75    };
76    // The title's children must be in one of two forms:
77    // 1. rsx! { "static text" }
78    // 2. rsx! { "title: {dynamic_text}" }
79    let mut children = vnode.children();
80    let Some(child) = children.next() else {
81        return Err(ExtractSingleTextNodeError::NonTemplate);
82    };
83
84    if children.next().is_some() {
85        return Err(ExtractSingleTextNodeError::NonTemplate);
86    }
87
88    match child {
89        // rsx! { "static text" }
90        VNodeChild::Text(text) => Ok(text.text().to_string()),
91        // rsx! { "title: {dynamic_text}" }
92        VNodeChild::Dynamic(anchor) => {
93            let mut dynamic_nodes = anchor.nodes();
94            let dynamic_node = dynamic_nodes.next();
95            if dynamic_nodes.next().is_some() {
96                return Err(ExtractSingleTextNodeError::NonTemplate);
97            }
98            match dynamic_node {
99                Some(slot) => match &*slot {
100                    DynamicNode::Text(text) => Ok(text.value.clone()),
101                    _ => Err(ExtractSingleTextNodeError::NonTextNode),
102                },
103                _ => Err(ExtractSingleTextNodeError::NonTemplate),
104            }
105        }
106        _ => Err(ExtractSingleTextNodeError::NonTemplate),
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use dioxus_core::{DynamicNode, DynamicValues, VNode, VText};
113    use dioxus_core_template::{TemplateRawTree, TemplateStorage};
114
115    use super::*;
116
117    #[test]
118    fn extracts_static_text() {
119        static TREE: TemplateRawTree = TemplateRawTree::StaticText("static text");
120        static STORAGE: TemplateStorage<2, 1, 1> = TemplateStorage::build_from_tree(&TREE);
121        let children = Ok(VNode::new(
122            STORAGE.as_template(),
123            DynamicValues::from_parts(None, Box::new([]), Box::new([])),
124        ));
125
126        assert_eq!(
127            extract_single_text_node(&children).expect("static text should extract"),
128            "static text"
129        );
130    }
131
132    #[test]
133    fn extracts_formatted_text() {
134        static TREE: TemplateRawTree = TemplateRawTree::DynamicNode;
135        static STORAGE: TemplateStorage<1, 1, 1> = TemplateStorage::build_from_tree(&TREE);
136        let children = Ok(VNode::new(
137            STORAGE.as_template(),
138            DynamicValues::from_parts(
139                None,
140                Box::new([DynamicNode::Text(VText::new("title: dynamic text"))]),
141                Box::new([]),
142            ),
143        ));
144
145        assert_eq!(
146            extract_single_text_node(&children).expect("formatted text should extract"),
147            "title: dynamic text"
148        );
149    }
150}
151
152fn get_or_insert_root_context<T: Default + Clone + 'static>() -> T {
153    let rt = Runtime::current();
154    match rt.has_context::<T>(ScopeId::ROOT) {
155        Some(context) => context,
156        None => {
157            let context = T::default();
158            rt.provide_context(ScopeId::ROOT, context.clone());
159            context
160        }
161    }
162}
163
164#[derive(Default, Clone)]
165struct DeduplicationContext(Rc<RefCell<HashSet<String>>>);
166
167impl DeduplicationContext {
168    fn should_insert(&self, href: &str) -> bool {
169        let mut set = self.0.borrow_mut();
170        let present = set.contains(href);
171        if !present {
172            set.insert(href.to_string());
173            true
174        } else {
175            false
176        }
177    }
178}
179
180/// Extend a list of string attributes with a list of dioxus attribute
181pub(crate) fn extend_attributes(
182    attributes: &mut Vec<(&'static str, String)>,
183    additional_attributes: &[Attribute],
184) {
185    for additional_attribute in additional_attributes {
186        let attribute_value_as_string = match &additional_attribute.value {
187            dioxus_core::AttributeValue::Text(v) => v.to_string(),
188            dioxus_core::AttributeValue::Float(v) => v.to_string(),
189            dioxus_core::AttributeValue::Int(v) => v.to_string(),
190            dioxus_core::AttributeValue::Bool(v) => v.to_string(),
191            dioxus_core::AttributeValue::Listener(_) | dioxus_core::AttributeValue::Any(_) => {
192                tracing::error!(
193                    "document::* elements do not support event listeners or any value attributes. Expected displayable attribute, found {:?}",
194                    additional_attribute.value
195                );
196                continue;
197            }
198            dioxus_core::AttributeValue::None => {
199                continue;
200            }
201        };
202        attributes.push((additional_attribute.name, attribute_value_as_string));
203    }
204}