element.rs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. use super::*;
  2. use proc_macro2::TokenStream as TokenStream2;
  3. use quote::{quote, ToTokens, TokenStreamExt};
  4. use syn::{
  5. ext::IdentExt,
  6. parse::{discouraged::Speculative, Parse, ParseBuffer, ParseStream},
  7. token, Error, Expr, ExprClosure, Ident, LitStr, Result, Token,
  8. };
  9. // =======================================
  10. // Parse the VNode::Element type
  11. // =======================================
  12. pub struct Element {
  13. name: Ident,
  14. key: Option<AttrType>,
  15. attributes: Vec<ElementAttr>,
  16. listeners: Vec<ElementAttr>,
  17. children: Vec<Node>,
  18. is_static: bool,
  19. }
  20. impl ToTokens for Element {
  21. fn to_tokens(&self, tokens: &mut TokenStream2) {
  22. let name = &self.name;
  23. let attr = &self.attributes;
  24. let childs = &self.children;
  25. let listeners = &self.listeners;
  26. tokens.append_all(quote! {
  27. __cx.element(
  28. dioxus_elements::#name,
  29. __cx.bump().alloc([ #(#listeners),* ]),
  30. __cx.bump().alloc([ #(#attr),* ]),
  31. __cx.bump().alloc([ #(#childs),* ]),
  32. None,
  33. )
  34. });
  35. }
  36. }
  37. impl Parse for Element {
  38. fn parse(stream: ParseStream) -> Result<Self> {
  39. let name = Ident::parse(stream)?;
  40. // TODO: remove this in favor of the compile-time validation system
  41. if !crate::util::is_valid_tag(&name.to_string()) {
  42. return Err(Error::new(name.span(), "Not a valid Html tag"));
  43. }
  44. // parse the guts
  45. let content: ParseBuffer;
  46. syn::braced!(content in stream);
  47. let mut attributes: Vec<ElementAttr> = vec![];
  48. let mut listeners: Vec<ElementAttr> = vec![];
  49. let mut children: Vec<Node> = vec![];
  50. let mut key = None;
  51. 'parsing: loop {
  52. // [1] Break if empty
  53. if content.is_empty() {
  54. break 'parsing;
  55. }
  56. if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
  57. parse_element_body(
  58. &content,
  59. &mut attributes,
  60. &mut listeners,
  61. &mut key,
  62. name.clone(),
  63. )?;
  64. } else {
  65. children.push(content.parse::<Node>()?);
  66. }
  67. // consume comma if it exists
  68. // we don't actually care if there *are* commas after elements/text
  69. if content.peek(Token![,]) {
  70. let _ = content.parse::<Token![,]>();
  71. }
  72. }
  73. Ok(Self {
  74. key,
  75. name,
  76. attributes,
  77. children,
  78. listeners,
  79. is_static: false,
  80. })
  81. }
  82. }
  83. /// =======================================
  84. /// Parse a VElement's Attributes
  85. /// =======================================
  86. struct ElementAttr {
  87. element_name: Ident,
  88. name: Ident,
  89. value: AttrType,
  90. namespace: Option<String>,
  91. }
  92. enum AttrType {
  93. BumpText(LitStr),
  94. FieldTokens(Expr),
  95. EventTokens(Expr),
  96. Event(ExprClosure),
  97. }
  98. // We parse attributes and dump them into the attribute vec
  99. // This is because some tags might be namespaced (IE style)
  100. // These dedicated tags produce multiple name-spaced attributes
  101. fn parse_element_body(
  102. stream: ParseStream,
  103. attrs: &mut Vec<ElementAttr>,
  104. listeners: &mut Vec<ElementAttr>,
  105. key: &mut Option<AttrType>,
  106. element_name: Ident,
  107. ) -> Result<()> {
  108. let mut name = Ident::parse_any(stream)?;
  109. let name_str = name.to_string();
  110. stream.parse::<Token![:]>()?;
  111. // Return early if the field is a listener
  112. if name_str.starts_with("on") {
  113. // remove the "on" bit
  114. // name = Ident::new(&name_str.trim_start_matches("on"), name.span());
  115. let ty = if stream.peek(token::Brace) {
  116. let content;
  117. syn::braced!(content in stream);
  118. // Try to parse directly as a closure
  119. let fork = content.fork();
  120. if let Ok(event) = fork.parse::<ExprClosure>() {
  121. content.advance_to(&fork);
  122. AttrType::Event(event)
  123. } else {
  124. AttrType::EventTokens(content.parse()?)
  125. }
  126. } else {
  127. AttrType::Event(stream.parse()?)
  128. };
  129. listeners.push(ElementAttr {
  130. name,
  131. value: ty,
  132. namespace: None,
  133. element_name: element_name.clone(),
  134. });
  135. return Ok(());
  136. }
  137. let ty: AttrType = match name_str.as_str() {
  138. // short circuit early if style is using the special syntax
  139. "style" if stream.peek(token::Brace) => {
  140. let inner;
  141. syn::braced!(inner in stream);
  142. while !inner.is_empty() {
  143. let name = Ident::parse_any(&inner)?;
  144. inner.parse::<Token![:]>()?;
  145. let ty = if inner.peek(LitStr) {
  146. let rawtext = inner.parse::<LitStr>().unwrap();
  147. AttrType::BumpText(rawtext)
  148. } else {
  149. let toks = inner.parse::<Expr>()?;
  150. AttrType::FieldTokens(toks)
  151. };
  152. if inner.peek(Token![,]) {
  153. let _ = inner.parse::<Token![,]>();
  154. }
  155. attrs.push(ElementAttr {
  156. name,
  157. value: ty,
  158. namespace: Some("style".to_string()),
  159. element_name: element_name.clone(),
  160. });
  161. }
  162. return Ok(());
  163. }
  164. "key" => {
  165. *key = Some(AttrType::BumpText(stream.parse::<LitStr>()?));
  166. return Ok(());
  167. }
  168. "classes" => {
  169. todo!("custom class lsit not supported")
  170. }
  171. "namespace" => {
  172. todo!("custom namespace not supported")
  173. }
  174. "ref" => {
  175. todo!("NodeRefs are currently not supported! This is currently a reserved keyword.")
  176. }
  177. // Fall through
  178. _ => {
  179. if stream.peek(LitStr) {
  180. let rawtext = stream.parse::<LitStr>().unwrap();
  181. AttrType::BumpText(rawtext)
  182. } else {
  183. let toks = stream.parse::<Expr>()?;
  184. AttrType::FieldTokens(toks)
  185. }
  186. }
  187. };
  188. // consume comma if it exists
  189. // we don't actually care if there *are* commas between attrs
  190. if stream.peek(Token![,]) {
  191. let _ = stream.parse::<Token![,]>();
  192. }
  193. attrs.push(ElementAttr {
  194. name,
  195. value: ty,
  196. namespace: None,
  197. element_name,
  198. });
  199. Ok(())
  200. }
  201. impl ToTokens for ElementAttr {
  202. fn to_tokens(&self, tokens: &mut TokenStream2) {
  203. let el_name = &self.element_name;
  204. let name_str = self.name.to_string();
  205. let nameident = &self.name;
  206. let namespace = match &self.namespace {
  207. Some(t) => quote! { Some(#t) },
  208. None => quote! { None },
  209. };
  210. match &self.value {
  211. AttrType::BumpText(value) => tokens.append_all(quote! {
  212. dioxus_elements::#el_name.#nameident(__cx, format_args_f!(#value))
  213. }),
  214. // __cx.attr(#name, format_args_f!(#value), #namespace, false)
  215. //
  216. // AttrType::BumpText(value) => tokens.append_all(quote! {
  217. // __cx.attr(#name, format_args_f!(#value), #namespace, false)
  218. // }),
  219. AttrType::FieldTokens(exp) => tokens.append_all(quote! {
  220. dioxus_elements::#el_name.#nameident(__cx, #exp)
  221. }),
  222. // __cx.attr(#name_str, #exp, #namespace, false)
  223. // AttrType::FieldTokens(exp) => tokens.append_all(quote! {
  224. // dioxus_elements::#el_name.#nameident(__cx, format_args_f!(#value))
  225. // __cx.attr(#name_str, #exp, #namespace, false)
  226. // }),
  227. // todo: move event handlers on to the elements or onto the nodefactory
  228. AttrType::Event(event) => tokens.append_all(quote! {
  229. dioxus::events::on::#nameident(__cx, #event)
  230. }),
  231. AttrType::EventTokens(event) => tokens.append_all(quote! {
  232. dioxus::events::on::#nameident(__cx, #event)
  233. }),
  234. }
  235. }
  236. }