Skip to content

Commit a190a94

Browse files
bors[bot]jstarry
andcommitted
Merge #550
550: Add support for svg by creating elements with correct namespace r=jstarry a=jstarry Fixes #143 Fixes #328 Co-authored-by: Justin Starry <[email protected]>
2 parents 16695a2 + 480a099 commit a190a94

File tree

13 files changed

+125
-55
lines changed

13 files changed

+125
-55
lines changed

Diff for: crates/macro/src/html_tree/html_component.rs

+5-9
Original file line numberDiff line numberDiff line change
@@ -136,25 +136,20 @@ impl HtmlComponent {
136136
}
137137

138138
fn peek_type(mut cursor: Cursor) -> Option<()> {
139-
let mut type_str: String = "".to_owned();
140139
let mut colons_optional = true;
140+
let mut last_ident = None;
141141

142142
loop {
143-
let mut found_colons = false;
144143
let mut post_colons_cursor = cursor;
145144
if let Some(c) = Self::double_colon(post_colons_cursor) {
146-
found_colons = true;
147145
post_colons_cursor = c;
148146
} else if !colons_optional {
149147
break;
150148
}
151149

152150
if let Some((ident, c)) = post_colons_cursor.ident() {
153151
cursor = c;
154-
if found_colons {
155-
type_str += "::";
156-
}
157-
type_str += &ident.to_string();
152+
last_ident = Some(ident);
158153
} else {
159154
break;
160155
}
@@ -163,8 +158,9 @@ impl HtmlComponent {
163158
colons_optional = false;
164159
}
165160

166-
(!type_str.is_empty()).as_option()?;
167-
(type_str.to_lowercase() != type_str).as_option()
161+
let type_str = last_ident?.to_string();
162+
type_str.is_ascii().as_option()?;
163+
type_str.bytes().next()?.is_ascii_uppercase().as_option()
168164
}
169165
}
170166

Diff for: crates/macro/src/html_tree/html_dashed_name.rs

+4-10
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
use crate::Peek;
1+
use crate::{non_capitalized_ascii, Peek};
22
use boolinator::Boolinator;
33
use proc_macro2::Ident;
44
use proc_macro2::Span;
55
use quote::{quote, ToTokens};
66
use std::fmt;
77
use syn::buffer::Cursor;
8+
use syn::ext::IdentExt;
89
use syn::parse::{Parse, ParseStream, Result as ParseResult};
910
use syn::Token;
1011

