Extend head components with global attributes (#2888)

* Fix hot reloading components with keys

* include component formatted segment keys, but not dynamic component value

* extend each head component with the corresponding element

* Allow spreading custom attributes into components

* Fix component_literal_dyn_idx index

* add a new test for hot reloading components with keys

* FIx script without body warning and rendering styles with a href set

* fix clippy
This commit is contained in:
Evan Almloff authored and GitHub committed 2024-10-01 10:24:22 -05:00
1 parent c2b131f249
commit 95976d9a17
8 files changed
+863 -1134

No files matched your search

+60 -21
View File
@@ -2,6 +2,7 @@
use std::{cell::RefCell, collections::HashSet, rc::Rc};
use crate as dioxus_elements;
use dioxus_core::{prelude::*, DynamicNode};
use dioxus_core_macro::*;
@@ -19,12 +20,42 @@ fn use_update_warning<T: PartialEq + Clone + 'static>(value: &T, name: &'static
}
}
fn extract_single_text_node(children: &Element, component: &str) -> Option<String> {
/// An error that can occur when extracting a single text node from a component
pub enum ExtractSingleTextNodeError<'a> {
/// The node contained an render error, so we can't extract the text node
RenderError(&'a RenderError),
/// There was only one child, but it wasn't a text node
NonTextNode,
/// There is multiple child nodes
NonTemplate,
}
impl ExtractSingleTextNodeError<'_> {
/// Log a warning depending on the error
pub fn log(&self, component: &str) {
match self {
ExtractSingleTextNodeError::RenderError(err) => {
tracing::error!("Error while rendering {component}: {err}");
}
ExtractSingleTextNodeError::NonTextNode => {
tracing::error!(
"Error while rendering {component}: The children of {component} must be a single text node"
);
}
ExtractSingleTextNodeError::NonTemplate => {
tracing::error!(
"Error while rendering {component}: The children of {component} must be a single text node"
);
}
}
}
}
fn extract_single_text_node(children: &Element) -> Result<String, ExtractSingleTextNodeError<'_>> {
let vnode = match children {
Element::Ok(vnode) => vnode,
Element::Err(err) => {
tracing::error!("Error while rendering {component}: {err}");
return None;
return Err(ExtractSingleTextNodeError::RenderError(err));
}
};
// The title's children must be in one of two forms:
@@ -37,7 +68,7 @@ fn extract_single_text_node(children: &Element, component: &str) -> Option<Strin
node_paths: &[],
attr_paths: &[],
..
} => Some(text.to_string()),
} => Ok(text.to_string()),
// rsx! { "title: {dynamic_text}" }
Template {
roots: &[TemplateNode::Dynamic { id }],
@@ -47,19 +78,11 @@ fn extract_single_text_node(children: &Element, component: &str) -> Option<Strin
} => {
let node = &vnode.dynamic_nodes[id];
match node {
DynamicNode::Text(text) => Some(text.value.clone()),
_ => {
tracing::error!("Error while rendering {component}: The children of {component} must be a single text node. It cannot be a component, if statement, loop, or a fragment");
None
}
DynamicNode::Text(text) => Ok(text.value.clone()),
_ => Err(ExtractSingleTextNodeError::NonTextNode),
}
}
_ => {
tracing::error!(
"Error while rendering title: The children of title must be a single text node"
);
None
}
_ => Err(ExtractSingleTextNodeError::NonTemplate),
}
}
@@ -90,8 +113,12 @@ pub struct TitleProps {
#[component]
pub fn Title(props: TitleProps) -> Element {
let children = props.children;
let Some(text) = extract_single_text_node(&children, "Title") else {
return VNode::empty();
let text = match extract_single_text_node(&children) {
Ok(text) => text,
Err(err) => {
err.log("Title");
return VNode::empty();
}
};
// Update the title as it changes. NOTE: We don't use use_effect here because we need this to run on the server
@@ -112,6 +139,7 @@ pub fn Title(props: TitleProps) -> Element {
VNode::empty()
}
#[non_exhaustive]
/// Props for the [`Meta`] component
#[derive(Clone, Props, PartialEq)]
pub struct MetaProps {
@@ -120,6 +148,8 @@ pub struct MetaProps {
pub charset: Option<String>,
pub http_equiv: Option<String>,
pub content: Option<String>,
#[props(extends = meta, extends = GlobalAttributes)]
pub additional_attributes: Vec<Attribute>,
}
impl MetaProps {
@@ -179,6 +209,7 @@ pub fn Meta(props: MetaProps) -> Element {
VNode::empty()
}
#[non_exhaustive]
#[derive(Clone, Props, PartialEq)]
pub struct ScriptProps {
/// The contents of the script tag. If present, the children must be a single text node.
@@ -193,6 +224,8 @@ pub struct ScriptProps {
pub nonce: Option<String>,
pub referrerpolicy: Option<String>,
pub r#type: Option<String>,
#[props(extends = script, extends = GlobalAttributes)]
pub additional_attributes: Vec<Attribute>,
}
impl ScriptProps {
@@ -228,8 +261,8 @@ impl ScriptProps {
attributes
}
pub fn script_contents(&self) -> Option<String> {
extract_single_text_node(&self.children, "Script")
pub fn script_contents(&self) -> Result<String, ExtractSingleTextNodeError<'_>> {
extract_single_text_node(&self.children)
}
}
@@ -277,6 +310,7 @@ pub fn Script(props: ScriptProps) -> Element {
VNode::empty()
}
#[non_exhaustive]
#[derive(Clone, Props, PartialEq)]
pub struct StyleProps {
/// Styles are deduplicated by their href attribute
@@ -286,6 +320,8 @@ pub struct StyleProps {
pub title: Option<String>,
/// The contents of the style tag. If present, the children must be a single text node.
pub children: Element,
#[props(extends = style, extends = GlobalAttributes)]
pub additional_attributes: Vec<Attribute>,
}
impl StyleProps {
@@ -306,8 +342,8 @@ impl StyleProps {
attributes
}
pub fn style_contents(&self) -> Option<String> {
extract_single_text_node(&self.children, "Title")
pub fn style_contents(&self) -> Result<String, ExtractSingleTextNodeError<'_>> {
extract_single_text_node(&self.children)
}
}
@@ -357,6 +393,7 @@ pub fn Style(props: StyleProps) -> Element {
use super::*;
#[non_exhaustive]
#[derive(Clone, Props, PartialEq)]
pub struct LinkProps {
pub rel: Option<String>,
@@ -374,6 +411,8 @@ pub struct LinkProps {
pub integrity: Option<String>,
pub r#type: Option<String>,
pub blocking: Option<String>,
#[props(extends = link, extends = GlobalAttributes)]
pub additional_attributes: Vec<Attribute>,
}
impl LinkProps {
+26 -3
View File
@@ -61,14 +61,37 @@ pub trait Document {
/// Create a new script tag
fn create_script(&self, props: ScriptProps) {
let attributes = props.attributes();
let js = create_element_in_head("script", &attributes, props.script_contents());
let js = match (&props.src, props.script_contents()) {
// The script has inline contents, render it as a script tag
(_, Ok(contents)) => create_element_in_head("script", &attributes, Some(contents)),
// The script has a src, render it as a script tag without a body
(Some(_), _) => create_element_in_head("script", &attributes, None),
// The script has neither contents nor src, log an error
(None, Err(err)) => {
err.log("Script");
return;
}
};
self.new_evaluator(js);
}
/// Create a new style tag
fn create_style(&self, props: StyleProps) {
let attributes = props.attributes();
let js = create_element_in_head("style", &attributes, props.style_contents());
let mut attributes = props.attributes();
let js = match (&props.href, props.style_contents()) {
// The style has inline contents, render it as a style tag
(_, Ok(contents)) => create_element_in_head("style", &attributes, Some(contents)),
// The style has a src, render it as a link tag
(Some(_), _) => {
attributes.push(("type", "text/css".into()));
create_element_in_head("link", &attributes, None)
}
// The style has neither contents nor src, log an error
(None, Err(err)) => {
err.log("Style");
return;
}
};
self.new_evaluator(js);
}