Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for SVG #389

Merged
merged 1 commit into from
Mar 17, 2022
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
6 changes: 2 additions & 4 deletions packages/sycamore-macro/src/view/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,10 @@ impl Codegen {

let quote_tag = match tag {
ElementTag::Builtin(id) => quote! {
let __el = ::sycamore::generic_node::GenericNode::element(
<::sycamore::html::#id as ::sycamore::generic_node::SycamoreElement>::TAG_NAME
);
let __el = ::sycamore::generic_node::GenericNode::element::<::sycamore::html::#id>();
},
ElementTag::Custom(tag_s) => quote! {
let __el = ::sycamore::generic_node::GenericNode::element(#tag_s);
let __el = ::sycamore::generic_node::GenericNode::element_from_tag(#tag_s);
},
};

Expand Down
4 changes: 2 additions & 2 deletions packages/sycamore/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ impl<'a, G: GenericNode, F: FnOnce(Scope<'a>) -> G + 'a> ElementBuilderOrView<'a
pub fn h<'a, E: SycamoreElement, G: GenericNode>(
_: E,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G> {
ElementBuilder::new(move |_| G::element(E::TAG_NAME))
ElementBuilder::new(move |_| G::element::<E>())
}

/// Construct a new [`ElementBuilder`] from a tag name.
Expand All @@ -105,7 +105,7 @@ pub fn h<'a, E: SycamoreElement, G: GenericNode>(
pub fn tag<'a, G: GenericNode>(
t: impl AsRef<str>,
) -> ElementBuilder<'a, G, impl FnOnce(Scope<'a>) -> G> {
ElementBuilder::new(move |_| G::element(t.as_ref()))
ElementBuilder::new(move |_| G::element_from_tag(t.as_ref()))
}