@@ -36,7 +37,7 @@ impl fmt::Display for HtmlDashedName {
3637
impl Peek<'_, Self> for HtmlDashedName {
3738
fn peek(cursor: Cursor) -> Option<(Self, Cursor)> {
3839
let (name, cursor) = cursor.ident()?;
39-
(name.to_string().to_lowercase() == name.to_string()).as_option()?;
40+
non_capitalized_ascii(&name.to_string()).as_option()?;
4041

4142
let mut extended = Vec::new();
4243
let mut cursor = cursor;
@@ -58,14 +59,7 @@ impl Peek<'_, Self> for HtmlDashedName {
5859

5960
impl Parse for HtmlDashedName {
6061
fn parse(input: ParseStream) -> ParseResult<Self> {
61-
let name = if let Ok(token) = input.parse::<Token![type]>() {
62-
Ident::new("type", token.span).into()
63-
} else if let Ok(token) = input.parse::<Token![for]>() {
64-
Ident::new("for", token.span).into()
65-
} else {
66-
input.parse::<Ident>()?.into()
67-
};
68-
62+
let name = input.call(Ident::parse_any)?;
6963
let mut extended = Vec::new();
7064
while input.peek(Token![-]) {
7165
extended.push((input.parse::<Token![-]>()?, input.parse::<Ident>()?));

Diff for: crates/macro/src/html_tree/html_tag/mod.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use super::HtmlDashedName as TagName;
44
use super::HtmlProp as TagAttribute;
55
use super::HtmlPropSuffix as TagSuffix;
66
use super::HtmlTree;
7-
use crate::{Peek, PeekValue};
7+
use crate::{non_capitalized_ascii, Peek, PeekValue};
88
use boolinator::Boolinator;
99
use proc_macro2::{Delimiter, Span};
1010
use quote::{quote, quote_spanned, ToTokens};
@@ -198,7 +198,7 @@ impl PeekValue<TagName> for HtmlSelfClosingTag {
198198
(punct.as_char() == '<').as_option()?;
199199

200200
let (name, mut cursor) = TagName::peek(cursor)?;
201-
(name.to_string().to_lowercase() == name.to_string()).as_option()?;
201+
non_capitalized_ascii(&name.to_string()).as_option()?;
202202

203203
let mut after_slash = false;
204204
loop {
@@ -261,7 +261,7 @@ impl PeekValue<TagName> for HtmlTagOpen {
261261
(punct.as_char() == '<').as_option()?;
262262

263263
let (name, _) = TagName::peek(cursor)?;
264-
(name.to_string().to_lowercase() == name.to_string()).as_option()?;
264+
non_capitalized_ascii(&name.to_string()).as_option()?;
265265

266266
Some(name)
267267
}
@@ -320,7 +320,7 @@ impl PeekValue<TagName> for HtmlTagClose {
320320
(punct.as_char() == '/').as_option()?;
321321

322322
let (name, cursor) = TagName::peek(cursor)?;
323-
(name.to_string().to_lowercase() == name.to_string()).as_option()?;
323+
non_capitalized_ascii(&name.to_string()).as_option()?;
324324

325325
let (punct, _) = cursor.punct()?;
326326
(punct.as_char() == '>').as_option()?;

Diff for: crates/macro/src/lib.rs

+10
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ trait PeekValue<T> {
7575
fn peek(cursor: Cursor) -> Option<T>;
7676
}
7777

78+
fn non_capitalized_ascii(string: &str) -> bool {
79+
if !string.is_ascii() {
80+
false
81+
} else if let Some(c) = string.bytes().next() {
82+
c.is_ascii_lowercase()
83+
} else {
84+
false
85+
}
86+
}
87+
7888
#[proc_macro_derive(Properties, attributes(props))]
7989
pub fn derive_props(input: TokenStream) -> TokenStream {
8090
let input = parse_macro_input!(input as DerivePropsInput);

Diff for: src/html.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ struct CreatedState<COMP: Component> {
163163
impl<COMP: Component + Renderable<COMP>> CreatedState<COMP> {
164164
fn update(mut self) -> Self {
165165
let mut next_frame = self.component.view();
166-
let node = next_frame.apply(self.element.as_node(), None, self.last_frame, &self.env);
166+
let node = next_frame.apply(&self.element, None, self.last_frame, &self.env);
167167
if let Some(ref mut cell) = self.occupied {
168168
*cell.borrow_mut() = node;
169169
}
@@ -229,7 +229,8 @@ impl<COMP> Scope<COMP>
229229
where
230230
COMP: Component + Renderable<COMP>,
231231
{
232-
pub(crate) fn new() -> Self {
232+
/// visible for testing
233+
pub fn new() -> Self {
233234
let shared_state = Rc::new(RefCell::new(ComponentState::Empty));
234235
Scope { shared_state }
235236
}
@@ -298,12 +299,12 @@ where
298299
ComponentState::Created(mut this) => {
299300
this.component.destroy();
300301
if let Some(last_frame) = &mut this.last_frame {
301-
last_frame.detach(this.element.as_node());
302+
last_frame.detach(&this.element);
302303
}
303304
}
304305
ComponentState::Ready(mut this) => {
305306
if let Some(ancestor) = &mut this.ancestor {
306-
ancestor.detach(this.element.as_node());
307+
ancestor.detach(&this.element);
307308
}
308309
}
309310
ComponentState::Empty | ComponentState::Destroyed => {}

Diff for: src/virtual_dom/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub trait VDiff {
7777
type Component: Component;
7878

7979
/// Remove itself from parent and return the next sibling.
80-
fn detach(&mut self, parent: &Node) -> Option<Node>;
80+
fn detach(&mut self, parent: &Element) -> Option<Node>;
8181

8282
/// Scoped diff apply to other tree.
8383
///
@@ -102,7 +102,7 @@ pub trait VDiff {
102102
/// (always removes the `Node` that exists).
103103
fn apply(
104104
&mut self,
105-
parent: &Node,
105+
parent: &Element,
106106
precursor: Option<&Node>,
107107
ancestor: Option<VNode<Self::Component>>,
108108
scope: &Scope<Self::Component>,

Diff for: src/virtual_dom/vcomp.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ where
206206
type Component = COMP;
207207

208208
/// Remove VComp from parent.
209-
fn detach(&mut self, parent: &Node) -> Option<Node> {
209+
fn detach(&mut self, parent: &Element) -> Option<Node> {
210210
match self.state.replace(MountState::Detached) {
211211
MountState::Mounted(this) => {
212212
(this.destroyer)();
@@ -226,7 +226,7 @@ where
226226
/// It compares this with an ancestor `VComp` and overwrites it if it is the same type.
227227
fn apply(
228228
&mut self,
229-
parent: &Node,
229+
parent: &Element,
230230
precursor: Option<&Node>,
231231
ancestor: Option<VNode<Self::Component>>,
232232
env: &Scope<Self::Component>,

Diff for: src/virtual_dom/vlist.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! This module contains fragments implementation.
22
use super::{VDiff, VNode, VText};
33
use crate::html::{Component, Scope};
4-
use stdweb::web::Node;
4+
use stdweb::web::{Element, Node};
55

66
/// This struct represents a fragment of the Virtual DOM tree.
77
pub struct VList<COMP: Component> {
@@ -24,7 +24,7 @@ impl<COMP: Component> VList<COMP> {
2424
impl<COMP: Component> VDiff for VList<COMP> {
2525
type Component = COMP;
2626

27-
fn detach(&mut self, parent: &Node) -> Option<Node> {
27+
fn detach(&mut self, parent: &Element) -> Option<Node> {
2828
let mut last_sibling = None;
2929
for mut child in self.childs.drain(..) {
3030
last_sibling = child.detach(parent);
@@ -34,7 +34,7 @@ impl<COMP: Component> VDiff for VList<COMP> {
3434

3535
fn apply(
3636
&mut self,
37-
parent: &Node,
37+
parent: &Element,
3838
precursor: Option<&Node>,
3939
ancestor: Option<VNode<Self::Component>>,
4040
env: &Scope<Self::Component>,

Diff for: src/virtual_dom/vnode.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use super::{VComp, VDiff, VList, VTag, VText};
44
use crate::html::{Component, Renderable, Scope};
55
use std::cmp::PartialEq;
66
use std::fmt;
7-
use stdweb::web::{INode, Node};
7+
use stdweb::web::{Element, INode, Node};
88

99
/// Bind virtual element to a DOM reference.
1010
pub enum VNode<COMP: Component> {
@@ -24,7 +24,7 @@ impl<COMP: Component> VDiff for VNode<COMP> {
2424
type Component = COMP;
2525

2626
/// Remove VNode from parent.
27-
fn detach(&mut self, parent: &Node) -> Option<Node> {
27+
fn detach(&mut self, parent: &Element) -> Option<Node> {
2828
match *self {
2929
VNode::VTag(ref mut vtag) => vtag.detach(parent),
3030
VNode::VText(ref mut vtext) => vtext.detach(parent),
@@ -42,7 +42,7 @@ impl<COMP: Component> VDiff for VNode<COMP> {
4242

4343
fn apply(
4444
&mut self,
45-
parent: &Node,
45+
parent: &Element,
4646
precursor: Option<&Node>,
4747
ancestor: Option<VNode<Self::Component>>,
4848
env: &Scope<Self::Component>,

Diff for: src/virtual_dom/vtag.rs

+25-13
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ use stdweb::web::{document, Element, EventListenerHandle, IElement, INode, Node}
1414
#[allow(unused_imports)]
1515
use stdweb::{_js_impl, js};
1616

17+
/// SVG namespace string used for creating svg elements
18+
pub const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
19+
20+
/// Default namespace for html elements
21+
pub const HTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml";
22+
1723
/// A type for a virtual
1824
/// [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element)
1925
/// representation.
@@ -344,8 +350,8 @@ impl<COMP: Component> VTag<COMP> {
344350
}
345351
}
346352

347-
// IMPORTANT! This parameters have to be set every time
348-
// to prevent strange behaviour in browser when DOM changed
353+
// IMPORTANT! This parameter has to be set every time
354+
// to prevent strange behaviour in the browser when the DOM changes
349355
set_checked(&input, self.checked);
350356
} else if let Ok(tae) = TextAreaElement::try_from(element.clone()) {
351357
if let Some(change) = self.diff_value(ancestor) {
@@ -366,7 +372,7 @@ impl<COMP: Component> VDiff for VTag<COMP> {
366372
type Component = COMP;
367373

368374
/// Remove VTag from parent.
369-
fn detach(&mut self, parent: &Node) -> Option<Node> {
375+
fn detach(&mut self, parent: &Element) -> Option<Node> {
370376
let node = self
371377
.reference
372378
.take()
@@ -382,7 +388,7 @@ impl<COMP: Component> VDiff for VTag<COMP> {
382388
/// to compute what to patch in the actual DOM nodes.
383389
fn apply(
384390
&mut self,
385-
parent: &Node,
391+
parent: &Element,
386392
precursor: Option<&Node>,
387393
ancestor: Option<VNode<Self::Component>>,
388394
env: &Scope<Self::Component>,
@@ -421,9 +427,18 @@ impl<COMP: Component> VDiff for VTag<COMP> {
421427
match reform {
422428
Reform::Keep => {}
423429
Reform::Before(before) => {
424-
let element = document()
425-
.create_element(&self.tag)
426-
.expect("can't create element for vtag");
430+
let element = if self.tag == "svg"
431+
|| parent.namespace_uri() == Some(SVG_NAMESPACE.to_string())
432+
{
433+
document()
434+
.create_element_ns(SVG_NAMESPACE, &self.tag)
435+
.expect("can't create namespaced element for vtag")
436+
} else {
437+
document()
438+
.create_element(&self.tag)
439+
.expect("can't create element for vtag")
440+
};
441+
427442
if let Some(sibling) = before {
428443
parent
429444
.insert_before(&element, &sibling)
@@ -472,14 +487,11 @@ impl<COMP: Component> VDiff for VTag<COMP> {
472487
let mut ancestor_childs = ancestor_childs.drain(..);
473488
loop {
474489
match (self_childs.next(), ancestor_childs.next()) {
475-
(Some(left), Some(right)) => {
476-
precursor = left.apply(element.as_node(), precursor.as_ref(), Some(right), &env);
477-
}
478-
(Some(left), None) => {
479-
precursor = left.apply(element.as_node(), precursor.as_ref(), None, &env);
490+
(Some(left), right) => {
491+
precursor = left.apply(&element, precursor.as_ref(), right, &env);
480492
}
481493
(None, Some(ref mut right)) => {
482-
right.detach(element.as_node());
494+
right.detach(&element);
483495
}
484496
(None, None) => break,
485497
}

Diff for: src/virtual_dom/vtext.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use log::warn;
66
use std::cmp::PartialEq;
77
use std::fmt;
88
use std::marker::PhantomData;
9-
use stdweb::web::{document, INode, Node, TextNode};
9+
use stdweb::web::{document, Element, INode, Node, TextNode};
1010

1111
/// A type for a virtual
1212
/// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode)
@@ -34,7 +34,7 @@ impl<COMP: Component> VDiff for VText<COMP> {
3434
type Component = COMP;
3535

3636
/// Remove VTag from parent.
37-
fn detach(&mut self, parent: &Node) -> Option<Node> {
37+
fn detach(&mut self, parent: &Element) -> Option<Node> {
3838
let node = self
3939
.reference
4040
.take()
@@ -52,7 +52,7 @@ impl<COMP: Component> VDiff for VText<COMP> {
5252
/// has children and renders them.
5353
fn apply(
5454
&mut self,
55-
parent: &Node,
55+
parent: &Element,
5656
_: Option<&Node>,
5757
opposite: Option<VNode<Self::Component>>,
5858
_: &Scope<Self::Component>,

Diff for: tests/macro/html-tag-pass.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![recursion_limit = "256"]
1+
#![recursion_limit = "512"]
22

33
#[macro_use]
44
mod helpers;
@@ -18,6 +18,21 @@ pass_helper! {
1818
<option selected=false disabled=true value="">{"Unselected"}</option>
1919
</select>
2020
</div>
21+
<svg width="149" height="147" viewBox="0 0 149 147" fill="none" xmlns="http://www.w3.org/2000/svg">
22+
<path d="M60.5776 13.8268L51.8673 42.6431L77.7475 37.331L60.5776 13.8268Z" fill="#DEB819"/>
23+
<path d="M108.361 94.9937L138.708 90.686L115.342 69.8642" stroke="black" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
24+
<g filter="url(#filter0_d)">
25+
<circle cx="75.3326" cy="73.4918" r="55" fill="#FDD630"/>
26+
<circle cx="75.3326" cy="73.4918" r="52.5" stroke="black" stroke-width="5"/>
27+
</g>
28+
<circle cx="71" cy="99" r="5" fill="white" fill-opacity="0.75" stroke="black" stroke-width="3"/>
29+
<defs>
30+
<filter id="filter0_d" x="16.3326" y="18.4918" width="118" height="118" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
31+
<feGaussianBlur stdDeviation="2"/>
32+
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
33+
</filter>
34+
</defs>
35+
</svg>
2136
<img class=("avatar", "hidden") src="http://pic.com" />
2237
<img class="avatar hidden", />
2338
<button onclick=|e| panic!(e) />

0 commit comments

Comments
 (0)