Skip to content

Commit

Permalink
Merge #550
Browse files Browse the repository at this point in the history
550: Add support for svg by creating elements with correct namespace r=jstarry a=jstarry

Fixes #143
Fixes #328

Co-authored-by: Justin Starry <jstarry@users.noreply.github.com>
  • Loading branch information
bors[bot] and jstarry committed Aug 3, 2019
2 parents 16695a2 + 480a099 commit a190a94
Show file tree
Hide file tree
Showing 13 changed files with 125 additions and 55 deletions.
14 changes: 5 additions & 9 deletions crates/macro/src/html_tree/html_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,25 +136,20 @@ 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;
}

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;
}
Expand All @@ -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()
}
}

Expand Down
14 changes: 4 additions & 10 deletions crates/macro/src/html_tree/html_dashed_name.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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;
Expand All @@ -58,14 +59,7 @@ impl Peek<'_, Self> for HtmlDashedName {

impl Parse for HtmlDashedName {
fn parse(input: ParseStream) -> ParseResult<Self> {
let name = if let Ok(token) = input.parse::<Token![type]>() {
Ident::new("type", token.span).into()
} else if let Ok(token) = input.parse::<Token![for]>() {
Ident::new("for", token.span).into()
} else {
input.parse::<Ident>()?.into()
};

let name = input.call(Ident::parse_any)?;
let mut extended = Vec::new();
while input.peek(Token![-]) {
extended.push((input.parse::<Token![-]>()?, input.parse::<Ident>()?));
Expand Down
8 changes: 4 additions & 4 deletions crates/macro/src/html_tree/html_tag/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -198,7 +198,7 @@ impl PeekValue<TagName> 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 {
Expand Down Expand Up @@ -261,7 +261,7 @@ impl PeekValue<TagName> 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)
}
Expand Down Expand Up @@ -320,7 +320,7 @@ impl PeekValue<TagName> 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()?;
Expand Down
10 changes: 10 additions & 0 deletions crates/macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ trait PeekValue<T> {
fn peek(cursor: Cursor) -> Option<T>;
}

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);
Expand Down
9 changes: 5 additions & 4 deletions src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ struct CreatedState<COMP: Component> {
impl<COMP: Component + Renderable<COMP>> CreatedState<COMP> {
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;
}
Expand Down Expand Up @@ -229,7 +229,8 @@ impl<COMP> Scope<COMP>
where
COMP: Component + Renderable<COMP>,
{
pub(crate) fn new() -> Self {
/// visible for testing
pub fn new() -> Self {
let shared_state = Rc::new(RefCell::new(ComponentState::Empty));
Scope { shared_state }
}
Expand Down Expand Up @@ -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 => {}
Expand Down
4 changes: 2 additions & 2 deletions src/virtual_dom/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub trait VDiff {
type Component: Component;

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

/// Scoped diff apply to other tree.
///
Expand All @@ -102,7 +102,7 @@ pub trait VDiff {
/// (always removes the `Node` that exists).
fn apply(
&mut self,
parent: &Node,
parent: &Element,
precursor: Option<&Node>,
ancestor: Option<VNode<Self::Component>>,
scope: &Scope<Self::Component>,
Expand Down
4 changes: 2 additions & 2 deletions src/virtual_dom/vcomp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ where
type Component = COMP;

/// Remove VComp from parent.
fn detach(&mut self, parent: &Node) -> Option<Node> {
fn detach(&mut self, parent: &Element) -> Option<Node> {
match self.state.replace(MountState::Detached) {
MountState::Mounted(this) => {
(this.destroyer)();
Expand All @@ -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<VNode<Self::Component>>,
env: &Scope<Self::Component>,
Expand Down
6 changes: 3 additions & 3 deletions src/virtual_dom/vlist.rs
Original file line number Diff line number Diff line change
@@ -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<COMP: Component> {
Expand All @@ -24,7 +24,7 @@ impl<COMP: Component> VList<COMP> {
impl<COMP: Component> VDiff for VList<COMP> {
type Component = COMP;

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

fn apply(
&mut self,
parent: &Node,
parent: &Element,
precursor: Option<&Node>,
ancestor: Option<VNode<Self::Component>>,
env: &Scope<Self::Component>,
Expand Down
6 changes: 3 additions & 3 deletions src/virtual_dom/vnode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<COMP: Component> {
Expand All @@ -24,7 +24,7 @@ impl<COMP: Component> VDiff for VNode<COMP> {
type Component = COMP;

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

fn apply(
&mut self,
parent: &Node,
parent: &Element,
precursor: Option<&Node>,
ancestor: Option<VNode<Self::Component>>,
env: &Scope<Self::Component>,
Expand Down
38 changes: 25 additions & 13 deletions src/virtual_dom/vtag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -344,8 +350,8 @@ impl<COMP: Component> VTag<COMP> {
}
}

// 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) {
Expand All @@ -366,7 +372,7 @@ impl<COMP: Component> VDiff for VTag<COMP> {
type Component = COMP;

/// Remove VTag from parent.
fn detach(&mut self, parent: &Node) -> Option<Node> {
fn detach(&mut self, parent: &Element) -> Option<Node> {
let node = self
.reference
.take()
Expand All @@ -382,7 +388,7 @@ impl<COMP: Component> VDiff for VTag<COMP> {
/// to compute what to patch in the actual DOM nodes.
fn apply(
&mut self,
parent: &Node,
parent: &Element,
precursor: Option<&Node>,
ancestor: Option<VNode<Self::Component>>,
env: &Scope<Self::Component>,
Expand Down Expand Up @@ -421,9 +427,18 @@ impl<COMP: Component> VDiff for VTag<COMP> {
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)
Expand Down Expand Up @@ -472,14 +487,11 @@ impl<COMP: Component> VDiff for VTag<COMP> {
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,
}
Expand Down
6 changes: 3 additions & 3 deletions src/virtual_dom/vtext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -34,7 +34,7 @@ impl<COMP: Component> VDiff for VText<COMP> {
type Component = COMP;

/// Remove VTag from parent.
fn detach(&mut self, parent: &Node) -> Option<Node> {
fn detach(&mut self, parent: &Element) -> Option<Node> {
let node = self
.reference
.take()
Expand All @@ -52,7 +52,7 @@ impl<COMP: Component> VDiff for VText<COMP> {
/// has children and renders them.
fn apply(
&mut self,
parent: &Node,
parent: &Element,
_: Option<&Node>,
opposite: Option<VNode<Self::Component>>,
_: &Scope<Self::Component>,
Expand Down
17 changes: 16 additions & 1 deletion tests/macro/html-tag-pass.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![recursion_limit = "256"]
#![recursion_limit = "512"]

#[macro_use]
mod helpers;
Expand All @@ -18,6 +18,21 @@ pass_helper! {
<option selected=false disabled=true value="">{"Unselected"}</option>
</select>
</div>
<svg width="149" height="147" viewBox="0 0 149 147" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M60.5776 13.8268L51.8673 42.6431L77.7475 37.331L60.5776 13.8268Z" fill="#DEB819"/>
<path d="M108.361 94.9937L138.708 90.686L115.342 69.8642" stroke="black" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<g filter="url(#filter0_d)">
<circle cx="75.3326" cy="73.4918" r="55" fill="#FDD630"/>
<circle cx="75.3326" cy="73.4918" r="52.5" stroke="black" stroke-width="5"/>
</g>
<circle cx="71" cy="99" r="5" fill="white" fill-opacity="0.75" stroke="black" stroke-width="3"/>
<defs>
<filter id="filter0_d" x="16.3326" y="18.4918" width="118" height="118" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation="2"/>
<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"/>
</filter>
</defs>
</svg>
<img class=("avatar", "hidden") src="http://pic.com" />
<img class="avatar hidden", />
<button onclick=|e| panic!(e) />
Expand Down
Loading

0 comments on commit a190a94

Please sign in to comment.