impl<'a, G: GenericNode, F: FnOnce(Scope<'a>) -> G + 'a> ElementBuilder<'a, G, F> {
Expand Down
24 changes: 23 additions & 1 deletion packages/sycamore/src/generic_node/dom_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use crate::reactive::*;
use crate::utils::render::insert;
use crate::view::View;

use super::SycamoreElement;

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = Node)]
Expand Down Expand Up @@ -150,7 +152,27 @@ fn document() -> web_sys::Document {
impl GenericNode for DomNode {
type EventType = web_sys::Event;

fn element(tag: &str) -> Self {
fn element<T: SycamoreElement>() -> Self {
let node = if let Some(ns) = T::NAME_SPACE {
document()
.create_element_ns(Some(ns), intern(T::TAG_NAME))
.unwrap_throw()
.dyn_into()
.unwrap_throw()
} else {
document()
.create_element(intern(T::TAG_NAME))
.unwrap_throw()
.dyn_into()
.unwrap_throw()
};
DomNode {
id: Default::default(),
node,
}
}

fn element_from_tag(tag: &str) -> Self {
let node = document()
.create_element(intern(tag))
.unwrap_throw()
Expand Down
11 changes: 9 additions & 2 deletions packages/sycamore/src/generic_node/hydrate_dom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::utils::render::insert;
use crate::view::View;

use super::dom_node::NodeId;
use super::SycamoreElement;

/// Rendering backend for the DOM with hydration support.
///
Expand Down Expand Up @@ -88,19 +89,25 @@ impl GenericNode for HydrateNode {

/// When hydrating, instead of creating a new node, this will attempt to hydrate an existing
/// node.
fn element(tag: &str) -> Self {
fn element<T: SycamoreElement>() -> Self {
let el = get_next_element();
if let Some(el) = el {
Self {
node: DomNode::from_web_sys(el.into()),
}
} else {
Self {
node: DomNode::element(tag),
node: DomNode::element::<T>(),
}
}
}

fn element_from_tag(tag: &str) -> Self {
Self {
node: DomNode::element_from_tag(tag),
}
}

/// When hydrating, instead of creating a new node, this will attempt to hydrate an existing
/// node.
fn text_node(text: &str) -> Self {
Expand Down
5 changes: 4 additions & 1 deletion packages/sycamore/src/generic_node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ pub trait GenericNode: fmt::Debug + Clone + PartialEq + Eq + Hash + 'static {
const CLIENT_SIDE_HYDRATION: bool = false;

/// Create a new element node.
fn element(tag: &str) -> Self;
fn element<T: SycamoreElement>() -> Self;

/// Create a new element node from a tag string.
fn element_from_tag(tag: &str) -> Self;

/// Create a new text node.
fn text_node(text: &str) -> Self;
Expand Down
43 changes: 30 additions & 13 deletions packages/sycamore/src/generic_node/ssr_node.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Rendering backend for Server Side Rendering, aka. SSR.

use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
Expand All @@ -15,6 +16,8 @@ use crate::reactive::*;
use crate::utils::hydrate::{get_next_id, with_hydration_context};
use crate::view::View;

use super::SycamoreElement;

static VOID_ELEMENTS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
vec![
"area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param",
Expand Down Expand Up @@ -129,14 +132,27 @@ impl GenericNode for SsrNode {
type EventType = web_sys::Event;
const USE_HYDRATION_CONTEXT: bool = true;

fn element(tag: &str) -> Self {
fn element<T: SycamoreElement>() -> Self {
let hk = get_next_id();
let mut attributes = IndexMap::new();
if let Some(hk) = hk {
attributes.insert("data-hk".to_string(), format!("{}.{}", hk.0, hk.1));
}
Self::new(SsrNodeType::Element(RefCell::new(Element {
name: Cow::Borrowed(T::TAG_NAME),
attributes,
children: Default::default(),
})))
}

fn element_from_tag(tag: &str) -> Self {
let hk = get_next_id();
let mut attributes = IndexMap::new();
if let Some(hk) = hk {
attributes.insert("data-hk".to_string(), format!("{}.{}", hk.0, hk.1));
}
Self::new(SsrNodeType::Element(RefCell::new(Element {
name: tag.to_string(),
name: Cow::Owned(tag.to_string()),
attributes,
children: Default::default(),
})))
Expand Down Expand Up @@ -365,7 +381,7 @@ impl WriteToString for SsrNode {
/// A SSR element.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Element {
name: String,
name: Cow<'static, str>,
attributes: IndexMap<String, String>,
children: Vec<SsrNode>,
}
Expand All @@ -386,7 +402,7 @@ impl WriteToString for Element {
}

// Check if self-closing tag (void-element).
if self.children.is_empty() && VOID_ELEMENTS.contains(self.name.as_str()) {
if self.children.is_empty() && VOID_ELEMENTS.contains(&*self.name) {
s.push_str("/>");
} else {
s.push('>');
Expand Down Expand Up @@ -502,6 +518,7 @@ pub async fn render_to_string_await_suspense(
#[cfg(test)]
mod tests {
use super::*;
use crate::html;
use crate::prelude::*;

#[test]
Expand Down Expand Up @@ -536,9 +553,9 @@ mod tests {

#[test]
fn append_child() {
let node = SsrNode::element("div");
let p = SsrNode::element("p");
let p2 = SsrNode::element("p");
let node = SsrNode::element::<html::div>();
let p = SsrNode::element::<html::p>();
let p2 = SsrNode::element::<html::p>();

node.append_child(&p);
node.append_child(&p2);
Expand All @@ -556,8 +573,8 @@ mod tests {

#[test]
fn remove_child() {
let node = SsrNode::element("div");
let p = SsrNode::element("p");
let node = SsrNode::element::<html::div>();
let p = SsrNode::element::<html::p>();

node.append_child(&p);
// p parent should be updated
Expand All @@ -575,10 +592,10 @@ mod tests {

#[test]
fn remove_child_2() {
let node = SsrNode::element("div");
let p = SsrNode::element("p");
let p2 = SsrNode::element("p");
let p3 = SsrNode::element("p");
let node = SsrNode::element::<html::div>();
let p = SsrNode::element::<html::p>();
let p2 = SsrNode::element::<html::p>();
let p3 = SsrNode::element::<html::p>();

node.append_child(&p);
node.append_child(&p2);
Expand Down
76 changes: 74 additions & 2 deletions packages/sycamore/src/html/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::generic_node::SycamoreElement;
/// MBE for generating elements.
macro_rules! define_elements {
(
$ns:expr,
$(
$(#[$attr:meta])*
$el:ident {
Expand All @@ -25,14 +26,15 @@ macro_rules! define_elements {

impl SycamoreElement for $el {
const TAG_NAME: &'static str = stringify!($el);
const NAME_SPACE: Option<&'static str> = None;
const NAME_SPACE: Option<&'static str> = $ns;
}
)*
};
}

// A list of valid HTML5 elements (does not include removed or obsolete elements).
define_elements! {
None,
/// The `<a>` HTML element (or anchor element), with its `href` attribute, creates a hyperlink to web pages, files, email addresses, locations in the same page, or anything else a URL can address.
///
/// Content within each `<a>` should indicate the link's destination. If the `href` attribute is present, pressing the enter key while focused on the `<a>` element will activate it.
Expand Down Expand Up @@ -170,7 +172,6 @@ define_elements! {
sub {},
summary {},
sup {},
svg {},
table {},
tbody {},
td {},
Expand All @@ -190,3 +191,74 @@ define_elements! {
video {},
wbr {},
}

// A list of valid SVG elements. Some elements are commented out because they conflict with the HTML elements.
define_elements! {
Some("http://www.w3.org/2000/svg"),
svg {},
// a,
animate {},
animateMotion {},
animateTransform {},
circle {},
clipPath {},
defs {},
desc {},
discard {},
ellipse {},
feBlend {},
feColorMatrix {},
feComponentTransfer {},
feComposite {},
feConvolveMatrix {},
feDiffuseLighting {},
feDisplacementMap {},
feDistantLight {},
feDropShadow {},
feFlood {},
feFuncA {},
feFuncB {},
feFuncG {},
feFuncR {},
feGaussianBlur {},
feImage {},
feMerge {},
feMergeNode {},
feMorphology {},
feOffset {},
fePointLight {},
feSpecularLighting {},
feSpotLight {},
feTile {},
feTurbulence {},
filter {},
foreignObject {},
g {},
hatch {},
hatchpath {},
image {},
line {},
linearGradient {},
marker {},
mask {},
metadata {},
mpath {},
path {},
pattern {},
polygon {},
polyline {},
radialGradient {},
rect {},
// script {},
set {},
stop {},
// style {},
switch {},
symbol {},
text {},
textPath {},
// title {},
tspan {},
r#use {},
view {},
}
5 changes: 3 additions & 2 deletions packages/sycamore/src/noderef.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ impl<'a> ScopeCreateNodeRef<'a> for Scope<'a> {

#[cfg(all(test, feature = "ssr"))]
mod tests {
use crate::html;
use crate::prelude::*;

#[test]
Expand All @@ -108,7 +109,7 @@ mod tests {
#[test]
fn set_noderef() {
let noderef = NodeRef::<SsrNode>::new();
let node = SsrNode::element("div");
let node = SsrNode::element::<html::div>();
noderef.set(node.clone());
assert_eq!(noderef.try_get_raw(), Some(node.clone()));
assert_eq!(noderef.try_get::<SsrNode>(), Some(node));
Expand All @@ -117,7 +118,7 @@ mod tests {
#[test]
fn cast_noderef() {
let noderef = NodeRef::<SsrNode>::new();
let node = SsrNode::element("div");
let node = SsrNode::element::<html::div>();
noderef.set(node.clone());
assert_eq!(noderef.try_get::<SsrNode>(), Some(node));
assert!(noderef.try_get::<DomNode>().is_none());
Expand Down
3 changes: 2 additions & 1 deletion packages/sycamore/tests/web/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod portal;
pub mod reconcile;
pub mod render;

use sycamore::html;
use sycamore::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;
Expand Down Expand Up @@ -437,7 +438,7 @@ fn dyn_fragment_reuse_nodes() {

#[wasm_bindgen_test]
fn dom_node_add_class_splits_at_whitespace() {
let node = DomNode::element("div");
let node = DomNode::element::<html::div>();
node.add_class("my_class");
assert_eq!(
node.inner_element()
Expand Down
Loading