diff --git a/crates/macro/src/html_tree/html_component.rs b/crates/macro/src/html_tree/html_component.rs
index 990c6f7de4b..725b939502d 100644
--- a/crates/macro/src/html_tree/html_component.rs
+++ b/crates/macro/src/html_tree/html_component.rs
@@ -136,14 +136,12 @@ impl HtmlComponent {
}
fn peek_type(mut cursor: Cursor) -> Option<()> {
- let mut type_str: String = "".to_owned();
let mut colons_optional = true;
+ let mut last_ident = None;
loop {
- let mut found_colons = false;
let mut post_colons_cursor = cursor;
if let Some(c) = Self::double_colon(post_colons_cursor) {
- found_colons = true;
post_colons_cursor = c;
} else if !colons_optional {
break;
@@ -151,10 +149,7 @@ impl HtmlComponent {
if let Some((ident, c)) = post_colons_cursor.ident() {
cursor = c;
- if found_colons {
- type_str += "::";
- }
- type_str += &ident.to_string();
+ last_ident = Some(ident);
} else {
break;
}
@@ -163,8 +158,9 @@ impl HtmlComponent {
colons_optional = false;
}
- (!type_str.is_empty()).as_option()?;
- (type_str.to_lowercase() != type_str).as_option()
+ let type_str = last_ident?.to_string();
+ type_str.is_ascii().as_option()?;
+ type_str.bytes().next()?.is_ascii_uppercase().as_option()
}
}
diff --git a/crates/macro/src/html_tree/html_dashed_name.rs b/crates/macro/src/html_tree/html_dashed_name.rs
index e2b6be916f4..f6b894fddf3 100644
--- a/crates/macro/src/html_tree/html_dashed_name.rs
+++ b/crates/macro/src/html_tree/html_dashed_name.rs
@@ -1,10 +1,11 @@
-use crate::Peek;
+use crate::{non_capitalized_ascii, Peek};
use boolinator::Boolinator;
use proc_macro2::Ident;
use proc_macro2::Span;
use quote::{quote, ToTokens};
use std::fmt;
use syn::buffer::Cursor;
+use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::Token;
@@ -36,7 +37,7 @@ impl fmt::Display for HtmlDashedName {
impl Peek<'_, Self> for HtmlDashedName {
fn peek(cursor: Cursor) -> Option<(Self, Cursor)> {
let (name, cursor) = cursor.ident()?;
- (name.to_string().to_lowercase() == name.to_string()).as_option()?;
+ non_capitalized_ascii(&name.to_string()).as_option()?;
let mut extended = Vec::new();
let mut cursor = cursor;
@@ -58,14 +59,7 @@ impl Peek<'_, Self> for HtmlDashedName {
impl Parse for HtmlDashedName {
fn parse(input: ParseStream) -> ParseResult {
- let name = if let Ok(token) = input.parse::() {
- Ident::new("type", token.span).into()
- } else if let Ok(token) = input.parse::() {
- Ident::new("for", token.span).into()
- } else {
- input.parse::()?.into()
- };
-
+ let name = input.call(Ident::parse_any)?;
let mut extended = Vec::new();
while input.peek(Token![-]) {
extended.push((input.parse::()?, input.parse::()?));
diff --git a/crates/macro/src/html_tree/html_tag/mod.rs b/crates/macro/src/html_tree/html_tag/mod.rs
index 165eb82ea33..0252f3ed5ef 100644
--- a/crates/macro/src/html_tree/html_tag/mod.rs
+++ b/crates/macro/src/html_tree/html_tag/mod.rs
@@ -4,7 +4,7 @@ use super::HtmlDashedName as TagName;
use super::HtmlProp as TagAttribute;
use super::HtmlPropSuffix as TagSuffix;
use super::HtmlTree;
-use crate::{Peek, PeekValue};
+use crate::{non_capitalized_ascii, Peek, PeekValue};
use boolinator::Boolinator;
use proc_macro2::{Delimiter, Span};
use quote::{quote, quote_spanned, ToTokens};
@@ -198,7 +198,7 @@ impl PeekValue for HtmlSelfClosingTag {
(punct.as_char() == '<').as_option()?;
let (name, mut cursor) = TagName::peek(cursor)?;
- (name.to_string().to_lowercase() == name.to_string()).as_option()?;
+ non_capitalized_ascii(&name.to_string()).as_option()?;
let mut after_slash = false;
loop {
@@ -261,7 +261,7 @@ impl PeekValue for HtmlTagOpen {
(punct.as_char() == '<').as_option()?;
let (name, _) = TagName::peek(cursor)?;
- (name.to_string().to_lowercase() == name.to_string()).as_option()?;
+ non_capitalized_ascii(&name.to_string()).as_option()?;
Some(name)
}
@@ -320,7 +320,7 @@ impl PeekValue for HtmlTagClose {
(punct.as_char() == '/').as_option()?;
let (name, cursor) = TagName::peek(cursor)?;
- (name.to_string().to_lowercase() == name.to_string()).as_option()?;
+ non_capitalized_ascii(&name.to_string()).as_option()?;
let (punct, _) = cursor.punct()?;
(punct.as_char() == '>').as_option()?;
diff --git a/crates/macro/src/lib.rs b/crates/macro/src/lib.rs
index 48c9a426a68..012f53c5763 100644
--- a/crates/macro/src/lib.rs
+++ b/crates/macro/src/lib.rs
@@ -75,6 +75,16 @@ trait PeekValue {
fn peek(cursor: Cursor) -> Option;
}
+fn non_capitalized_ascii(string: &str) -> bool {
+ if !string.is_ascii() {
+ false
+ } else if let Some(c) = string.bytes().next() {
+ c.is_ascii_lowercase()
+ } else {
+ false
+ }
+}
+
#[proc_macro_derive(Properties, attributes(props))]
pub fn derive_props(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DerivePropsInput);
diff --git a/src/html.rs b/src/html.rs
index 31918673de1..246023bb93c 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -163,7 +163,7 @@ struct CreatedState {
impl> CreatedState {
fn update(mut self) -> Self {
let mut next_frame = self.component.view();
- let node = next_frame.apply(self.element.as_node(), None, self.last_frame, &self.env);
+ let node = next_frame.apply(&self.element, None, self.last_frame, &self.env);
if let Some(ref mut cell) = self.occupied {
*cell.borrow_mut() = node;
}
@@ -229,7 +229,8 @@ impl Scope
where
COMP: Component + Renderable,
{
- pub(crate) fn new() -> Self {
+ /// visible for testing
+ pub fn new() -> Self {
let shared_state = Rc::new(RefCell::new(ComponentState::Empty));
Scope { shared_state }
}
@@ -298,12 +299,12 @@ where
ComponentState::Created(mut this) => {
this.component.destroy();
if let Some(last_frame) = &mut this.last_frame {
- last_frame.detach(this.element.as_node());
+ last_frame.detach(&this.element);
}
}
ComponentState::Ready(mut this) => {
if let Some(ancestor) = &mut this.ancestor {
- ancestor.detach(this.element.as_node());
+ ancestor.detach(&this.element);
}
}
ComponentState::Empty | ComponentState::Destroyed => {}
diff --git a/src/virtual_dom/mod.rs b/src/virtual_dom/mod.rs
index 473ac5fae9c..978d2155731 100644
--- a/src/virtual_dom/mod.rs
+++ b/src/virtual_dom/mod.rs
@@ -76,7 +76,7 @@ pub trait VDiff {
type Component: Component;
/// Remove itself from parent and return the next sibling.
- fn detach(&mut self, parent: &Node) -> Option;
+ fn detach(&mut self, parent: &Element) -> Option;
/// Scoped diff apply to other tree.
///
@@ -101,7 +101,7 @@ pub trait VDiff {
/// (always removes the `Node` that exists).
fn apply(
&mut self,
- parent: &Node,
+ parent: &Element,
precursor: Option<&Node>,
ancestor: Option>,
scope: &Scope,
diff --git a/src/virtual_dom/vcomp.rs b/src/virtual_dom/vcomp.rs
index 4d17749e356..daa99256e32 100644
--- a/src/virtual_dom/vcomp.rs
+++ b/src/virtual_dom/vcomp.rs
@@ -206,7 +206,7 @@ where
type Component = COMP;
/// Remove VComp from parent.
- fn detach(&mut self, parent: &Node) -> Option {
+ fn detach(&mut self, parent: &Element) -> Option {
match self.state.replace(MountState::Detached) {
MountState::Mounted(this) => {
(this.destroyer)();
@@ -226,7 +226,7 @@ where
/// It compares this with an ancestor `VComp` and overwrites it if it is the same type.
fn apply(
&mut self,
- parent: &Node,
+ parent: &Element,
precursor: Option<&Node>,
ancestor: Option>,
env: &Scope,
diff --git a/src/virtual_dom/vlist.rs b/src/virtual_dom/vlist.rs
index 9f603dd34e0..deede464cbe 100644
--- a/src/virtual_dom/vlist.rs
+++ b/src/virtual_dom/vlist.rs
@@ -1,7 +1,7 @@
//! This module contains fragments implementation.
use super::{VDiff, VNode, VText};
use crate::html::{Component, Scope};
-use stdweb::web::Node;
+use stdweb::web::{Element, Node};
/// This struct represents a fragment of the Virtual DOM tree.
pub struct VList {
@@ -24,7 +24,7 @@ impl VList {
impl VDiff for VList {
type Component = COMP;
- fn detach(&mut self, parent: &Node) -> Option {
+ fn detach(&mut self, parent: &Element) -> Option {
let mut last_sibling = None;
for mut child in self.childs.drain(..) {
last_sibling = child.detach(parent);
@@ -34,7 +34,7 @@ impl VDiff for VList {
fn apply(
&mut self,
- parent: &Node,
+ parent: &Element,
precursor: Option<&Node>,
ancestor: Option>,
env: &Scope,
diff --git a/src/virtual_dom/vnode.rs b/src/virtual_dom/vnode.rs
index e5aed48a56f..679df81c3b7 100644
--- a/src/virtual_dom/vnode.rs
+++ b/src/virtual_dom/vnode.rs
@@ -4,7 +4,7 @@ use super::{VComp, VDiff, VList, VTag, VText};
use crate::html::{Component, Renderable, Scope};
use std::cmp::PartialEq;
use std::fmt;
-use stdweb::web::{INode, Node};
+use stdweb::web::{Element, INode, Node};
/// Bind virtual element to a DOM reference.
pub enum VNode {
@@ -24,7 +24,7 @@ impl VDiff for VNode {
type Component = COMP;
/// Remove VNode from parent.
- fn detach(&mut self, parent: &Node) -> Option {
+ fn detach(&mut self, parent: &Element) -> Option {
match *self {
VNode::VTag(ref mut vtag) => vtag.detach(parent),
VNode::VText(ref mut vtext) => vtext.detach(parent),
@@ -42,7 +42,7 @@ impl VDiff for VNode {
fn apply(
&mut self,
- parent: &Node,
+ parent: &Element,
precursor: Option<&Node>,
ancestor: Option>,
env: &Scope,
diff --git a/src/virtual_dom/vtag.rs b/src/virtual_dom/vtag.rs
index e350f5eff6e..58190512ddb 100644
--- a/src/virtual_dom/vtag.rs
+++ b/src/virtual_dom/vtag.rs
@@ -14,6 +14,12 @@ use stdweb::web::{document, Element, EventListenerHandle, IElement, INode, Node}
#[allow(unused_imports)]
use stdweb::{_js_impl, js};
+/// SVG namespace string used for creating svg elements
+pub const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
+
+/// Default namespace for html elements
+pub const HTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml";
+
/// A type for a virtual
/// [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element)
/// representation.
@@ -344,8 +350,8 @@ impl VTag {
}
}
- // IMPORTANT! This parameters have to be set every time
- // to prevent strange behaviour in browser when DOM changed
+ // IMPORTANT! This parameter has to be set every time
+ // to prevent strange behaviour in the browser when the DOM changes
set_checked(&input, self.checked);
} else if let Ok(tae) = TextAreaElement::try_from(element.clone()) {
if let Some(change) = self.diff_value(ancestor) {
@@ -366,7 +372,7 @@ impl VDiff for VTag {
type Component = COMP;
/// Remove VTag from parent.
- fn detach(&mut self, parent: &Node) -> Option {
+ fn detach(&mut self, parent: &Element) -> Option {
let node = self
.reference
.take()
@@ -382,7 +388,7 @@ impl VDiff for VTag {
/// to compute what to patch in the actual DOM nodes.
fn apply(
&mut self,
- parent: &Node,
+ parent: &Element,
precursor: Option<&Node>,
ancestor: Option>,
env: &Scope,
@@ -421,9 +427,18 @@ impl VDiff for VTag {
match reform {
Reform::Keep => {}
Reform::Before(before) => {
- let element = document()
- .create_element(&self.tag)
- .expect("can't create element for vtag");
+ let element = if self.tag == "svg"
+ || parent.namespace_uri() == Some(SVG_NAMESPACE.to_string())
+ {
+ document()
+ .create_element_ns(SVG_NAMESPACE, &self.tag)
+ .expect("can't create namespaced element for vtag")
+ } else {
+ document()
+ .create_element(&self.tag)
+ .expect("can't create element for vtag")
+ };
+
if let Some(sibling) = before {
parent
.insert_before(&element, &sibling)
@@ -472,14 +487,11 @@ impl VDiff for VTag {
let mut ancestor_childs = ancestor_childs.drain(..);
loop {
match (self_childs.next(), ancestor_childs.next()) {
- (Some(left), Some(right)) => {
- precursor = left.apply(element.as_node(), precursor.as_ref(), Some(right), &env);
- }
- (Some(left), None) => {
- precursor = left.apply(element.as_node(), precursor.as_ref(), None, &env);
+ (Some(left), right) => {
+ precursor = left.apply(&element, precursor.as_ref(), right, &env);
}
(None, Some(ref mut right)) => {
- right.detach(element.as_node());
+ right.detach(&element);
}
(None, None) => break,
}
diff --git a/src/virtual_dom/vtext.rs b/src/virtual_dom/vtext.rs
index 3d6f0f55bde..9e3adfd679a 100644
--- a/src/virtual_dom/vtext.rs
+++ b/src/virtual_dom/vtext.rs
@@ -6,7 +6,7 @@ use log::warn;
use std::cmp::PartialEq;
use std::fmt;
use std::marker::PhantomData;
-use stdweb::web::{document, INode, Node, TextNode};
+use stdweb::web::{document, Element, INode, Node, TextNode};
/// A type for a virtual
/// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode)
@@ -34,7 +34,7 @@ impl VDiff for VText {
type Component = COMP;
/// Remove VTag from parent.
- fn detach(&mut self, parent: &Node) -> Option {
+ fn detach(&mut self, parent: &Element) -> Option {
let node = self
.reference
.take()
@@ -52,7 +52,7 @@ impl VDiff for VText {
/// has children and renders them.
fn apply(
&mut self,
- parent: &Node,
+ parent: &Element,
_: Option<&Node>,
opposite: Option>,
_: &Scope,
diff --git a/tests/macro/html-tag-pass.rs b/tests/macro/html-tag-pass.rs
index 4170257401a..c933e3add6d 100644
--- a/tests/macro/html-tag-pass.rs
+++ b/tests/macro/html-tag-pass.rs
@@ -1,4 +1,4 @@
-#![recursion_limit = "256"]
+#![recursion_limit = "512"]
#[macro_use]
mod helpers;
@@ -18,6 +18,21 @@ pass_helper! {
+
diff --git a/tests/vtag_test.rs b/tests/vtag_test.rs
index d65a7ed8ee5..3c3f4f1c7f3 100644
--- a/tests/vtag_test.rs
+++ b/tests/vtag_test.rs
@@ -1,7 +1,10 @@
#![recursion_limit = "128"]
+use stdweb::web::{document, IElement};
#[cfg(feature = "wasm_test")]
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
-use yew::virtual_dom::VNode;
+use yew::html::Scope;
+use yew::virtual_dom::vtag::{VTag, HTML_NAMESPACE, SVG_NAMESPACE};
+use yew::virtual_dom::{VDiff, VNode};
use yew::{html, Component, ComponentLink, Html, Renderable, ShouldRender};
#[cfg(feature = "wasm_test")]
@@ -213,6 +216,45 @@ fn supports_multiple_classes_string() {
}
}
+fn assert_vtag(node: &mut VNode) -> &mut VTag {
+ if let VNode::VTag(vtag) = node {
+ return vtag;
+ }
+ panic!("should be vtag");
+}
+
+fn assert_namespace(vtag: &VTag, namespace: &'static str) {
+ assert_eq!(
+ vtag.reference.as_ref().unwrap().namespace_uri().unwrap(),
+ namespace
+ );
+}
+
+#[test]
+fn supports_svg() {
+ let scope = Scope::new();
+ let div_el = document().create_element("div").unwrap();
+ let svg_el = document().create_element_ns(SVG_NAMESPACE, "svg").unwrap();
+
+ let mut g_node: VNode = html! { };
+ let path_node: VNode = html! { };
+ let mut svg_node: VNode = html! { };
+
+ let svg_tag = assert_vtag(&mut svg_node);
+ svg_tag.apply(&div_el, None, None, &scope);
+ assert_namespace(svg_tag, SVG_NAMESPACE);
+ let path_tag = assert_vtag(svg_tag.childs.get_mut(0).unwrap());
+ assert_namespace(path_tag, SVG_NAMESPACE);
+
+ let g_tag = assert_vtag(&mut g_node);
+ g_tag.apply(&div_el, None, None, &scope);
+ assert_namespace(g_tag, HTML_NAMESPACE);
+ g_tag.reference = None;
+
+ g_tag.apply(&svg_el, None, None, &scope);
+ assert_namespace(g_tag, SVG_NAMESPACE);
+}
+
#[test]
fn it_compares_values() {
let a: VNode = html! {