element.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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<const AS: HtmlOrRsx> {
  13. name: Ident,
  14. key: Option<LitStr>,
  15. attributes: Vec<ElementAttr<AS>>,
  16. listeners: Vec<ElementAttr<AS>>,
  17. children: Vec<BodyNode<AS>>,
  18. _is_static: bool,
  19. }
  20. impl Parse for Element<AS_RSX> {
  21. fn parse(stream: ParseStream) -> Result<Self> {
  22. let name = Ident::parse(stream)?;
  23. // parse the guts
  24. let content: ParseBuffer;
  25. syn::braced!(content in stream);
  26. let mut attributes: Vec<ElementAttr<AS_RSX>> = vec![];
  27. let mut listeners: Vec<ElementAttr<AS_RSX>> = vec![];
  28. let mut children: Vec<BodyNode<AS_RSX>> = vec![];
  29. let mut key = None;
  30. let mut el_ref = None;
  31. 'parsing: loop {
  32. // [1] Break if empty
  33. if content.is_empty() {
  34. break 'parsing;
  35. }
  36. if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
  37. parse_rsx_element_field(
  38. &content,
  39. &mut attributes,
  40. &mut listeners,
  41. &mut key,
  42. &mut el_ref,
  43. name.clone(),
  44. )?;
  45. } else {
  46. children.push(content.parse::<BodyNode<AS_RSX>>()?);
  47. }
  48. // consume comma if it exists
  49. // we don't actually care if there *are* commas after elements/text
  50. if content.peek(Token![,]) {
  51. let _ = content.parse::<Token![,]>();
  52. }
  53. }
  54. Ok(Self {
  55. key,
  56. name,
  57. attributes,
  58. children,
  59. listeners,
  60. _is_static: false,
  61. })
  62. }
  63. }
  64. impl Parse for Element<AS_HTML> {
  65. fn parse(stream: ParseStream) -> Result<Self> {
  66. let _l_tok = stream.parse::<Token![<]>()?;
  67. let el_name = Ident::parse(stream)?;
  68. let mut attributes: Vec<ElementAttr<AS_HTML>> = vec![];
  69. let mut listeners: Vec<ElementAttr<AS_HTML>> = vec![];
  70. let mut children: Vec<BodyNode<AS_HTML>> = vec![];
  71. let key = None;
  72. while !stream.peek(Token![>]) {
  73. // self-closing
  74. if stream.peek(Token![/]) {
  75. stream.parse::<Token![/]>()?;
  76. stream.parse::<Token![>]>()?;
  77. return Ok(Self {
  78. name: el_name,
  79. key: None,
  80. attributes,
  81. _is_static: false,
  82. listeners,
  83. children,
  84. });
  85. }
  86. let name = Ident::parse_any(stream)?;
  87. let name_str = name.to_string();
  88. stream.parse::<Token![=]>()?;
  89. if name_str.starts_with("on") {
  90. let inner;
  91. syn::braced!(inner in stream);
  92. let toks = inner.parse::<Expr>()?;
  93. let ty = AttrType::EventTokens(toks);
  94. listeners.push(ElementAttr {
  95. element_name: el_name.clone(),
  96. name,
  97. value: ty,
  98. namespace: None,
  99. })
  100. } else {
  101. match name_str.as_str() {
  102. "style" => {}
  103. "key" => {}
  104. _ => {
  105. // "classes" | "namespace" | "ref" | _ => {
  106. let ty = if stream.peek(LitStr) {
  107. let rawtext = stream.parse::<LitStr>().unwrap();
  108. AttrType::BumpText(rawtext)
  109. } else {
  110. // like JSX, we expect raw expressions
  111. let inner;
  112. syn::braced!(inner in stream);
  113. let toks = inner.parse::<Expr>()?;
  114. AttrType::FieldTokens(toks)
  115. };
  116. attributes.push(ElementAttr {
  117. element_name: el_name.clone(),
  118. name,
  119. value: ty,
  120. namespace: None,
  121. })
  122. }
  123. }
  124. };
  125. }
  126. stream.parse::<Token![>]>()?;
  127. 'parsing: loop {
  128. if stream.peek(Token![<]) && stream.peek2(Token![/]) {
  129. break 'parsing;
  130. }
  131. // [1] Break if empty
  132. if stream.is_empty() {
  133. break 'parsing;
  134. }
  135. children.push(stream.parse::<BodyNode<AS_HTML>>()?);
  136. }
  137. // closing element
  138. stream.parse::<Token![<]>()?;
  139. stream.parse::<Token![/]>()?;
  140. let close = Ident::parse_any(stream)?;
  141. if close != el_name {
  142. return Err(Error::new_spanned(
  143. close,
  144. "closing element does not match opening",
  145. ));
  146. }
  147. stream.parse::<Token![>]>()?;
  148. Ok(Self {
  149. key,
  150. name: el_name,
  151. attributes,
  152. children,
  153. listeners,
  154. _is_static: false,
  155. })
  156. }
  157. }
  158. impl<const AS: HtmlOrRsx> ToTokens for Element<AS> {
  159. fn to_tokens(&self, tokens: &mut TokenStream2) {
  160. let name = &self.name;
  161. let attr = &self.attributes;
  162. let childs = &self.children;
  163. let listeners = &self.listeners;
  164. let key = match &self.key {
  165. Some(ty) => quote! { Some(format_args_f!(#ty)) },
  166. None => quote! { None },
  167. };
  168. tokens.append_all(quote! {
  169. __cx.element(
  170. dioxus_elements::#name,
  171. [ #(#listeners),* ],
  172. [ #(#attr),* ],
  173. [ #(#childs),* ],
  174. #key,
  175. )
  176. });
  177. }
  178. }
  179. /// =======================================
  180. /// Parse a VElement's Attributes
  181. /// =======================================
  182. struct ElementAttr<const AS: HtmlOrRsx> {
  183. element_name: Ident,
  184. name: Ident,
  185. value: AttrType,
  186. namespace: Option<String>,
  187. }
  188. enum AttrType {
  189. BumpText(LitStr),
  190. FieldTokens(Expr),
  191. EventTokens(Expr),
  192. Event(ExprClosure),
  193. }
  194. // We parse attributes and dump them into the attribute vec
  195. // This is because some tags might be namespaced (IE style)
  196. // These dedicated tags produce multiple name-spaced attributes
  197. fn parse_rsx_element_field(
  198. stream: ParseStream,
  199. attrs: &mut Vec<ElementAttr<AS_RSX>>,
  200. listeners: &mut Vec<ElementAttr<AS_RSX>>,
  201. key: &mut Option<LitStr>,
  202. el_ref: &mut Option<Expr>,
  203. element_name: Ident,
  204. ) -> Result<()> {
  205. let name = Ident::parse_any(stream)?;
  206. let name_str = name.to_string();
  207. stream.parse::<Token![:]>()?;
  208. // Return early if the field is a listener
  209. if name_str.starts_with("on") {
  210. // remove the "on" bit
  211. let ty = if stream.peek(token::Brace) {
  212. let content;
  213. syn::braced!(content in stream);
  214. // Try to parse directly as a closure
  215. let fork = content.fork();
  216. if let Ok(event) = fork.parse::<ExprClosure>() {
  217. content.advance_to(&fork);
  218. AttrType::Event(event)
  219. } else {
  220. AttrType::EventTokens(content.parse()?)
  221. }
  222. } else {
  223. AttrType::Event(stream.parse()?)
  224. };
  225. listeners.push(ElementAttr {
  226. name,
  227. value: ty,
  228. namespace: None,
  229. element_name,
  230. });
  231. return Ok(());
  232. }
  233. let ty: AttrType = match name_str.as_str() {
  234. // short circuit early if style is using the special syntax
  235. "style" if stream.peek(token::Brace) => {
  236. let inner;
  237. syn::braced!(inner in stream);
  238. while !inner.is_empty() {
  239. let name = Ident::parse_any(&inner)?;
  240. inner.parse::<Token![:]>()?;
  241. let ty = if inner.peek(LitStr) {
  242. let rawtext = inner.parse::<LitStr>().unwrap();
  243. AttrType::BumpText(rawtext)
  244. } else {
  245. let toks = inner.parse::<Expr>()?;
  246. AttrType::FieldTokens(toks)
  247. };
  248. if inner.peek(Token![,]) {
  249. let _ = inner.parse::<Token![,]>();
  250. }
  251. attrs.push(ElementAttr {
  252. name,
  253. value: ty,
  254. namespace: Some("style".to_string()),
  255. element_name: element_name.clone(),
  256. });
  257. }
  258. return Ok(());
  259. }
  260. "key" => {
  261. *key = Some(stream.parse::<LitStr>()?);
  262. return Ok(());
  263. }
  264. "classes" => {
  265. todo!("custom class list not supported")
  266. }
  267. "namespace" => {
  268. todo!("custom namespace not supported")
  269. }
  270. "node_ref" => {
  271. *el_ref = Some(stream.parse::<Expr>()?);
  272. return Ok(());
  273. }
  274. // Fall through
  275. _ => {
  276. if stream.peek(LitStr) {
  277. let rawtext = stream.parse::<LitStr>().unwrap();
  278. AttrType::BumpText(rawtext)
  279. } else {
  280. let toks = stream.parse::<Expr>()?;
  281. AttrType::FieldTokens(toks)
  282. }
  283. }
  284. };
  285. // consume comma if it exists
  286. // we don't actually care if there *are* commas between attrs
  287. if stream.peek(Token![,]) {
  288. let _ = stream.parse::<Token![,]>();
  289. }
  290. attrs.push(ElementAttr {
  291. name,
  292. value: ty,
  293. namespace: None,
  294. element_name,
  295. });
  296. Ok(())
  297. }
  298. impl<const AS: HtmlOrRsx> ToTokens for ElementAttr<AS> {
  299. fn to_tokens(&self, tokens: &mut TokenStream2) {
  300. let el_name = &self.element_name;
  301. let nameident = &self.name;
  302. // TODO: wire up namespace
  303. let _name_str = self.name.to_string();
  304. let _namespace = match &self.namespace {
  305. Some(t) => quote! { Some(#t) },
  306. None => quote! { None },
  307. };
  308. match &self.value {
  309. AttrType::BumpText(value) => tokens.append_all(quote! {
  310. dioxus_elements::#el_name.#nameident(__cx, format_args_f!(#value))
  311. }),
  312. AttrType::FieldTokens(exp) => tokens.append_all(quote! {
  313. dioxus_elements::#el_name.#nameident(__cx, #exp)
  314. }),
  315. AttrType::Event(event) => tokens.append_all(quote! {
  316. dioxus::events::on::#nameident(__cx, #event)
  317. }),
  318. AttrType::EventTokens(event) => tokens.append_all(quote! {
  319. dioxus::events::on::#nameident(__cx, #event)
  320. }),
  321. }
  322. }
  323. }