diff --git a/packages/yew-macro/src/html_tree/html_element.rs b/packages/yew-macro/src/html_tree/html_element.rs index 6e038d7041a..31545029995 100644 --- a/packages/yew-macro/src/html_tree/html_element.rs +++ b/packages/yew-macro/src/html_tree/html_element.rs @@ -8,7 +8,7 @@ use syn::spanned::Spanned; use syn::{Block, Expr, Ident, Lit, LitStr, Token}; use super::{HtmlChildrenTree, HtmlDashedName, TagTokens}; -use crate::props::{ClassesForm, ElementProps, Prop}; +use crate::props::{ClassesForm, ElementProps, Prop, PropDirective}; use crate::stringify::{Stringify, Value}; use crate::{non_capitalized_ascii, Peek, PeekValue}; @@ -139,13 +139,13 @@ impl ToTokens for HtmlElement { |Prop { label, value, - is_forced_attribute, + directive, .. }| { ( label.to_lit_str(), value.optimize_literals_tagged(), - *is_forced_attribute, + *directive, ) }, ); @@ -153,7 +153,7 @@ impl ToTokens for HtmlElement { |Prop { label, value, - is_forced_attribute, + directive, .. }| { let key = label.to_lit_str(); @@ -183,7 +183,7 @@ impl ToTokens for HtmlElement { }, ), }, - *is_forced_attribute, + *directive, )) }, ); @@ -215,7 +215,7 @@ impl ToTokens for HtmlElement { __yew_classes } }), - false, + None, )) } ClassesForm::Single(classes) => { @@ -227,7 +227,7 @@ impl ToTokens for HtmlElement { Some(( LitStr::new("class", lit.span()), Value::Static(quote! { #lit }), - false, + None, )) } } @@ -237,26 +237,29 @@ impl ToTokens for HtmlElement { Value::Dynamic(quote! { ::std::convert::Into::<::yew::html::Classes>::into(#classes) }), - false, + None, )) } } } }); + fn apply_as(directive: Option<&PropDirective>) -> TokenStream { + match directive { + Some(PropDirective::ApplyAsProperty(token)) => quote_spanned!(token.span()=> ::yew::virtual_dom::ApplyAttributeAs::Property), + None => quote!(::yew::virtual_dom::ApplyAttributeAs::Attribute), + } + } + /// Try to turn attribute list into a `::yew::virtual_dom::Attributes::Static` - fn try_into_static(src: &[(LitStr, Value, bool)]) -> Option { + fn try_into_static(src: &[(LitStr, Value, Option)]) -> Option { let mut kv = Vec::with_capacity(src.len()); - for (k, v, is_forced_attribute) in src.iter() { + for (k, v, directive) in src.iter() { let v = match v { Value::Static(v) => quote! { #v }, Value::Dynamic(_) => return None, }; - let apply_as = if *is_forced_attribute { - quote! { ::yew::virtual_dom::ApplyAttributeAs::Attribute } - } else { - quote! { ::yew::virtual_dom::ApplyAttributeAs::Property } - }; + let apply_as = apply_as(directive.as_ref()); kv.push(quote! { ( #k, #v, #apply_as ) }); } @@ -266,15 +269,11 @@ impl ToTokens for HtmlElement { let attrs = normal_attrs .chain(boolean_attrs) .chain(class_attr) - .collect::>(); + .collect::)>>(); try_into_static(&attrs).unwrap_or_else(|| { let keys = attrs.iter().map(|(k, ..)| quote! { #k }); - let values = attrs.iter().map(|(_, v, is_forced_attribute)| { - let apply_as = if *is_forced_attribute { - quote! { ::yew::virtual_dom::ApplyAttributeAs::Attribute } - } else { - quote! { ::yew::virtual_dom::ApplyAttributeAs::Property } - }; + let values = attrs.iter().map(|(_, v, directive)| { + let apply_as = apply_as(directive.as_ref()); let value = wrap_attr_value(v); quote! { ::std::option::Option::map(#value, |it| (it, #apply_as)) } }); diff --git a/packages/yew-macro/src/props/prop.rs b/packages/yew-macro/src/props/prop.rs index 5d840032ec2..7bb3c75b0ea 100644 --- a/packages/yew-macro/src/props/prop.rs +++ b/packages/yew-macro/src/props/prop.rs @@ -13,19 +13,24 @@ use super::CHILDREN_LABEL; use crate::html_tree::HtmlDashedName; use crate::stringify::Stringify; +#[derive(Copy, Clone)] +pub enum PropDirective { + ApplyAsProperty(Token![~]), +} + pub struct Prop { - pub is_forced_attribute: bool, + pub directive: Option, pub label: HtmlDashedName, /// Punctuation between `label` and `value`. pub value: Expr, } impl Parse for Prop { fn parse(input: ParseStream) -> syn::Result { - let at = input.parse::().map(|_| true).unwrap_or(false); + let directive = input.parse::().map(|parsed| PropDirective::ApplyAsProperty(parsed)).ok(); if input.peek(Brace) { - Self::parse_shorthand_prop_assignment(input, at) + Self::parse_shorthand_prop_assignment(input, directive) } else { - Self::parse_prop_assignment(input, at) + Self::parse_prop_assignment(input, directive) } } } @@ -37,7 +42,7 @@ impl Prop { /// an ambiguity in the syntax fn parse_shorthand_prop_assignment( input: ParseStream, - is_forced_attribute: bool, + directive: Option, ) -> syn::Result { let value; let _brace = braced!(value in input); @@ -67,12 +72,12 @@ impl Prop { Ok(Self { label, value: expr, - is_forced_attribute, + directive, }) } /// Parse a prop of the form `label={value}` - fn parse_prop_assignment(input: ParseStream, is_forced_attribute: bool) -> syn::Result { + fn parse_prop_assignment(input: ParseStream, directive: Option) -> syn::Result { let label = input.parse::()?; let equals = input.parse::().map_err(|_| { syn::Error::new_spanned( @@ -95,7 +100,7 @@ impl Prop { Ok(Self { label, value, - is_forced_attribute, + directive, }) } } @@ -118,10 +123,10 @@ fn parse_prop_value(input: &ParseBuffer) -> syn::Result { match &expr { Expr::Lit(_) => Ok(expr), - _ => Err(syn::Error::new_spanned( + ref exp => Err(syn::Error::new_spanned( &expr, - "the property value must be either a literal or enclosed in braces. Consider \ - adding braces around your expression.", + format!("the property value must be either a literal or enclosed in braces. Consider \ + adding braces around your expression.: {:#?}", exp), )), } } diff --git a/packages/yew-macro/src/props/prop_macro.rs b/packages/yew-macro/src/props/prop_macro.rs index 99e8948fb49..1ff4312276b 100644 --- a/packages/yew-macro/src/props/prop_macro.rs +++ b/packages/yew-macro/src/props/prop_macro.rs @@ -64,7 +64,7 @@ impl From for Prop { Prop { label, value, - is_forced_attribute: false, + directive: None, } } } diff --git a/packages/yew/src/dom_bundle/btag/attributes.rs b/packages/yew/src/dom_bundle/btag/attributes.rs index d8c0fe86f5b..451f274a53b 100644 --- a/packages/yew/src/dom_bundle/btag/attributes.rs +++ b/packages/yew/src/dom_bundle/btag/attributes.rs @@ -145,8 +145,7 @@ impl Attributes { Some(old) => old != new, None => true, } { - // todo: set property - el.set_attribute(k, new.0).unwrap(); + Self::set(&el, k, new.0, new.1); } } @@ -164,16 +163,9 @@ impl Attributes { el.set_attribute(key, value).expect("invalid attribute key") } ApplyAttributeAs::Property => { - match key { - // need to be attributes because, otherwise query selectors fail - "class" => el.set_attribute(key, value).expect("invalid attribute key"), - _ => { - let key = JsValue::from_str(key); - let value = JsValue::from_str(value); - js_sys::Reflect::set(el.as_ref(), &key, &value) - .expect("could not set property"); - } - } + let key = JsValue::from_str(key); + let value = JsValue::from_str(value); + js_sys::Reflect::set(el.as_ref(), &key, &value).expect("could not set property"); } } } @@ -184,17 +176,9 @@ impl Attributes { .remove_attribute(key) .expect("could not remove attribute"), ApplyAttributeAs::Property => { - match key { - // need to be attributes because, otherwise query selectors fail - "class" => el - .remove_attribute(key) - .expect("could not remove attribute"), - _ => { - let key = JsValue::from_str(key); - js_sys::Reflect::set(el.as_ref(), &key, &JsValue::UNDEFINED) - .expect("could not remove property"); - } - } + let key = JsValue::from_str(key); + js_sys::Reflect::set(el.as_ref(), &key, &JsValue::UNDEFINED) + .expect("could not remove property"); } } } @@ -375,7 +359,7 @@ mod tests { async fn macro_syntax_works() { #[function_component] fn Comp() -> Html { - html! { } + html! { } } let output = gloo::utils::document().get_element_by_id("output").unwrap(); @@ -383,15 +367,15 @@ mod tests { gloo::timers::future::sleep(Duration::from_secs(1)).await; let element = output.query_selector("a").unwrap().unwrap(); - assert_eq!(element.get_attribute("alt").unwrap(), "abc"); + assert_eq!(element.get_attribute("href").unwrap(), "https://example.com/"); assert_eq!( - Reflect::get(element.as_ref(), &JsValue::from_str("href")) - .expect("no href") + Reflect::get(element.as_ref(), &JsValue::from_str("alt")) + .expect("no alt") .as_string() .expect("not a string"), - "https://example.com/", - "property `href` not set properly" + "abc", + "property `alt` not set properly" ); } } diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 37e2f9637e2..f24a2db6931 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -242,7 +242,7 @@ impl From> for Attributes { fn from(map: IndexMap) -> Self { let v = map .into_iter() - .map(|(k, v)| (k, (v, ApplyAttributeAs::Property))) + .map(|(k, v)| (k, (v, ApplyAttributeAs::Attribute))) .collect(); Self::IndexMap(v) } @@ -252,7 +252,7 @@ impl From> for Attributes { fn from(v: IndexMap<&'static str, AttrValue>) -> Self { let v = v .into_iter() - .map(|(k, v)| (AttrValue::Static(k), (v, ApplyAttributeAs::Property))) + .map(|(k, v)| (AttrValue::Static(k), (v, ApplyAttributeAs::Attribute))) .collect(); Self::IndexMap(v) } diff --git a/packages/yew/src/virtual_dom/vtag.rs b/packages/yew/src/virtual_dom/vtag.rs index f9621fe59c5..a4e423a4f59 100644 --- a/packages/yew/src/virtual_dom/vtag.rs +++ b/packages/yew/src/virtual_dom/vtag.rs @@ -369,11 +369,10 @@ impl VTag { ); } - /// Adds a key-value pair to element's property + /// Set the given key as property on the element /// - /// Not everything works when it set as an property. We use workarounds for: - /// `class`, which is set as attribute. - pub fn set_property(&mut self, key: &'static str, value: impl Into) { + /// [`js_sys::Reflect`] is used for setting properties. + pub fn add_property(&mut self, key: &'static str, value: impl Into) { self.attributes.get_mut_index_map().insert( AttrValue::Static(key), (value.into(), ApplyAttributeAs::Property),