Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl Component for Child {
let name = format!("I'm {my_name}.");

// Here we emit the callback to the grandparent component, whenever the button is clicked.
let onclick = self.state.child_clicked.reform(move |_| (my_name.clone()));
let onclick = self.state.child_clicked.reform(move |_| my_name.clone());

html! {
<div class="child-body">
Expand Down
9 changes: 7 additions & 2 deletions packages/yew-macro/src/html_tree/html_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,15 +454,20 @@ impl ToTokens for HtmlElement {
}}
});

#[rustversion::since(1.89)]
#[rustversion::since(1.88)]
fn derive_debug_tag(vtag: &Ident) -> String {
let span = vtag.span().unwrap();
#[allow(clippy::incompatible_msrv)]
{
// the file, line, column methods are stable since 1.88
format!("[{}:{}:{}] ", span.file(), span.line(), span.column())
}
}
#[rustversion::before(1.89)]
#[rustversion::before(1.88)]
fn derive_debug_tag(_: &Ident) -> &'static str {
""
}

let invalid_void_tag_msg_start = derive_debug_tag(&vtag);

let value = value();
Expand Down
31 changes: 24 additions & 7 deletions packages/yew/src/dom_bundle/btag/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use web_sys::{Element, HtmlTextAreaElement as TextAreaElement};

use super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
use crate::html::AnyScope;
#[cfg(feature = "hydration")]
use crate::virtual_dom::vtag::HTML_NAMESPACE;
use crate::virtual_dom::vtag::{
InputFields, TextareaFields, VTagInner, Value, MATHML_NAMESPACE, SVG_NAMESPACE,
};
Expand Down Expand Up @@ -365,14 +367,29 @@ mod feat_hydration {
);
let el = node.dyn_into::<Element>().expect("expected an element.");

assert_eq!(
el.tag_name().to_lowercase(),
tag_name,
"expected element of kind {}, found {}.",
tag_name,
el.tag_name().to_lowercase(),
);
{
let el_tag_name = el.tag_name();
let parent_namespace = _parent.namespace_uri();

// In HTML namespace (or no namespace), createElement is case-insensitive
// In other namespaces (SVG, MathML), createElementNS is case-sensitive
let should_compare_case_insensitive = parent_namespace.is_none()
|| parent_namespace.as_deref() == Some(HTML_NAMESPACE);

if should_compare_case_insensitive {
// Case-insensitive comparison for HTML elements
assert!(
tag_name.eq_ignore_ascii_case(&el_tag_name),
"expected element of kind {tag_name}, found {el_tag_name}.",
);
} else {
// Case-sensitive comparison for namespaced elements (SVG, MathML)
assert_eq!(
el_tag_name, tag_name,
"expected element of kind {tag_name}, found {el_tag_name}.",
);
}
}
// We simply register listeners and update all attributes.
let attributes = attributes.apply(root, &el);
let listeners = listeners.apply(root, &el);
Expand Down
1 change: 1 addition & 0 deletions packages/yew/src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ thread_local! {
/// Unless a panic hook is set through this function, Yew will
/// overwrite any existing panic hook when an application is rendered with [Renderer].
#[cfg(feature = "csr")]
#[allow(clippy::incompatible_msrv)]
pub fn set_custom_panic_hook(hook: Box<dyn Fn(&PanicInfo<'_>) + Sync + Send + 'static>) {
std::panic::set_hook(hook);
PANIC_HOOK_IS_SET.with(|hook_is_set| hook_is_set.set(true));
Expand Down
1 change: 1 addition & 0 deletions packages/yew/src/tests/layout_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::html::AnyScope;
use crate::virtual_dom::VNode;
use crate::{scheduler, Component, Context, Html};

#[allow(dead_code)]
struct Comp;
impl Component for Comp {
type Message = ();
Expand Down
97 changes: 97 additions & 0 deletions packages/yew/tests/hydration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1077,3 +1077,100 @@ async fn hydrate_empty() {
let result = obtain_result_by_id("output");
assert_eq!(result.as_str(), r#"<div>after</div><div>after</div>"#);
}

#[wasm_bindgen_test]
async fn hydration_with_camelcase_svg_elements() {
#[function_component]
fn SvgWithCamelCase() -> Html {
html! {
<svg width="100" height="100">
<defs>
<@{"linearGradient"} id="gradient1">
<stop offset="0%" stop-color="red" />
<stop offset="100%" stop-color="blue" />
</@>
<@{"radialGradient"} id="gradient2">
<stop offset="0%" stop-color="yellow" />
<stop offset="100%" stop-color="green" />
</@>
<@{"clipPath"} id="clip1">
<circle cx="50" cy="50" r="40" />
</@>
</defs>
<rect x="10" y="10" width="80" height="80" fill="url(#gradient1)" />
<circle cx="50" cy="50" r="30" fill="url(#gradient2)" clip-path="url(#clip1)" />
<@{"feDropShadow"} dx="2" dy="2" stdDeviation="2" />
</svg>
}
}

#[function_component]
fn App() -> Html {
let counter = use_state(|| 0);
let onclick = {
let counter = counter.clone();
Callback::from(move |_| counter.set(*counter + 1))
};

html! {
<div id="result">
<div class="counter">{"Count: "}{*counter}</div>
<button {onclick} class="increment">{"Increment"}</button>
<SvgWithCamelCase />
</div>
}
}

// Server render
let s = ServerRenderer::<App>::new().render().await;

// Set HTML
gloo::utils::document()
.query_selector("#output")
.unwrap()
.unwrap()
.set_inner_html(&s);

sleep(Duration::ZERO).await;

// Hydrate - this should not panic
Renderer::<App>::with_root(gloo::utils::document().get_element_by_id("output").unwrap())
.hydrate();

sleep(Duration::from_millis(10)).await;

// Verify the SVG elements are present and properly cased
let svg = gloo::utils::document()
.query_selector("svg")
.unwrap()
.unwrap();

let linear_gradient = svg.query_selector("linearGradient").unwrap().unwrap();
assert_eq!(linear_gradient.tag_name(), "linearGradient");

let radial_gradient = svg.query_selector("radialGradient").unwrap().unwrap();
assert_eq!(radial_gradient.tag_name(), "radialGradient");

let clip_path = svg.query_selector("clipPath").unwrap().unwrap();
assert_eq!(clip_path.tag_name(), "clipPath");

// Test interactivity still works after hydration
gloo::utils::document()
.query_selector(".increment")
.unwrap()
.unwrap()
.dyn_into::<HtmlElement>()
.unwrap()
.click();

sleep(Duration::from_millis(10)).await;

let counter_text = gloo::utils::document()
.query_selector(".counter")
.unwrap()
.unwrap()
.text_content()
.unwrap();

assert_eq!(counter_text, "Count: 1");
}
Loading