Merge pull request #1782 from ealmloff/dx-translate-element-conversion
Fix rsx rosetta element and attribute mapping
This commit is contained in:
10 files changed
+204
-17
No files matched your search
@@ -68,3 +68,4 @@ mounted = [
|
|||||||
wasm-bind = ["web-sys", "wasm-bindgen"]
|
wasm-bind = ["web-sys", "wasm-bindgen"]
|
||||||
native-bind = ["tokio"]
|
native-bind = ["tokio"]
|
||||||
hot-reload-context = ["dioxus-rsx"]
|
hot-reload-context = ["dioxus-rsx"]
|
||||||
|
html-to-rsx = []
|
||||||
@@ -79,6 +79,25 @@ macro_rules! impl_attribute_match {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "html-to-rsx")]
|
||||||
|
macro_rules! impl_html_to_rsx_attribute_match {
|
||||||
|
(
|
||||||
|
$attr:ident $fil:ident $name:literal
|
||||||
|
) => {
|
||||||
|
if $attr == $name {
|
||||||
|
return Some(stringify!($fil));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
$attr:ident $fil:ident $_:tt
|
||||||
|
) => {
|
||||||
|
if $attr == stringify!($fil) {
|
||||||
|
return Some(stringify!($fil));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! impl_element {
|
macro_rules! impl_element {
|
||||||
(
|
(
|
||||||
$(#[$attr:meta])*
|
$(#[$attr:meta])*
|
||||||
@@ -316,6 +335,38 @@ macro_rules! builder_constructors {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "html-to-rsx")]
|
||||||
|
pub fn map_html_attribute_to_rsx(html: &str) -> Option<&'static str> {
|
||||||
|
$(
|
||||||
|
$(
|
||||||
|
impl_html_to_rsx_attribute_match!(
|
||||||
|
html $fil $extra
|
||||||
|
);
|
||||||
|
)*
|
||||||
|
)*
|
||||||
|
|
||||||
|
if let Some(name) = crate::map_html_global_attributes_to_rsx(html) {
|
||||||
|
return Some(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(name) = crate::map_html_svg_attributes_to_rsx(html) {
|
||||||
|
return Some(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "html-to-rsx")]
|
||||||
|
pub fn map_html_element_to_rsx(html: &str) -> Option<&'static str> {
|
||||||
|
$(
|
||||||
|
if html == stringify!($name) {
|
||||||
|
return Some(stringify!($name));
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
$(
|
$(
|
||||||
impl_element!(
|
impl_element!(
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
@@ -998,9 +1049,8 @@ builder_constructors! {
|
|||||||
src: Uri DEFAULT,
|
src: Uri DEFAULT,
|
||||||
text: String DEFAULT,
|
text: String DEFAULT,
|
||||||
|
|
||||||
// r#async: Bool,
|
r#async: Bool "async",
|
||||||
// r#type: String, // TODO could be an enum
|
r#type: String "type", // TODO could be an enum
|
||||||
r#type: String "type",
|
|
||||||
r#script: String "script",
|
r#script: String "script",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -33,12 +33,44 @@ macro_rules! trait_method_mapping {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "html-to-rsx")]
|
||||||
|
macro_rules! html_to_rsx_attribute_mapping {
|
||||||
|
(
|
||||||
|
$matching:ident;
|
||||||
|
$(#[$attr:meta])*
|
||||||
|
$name:ident;
|
||||||
|
) => {
|
||||||
|
if $matching == stringify!($name) {
|
||||||
|
return Some(stringify!($name));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(
|
||||||
|
$matching:ident;
|
||||||
|
$(#[$attr:meta])*
|
||||||
|
$name:ident: $lit:literal;
|
||||||
|
) => {
|
||||||
|
if $matching == stringify!($lit) {
|
||||||
|
return Some(stringify!($name));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(
|
||||||
|
$matching:ident;
|
||||||
|
$(#[$attr:meta])*
|
||||||
|
$name:ident: $lit:literal, $ns:literal;
|
||||||
|
) => {
|
||||||
|
if $matching == stringify!($lit) {
|
||||||
|
return Some(stringify!($name));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! trait_methods {
|
macro_rules! trait_methods {
|
||||||
(
|
(
|
||||||
@base
|
@base
|
||||||
$(#[$trait_attr:meta])*
|
$(#[$trait_attr:meta])*
|
||||||
$trait:ident;
|
$trait:ident;
|
||||||
$fn:ident;
|
$fn:ident;
|
||||||
|
$fn_html_to_rsx:ident;
|
||||||
$(
|
$(
|
||||||
$(#[$attr:meta])*
|
$(#[$attr:meta])*
|
||||||
$name:ident $(: $($arg:literal),*)*;
|
$name:ident $(: $($arg:literal),*)*;
|
||||||
@@ -62,6 +94,18 @@ macro_rules! trait_methods {
|
|||||||
)*
|
)*
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "html-to-rsx")]
|
||||||
|
#[doc = "Converts an HTML attribute to an RSX attribute"]
|
||||||
|
pub(crate) fn $fn_html_to_rsx(html: &str) -> Option<&'static str> {
|
||||||
|
$(
|
||||||
|
html_to_rsx_attribute_mapping! {
|
||||||
|
html;
|
||||||
|
$name$(: $($arg),*)*;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Rename the incoming ident and apply a custom namespace
|
// Rename the incoming ident and apply a custom namespace
|
||||||
@@ -79,6 +123,7 @@ trait_methods! {
|
|||||||
|
|
||||||
GlobalAttributes;
|
GlobalAttributes;
|
||||||
map_global_attributes;
|
map_global_attributes;
|
||||||
|
map_html_global_attributes_to_rsx;
|
||||||
|
|
||||||
/// Prevent the default action for this element.
|
/// Prevent the default action for this element.
|
||||||
///
|
///
|
||||||
@@ -1593,6 +1638,7 @@ trait_methods! {
|
|||||||
@base
|
@base
|
||||||
SvgAttributes;
|
SvgAttributes;
|
||||||
map_svg_attributes;
|
map_svg_attributes;
|
||||||
|
map_html_svg_attributes_to_rsx;
|
||||||
|
|
||||||
/// Prevent the default action for this element.
|
/// Prevent the default action for this element.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
mod elements;
|
mod elements;
|
||||||
#[cfg(feature = "hot-reload-context")]
|
#[cfg(feature = "hot-reload-context")]
|
||||||
pub use elements::HtmlCtx;
|
pub use elements::HtmlCtx;
|
||||||
|
#[cfg(feature = "html-to-rsx")]
|
||||||
|
pub use elements::{map_html_attribute_to_rsx, map_html_element_to_rsx};
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
mod global_attributes;
|
mod global_attributes;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ keywords = ["dom", "ui", "gui", "react"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
dioxus-autofmt = { workspace = true }
|
dioxus-autofmt = { workspace = true }
|
||||||
dioxus-rsx = { workspace = true }
|
dioxus-rsx = { workspace = true }
|
||||||
|
dioxus-html = { workspace = true, features = ["html-to-rsx"]}
|
||||||
html_parser.workspace = true
|
html_parser.workspace = true
|
||||||
proc-macro2 = "1.0.49"
|
proc-macro2 = "1.0.49"
|
||||||
quote = "1.0.23"
|
quote = "1.0.23"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||||
|
|
||||||
use convert_case::{Case, Casing};
|
use convert_case::{Case, Casing};
|
||||||
|
use dioxus_html::{map_html_attribute_to_rsx, map_html_element_to_rsx};
|
||||||
use dioxus_rsx::{
|
use dioxus_rsx::{
|
||||||
BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, ElementName, IfmtInput,
|
BodyNode, CallBody, Component, Element, ElementAttr, ElementAttrNamed, ElementName, IfmtInput,
|
||||||
};
|
};
|
||||||
@@ -24,26 +25,41 @@ pub fn rsx_node_from_html(node: &Node) -> Option<BodyNode> {
|
|||||||
match node {
|
match node {
|
||||||
Node::Text(text) => Some(BodyNode::Text(ifmt_from_text(text))),
|
Node::Text(text) => Some(BodyNode::Text(ifmt_from_text(text))),
|
||||||
Node::Element(el) => {
|
Node::Element(el) => {
|
||||||
let el_name = el.name.to_case(Case::Snake);
|
let el_name = if let Some(name) = map_html_element_to_rsx(&el.name) {
|
||||||
let el_name = ElementName::Ident(Ident::new(el_name.as_str(), Span::call_site()));
|
ElementName::Ident(Ident::new(name, Span::call_site()))
|
||||||
|
} else {
|
||||||
|
// if we don't recognize it and it has a dash, we assume it's a web component
|
||||||
|
if el.name.contains('-') {
|
||||||
|
ElementName::Custom(LitStr::new(&el.name, Span::call_site()))
|
||||||
|
} else {
|
||||||
|
// otherwise, it might be an element that isn't supported yet
|
||||||
|
ElementName::Ident(Ident::new(&el.name.to_case(Case::Snake), Span::call_site()))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut attributes: Vec<_> = el
|
let mut attributes: Vec<_> = el
|
||||||
.attributes
|
.attributes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, value)| {
|
.map(|(name, value)| {
|
||||||
let ident = if matches!(name.as_str(), "for" | "async" | "type" | "as") {
|
let value = ifmt_from_text(value.as_deref().unwrap_or("false"));
|
||||||
Ident::new_raw(name.as_str(), Span::call_site())
|
let attr = if let Some(name) = map_html_attribute_to_rsx(name) {
|
||||||
|
let ident = if let Some(name) = name.strip_prefix("r#") {
|
||||||
|
Ident::new_raw(name, Span::call_site())
|
||||||
|
} else {
|
||||||
|
Ident::new(name, Span::call_site())
|
||||||
|
};
|
||||||
|
ElementAttr::AttrText { value, name: ident }
|
||||||
} else {
|
} else {
|
||||||
let new_name = name.to_case(Case::Snake);
|
// If we don't recognize the attribute, we assume it's a custom attribute
|
||||||
Ident::new(new_name.as_str(), Span::call_site())
|
ElementAttr::CustomAttrText {
|
||||||
|
value,
|
||||||
|
name: LitStr::new(name, Span::call_site()),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ElementAttrNamed {
|
ElementAttrNamed {
|
||||||
el_name: el_name.clone(),
|
el_name: el_name.clone(),
|
||||||
attr: ElementAttr::AttrText {
|
attr,
|
||||||
value: ifmt_from_text(value.as_deref().unwrap_or("false")),
|
|
||||||
name: ident,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
use html_parser::Dom;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn h_tags_translate() {
|
||||||
|
let html = r#"
|
||||||
|
<div>
|
||||||
|
<h1>hello world!</h1>
|
||||||
|
<h2>hello world!</h2>
|
||||||
|
<h3>hello world!</h3>
|
||||||
|
<h4>hello world!</h4>
|
||||||
|
<h5>hello world!</h5>
|
||||||
|
<h6>hello world!</h6>
|
||||||
|
</div>
|
||||||
|
"#
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
let dom = Dom::parse(html).unwrap();
|
||||||
|
|
||||||
|
let body = rsx_rosetta::rsx_from_html(&dom);
|
||||||
|
|
||||||
|
let out = dioxus_autofmt::write_block_out(body).unwrap();
|
||||||
|
|
||||||
|
let expected = r#"
|
||||||
|
div {
|
||||||
|
h1 { "hello world!" }
|
||||||
|
h2 { "hello world!" }
|
||||||
|
h3 { "hello world!" }
|
||||||
|
h4 { "hello world!" }
|
||||||
|
h5 { "hello world!" }
|
||||||
|
h6 { "hello world!" }
|
||||||
|
}"#;
|
||||||
|
pretty_assertions::assert_eq!(&out, &expected);
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
use html_parser::Dom;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn raw_attribute() {
|
||||||
|
let html = r#"
|
||||||
|
<div>
|
||||||
|
<div unrecognizedattribute="asd">hello world!</div>
|
||||||
|
</div>
|
||||||
|
"#
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
let dom = Dom::parse(html).unwrap();
|
||||||
|
|
||||||
|
let body = rsx_rosetta::rsx_from_html(&dom);
|
||||||
|
|
||||||
|
let out = dioxus_autofmt::write_block_out(body).unwrap();
|
||||||
|
|
||||||
|
let expected = r#"
|
||||||
|
div { div { "unrecognizedattribute": "asd", "hello world!" } }"#;
|
||||||
|
pretty_assertions::assert_eq!(&out, &expected);
|
||||||
|
}
|
||||||
@@ -9,8 +9,6 @@ fn simple_elements() {
|
|||||||
<div id="asd">hello world!</div>
|
<div id="asd">hello world!</div>
|
||||||
<div for="asd">hello world!</div>
|
<div for="asd">hello world!</div>
|
||||||
<div async="asd">hello world!</div>
|
<div async="asd">hello world!</div>
|
||||||
<div LargeThing="asd">hello world!</div>
|
|
||||||
<ai-is-awesome>hello world!</ai-is-awesome>
|
|
||||||
</div>
|
</div>
|
||||||
"#
|
"#
|
||||||
.trim();
|
.trim();
|
||||||
@@ -28,8 +26,6 @@ fn simple_elements() {
|
|||||||
div { id: "asd", "hello world!" }
|
div { id: "asd", "hello world!" }
|
||||||
div { r#for: "asd", "hello world!" }
|
div { r#for: "asd", "hello world!" }
|
||||||
div { r#async: "asd", "hello world!" }
|
div { r#async: "asd", "hello world!" }
|
||||||
div { large_thing: "asd", "hello world!" }
|
|
||||||
ai_is_awesome { "hello world!" }
|
|
||||||
}"#;
|
}"#;
|
||||||
pretty_assertions::assert_eq!(&out, &expected);
|
pretty_assertions::assert_eq!(&out, &expected);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
use html_parser::Dom;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn web_components_translate() {
|
||||||
|
let html = r#"
|
||||||
|
<div>
|
||||||
|
<my-component></my-component>
|
||||||
|
</div>
|
||||||
|
"#
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
let dom = Dom::parse(html).unwrap();
|
||||||
|
|
||||||
|
let body = rsx_rosetta::rsx_from_html(&dom);
|
||||||
|
|
||||||
|
let out = dioxus_autofmt::write_block_out(body).unwrap();
|
||||||
|
|
||||||
|
let expected = r#"
|
||||||
|
div { my-component {} }"#;
|
||||||
|
pretty_assertions::assert_eq!(&out, &expected);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user