@@ -4,10 +4,10 @@ use quote::{quote, quote_spanned, ToTokens};
44use syn:: buffer:: Cursor ;
55use syn:: parse:: { Parse , ParseStream } ;
66use syn:: spanned:: Spanned ;
7- use syn:: { Expr , Ident , Lit , LitStr , Token } ;
7+ use syn:: { Expr , ExprLit , Ident , Lit , LitStr , Token } ;
88
99use super :: { HtmlChildrenTree , HtmlDashedName , TagTokens } ;
10- use crate :: props:: { ElementProps , Prop , PropDirective } ;
10+ use crate :: props:: { ElementProps , Prop , PropDirective , PropLabel } ;
1111use crate :: stringify:: { Stringify , Value } ;
1212use crate :: { is_ide_completion, non_capitalized_ascii, Peek , PeekValue } ;
1313
@@ -198,6 +198,30 @@ impl ToTokens for HtmlElement {
198198 // other attributes
199199
200200 let attributes = {
201+ #[ derive( Clone ) ]
202+ enum Key {
203+ Static ( LitStr ) ,
204+ Dynamic ( Expr ) ,
205+ }
206+
207+ impl From < & PropLabel > for Key {
208+ fn from ( value : & PropLabel ) -> Self {
209+ match value {
210+ PropLabel :: Static ( dashed_name) => Self :: Static ( dashed_name. to_lit_str ( ) ) ,
211+ PropLabel :: Dynamic ( expr) => Self :: Dynamic ( expr. clone ( ) ) ,
212+ }
213+ }
214+ }
215+
216+ impl ToTokens for Key {
217+ fn to_tokens ( & self , tokens : & mut TokenStream ) {
218+ match self {
219+ Key :: Static ( dashed_name) => dashed_name. to_tokens ( tokens) ,
220+ Key :: Dynamic ( expr) => expr. to_tokens ( tokens) ,
221+ }
222+ }
223+ }
224+
201225 let normal_attrs = attributes. iter ( ) . map (
202226 |Prop {
203227 label,
@@ -206,7 +230,7 @@ impl ToTokens for HtmlElement {
206230 ..
207231 } | {
208232 (
209- label . to_lit_str ( ) ,
233+ Key :: from ( label ) ,
210234 value. optimize_literals_tagged ( ) ,
211235 * directive,
212236 )
@@ -219,26 +243,30 @@ impl ToTokens for HtmlElement {
219243 directive,
220244 ..
221245 } | {
222- let key = label. to_lit_str ( ) ;
246+ let key = Key :: from ( label) ;
247+ let lit = match & key {
248+ Key :: Static ( lit) => lit,
249+ Key :: Dynamic ( _) => unreachable ! ( ) ,
250+ } ;
223251 Some ( (
224252 key. clone ( ) ,
225253 match value {
226254 Expr :: Lit ( e) => match & e. lit {
227255 Lit :: Bool ( b) => Value :: Static ( if b. value {
228- quote ! { #key }
256+ quote ! { #lit }
229257 } else {
230258 return None ;
231259 } ) ,
232260 _ => Value :: Dynamic ( quote_spanned ! { value. span( ) => {
233261 :: yew:: utils:: __ensure_type:: <:: std:: primitive:: bool >( #value) ;
234- #key
262+ #lit
235263 } } ) ,
236264 } ,
237265 expr => Value :: Dynamic (
238266 quote_spanned ! { expr. span( ) . resolved_at( Span :: call_site( ) ) =>
239267 if #expr {
240268 :: std:: option:: Option :: Some (
241- :: yew:: virtual_dom:: AttrValue :: Static ( #key )
269+ :: yew:: virtual_dom:: AttrValue :: Static ( #lit )
242270 )
243271 } else {
244272 :: std:: option:: Option :: None
@@ -260,7 +288,7 @@ impl ToTokens for HtmlElement {
260288 None
261289 } else {
262290 Some ( (
263- LitStr :: new ( "class" , lit. span ( ) ) ,
291+ Key :: Static ( LitStr :: new ( "class" , lit. span ( ) ) ) ,
264292 Value :: Static ( quote ! { #lit } ) ,
265293 None ,
266294 ) )
@@ -269,7 +297,7 @@ impl ToTokens for HtmlElement {
269297 None => {
270298 let expr = & classes. value ;
271299 Some ( (
272- LitStr :: new ( "class" , classes. label . span ( ) ) ,
300+ Key :: Static ( LitStr :: new ( "class" , classes. label . span ( ) ) ) ,
273301 Value :: Dynamic ( quote ! {
274302 :: std:: convert:: Into :: <:: yew:: html:: Classes >:: into( #expr)
275303 } ) ,
@@ -279,15 +307,13 @@ impl ToTokens for HtmlElement {
279307 } ) ;
280308
281309 /// Try to turn attribute list into a `::yew::virtual_dom::Attributes::Static`
282- fn try_into_static (
283- src : & [ ( LitStr , Value , Option < PropDirective > ) ] ,
284- ) -> Option < TokenStream > {
285- if src
286- . iter ( )
287- . any ( |( _, _, d) | matches ! ( d, Some ( PropDirective :: ApplyAsProperty ( _) ) ) )
288- {
310+ fn try_into_static ( src : & [ ( Key , Value , Option < PropDirective > ) ] ) -> Option < TokenStream > {
311+ if src. iter ( ) . any ( |( k, _, d) | {
312+ matches ! ( k, Key :: Dynamic ( _) )
313+ || matches ! ( d, Some ( PropDirective :: ApplyAsProperty ( _) ) )
314+ } ) {
289315 // don't try to make a static attribute list if there are any properties to
290- // assign
316+ // assign or any labels are dynamic
291317 return None ;
292318 }
293319 let mut kv = Vec :: with_capacity ( src. len ( ) ) ;
@@ -312,13 +338,24 @@ impl ToTokens for HtmlElement {
312338 Some ( quote ! { :: yew:: virtual_dom:: Attributes :: Static ( & [ #( #kv) , * ] ) } )
313339 }
314340
315- let attrs = normal_attrs
316- . chain ( boolean_attrs)
317- . chain ( class_attr)
318- . collect :: < Vec < ( LitStr , Value , Option < PropDirective > ) > > ( ) ;
319- try_into_static ( & attrs) . unwrap_or_else ( || {
320- let keys = attrs. iter ( ) . map ( |( k, ..) | quote ! { #k } ) ;
321- let values = attrs. iter ( ) . map ( |( _, v, directive) | {
341+ /// Try to turn attribute list into a `::yew::virtual_dom::Attributes::Dynamic`
342+ fn try_into_dynamic (
343+ src : & [ ( Key , Value , Option < PropDirective > ) ] ,
344+ ) -> Option < TokenStream > {
345+ if src. iter ( ) . any ( |( k, ..) | {
346+ !matches ! (
347+ k,
348+ Key :: Dynamic ( Expr :: Lit ( ExprLit {
349+ lit: Lit :: Str ( _) ,
350+ ..
351+ } ) ) | Key :: Static ( _)
352+ )
353+ } ) {
354+ // use IndexMap if there are any dynamic-expr labels
355+ return None ;
356+ }
357+ let keys = src. iter ( ) . map ( |( k, ..) | quote ! { #k } ) ;
358+ let values = src. iter ( ) . map ( |( _, v, directive) | {
322359 let value = match directive {
323360 Some ( PropDirective :: ApplyAsProperty ( token) ) => {
324361 quote_spanned ! ( token. span( ) => :: std:: option:: Option :: Some (
@@ -336,11 +373,49 @@ impl ToTokens for HtmlElement {
336373 } ;
337374 quote ! { #value }
338375 } ) ;
339- quote ! {
376+ Some ( quote ! {
340377 :: yew:: virtual_dom:: Attributes :: Dynamic {
341378 keys: & [ #( #keys) , * ] ,
342379 values: :: std:: boxed:: Box :: new( [ #( #values) , * ] ) ,
343380 }
381+ } )
382+ }
383+
384+ let attrs = normal_attrs
385+ . chain ( boolean_attrs)
386+ . chain ( class_attr)
387+ . collect :: < Vec < ( Key , Value , Option < PropDirective > ) > > ( ) ;
388+ try_into_static ( & attrs) . or_else ( || try_into_dynamic ( & attrs) ) . unwrap_or_else ( || {
389+ let results = attrs. iter ( )
390+ . map ( |( k, v, directive) | {
391+ let value = match directive {
392+ Some ( PropDirective :: ApplyAsProperty ( token) ) => {
393+ quote_spanned ! ( token. span( ) => :: std:: option:: Option :: Some (
394+ :: yew:: virtual_dom:: AttributeOrProperty :: Property (
395+ :: std:: convert:: Into :: into( #v)
396+ ) )
397+ )
398+ }
399+ None => {
400+ let value = wrap_attr_value ( v) ;
401+ quote ! {
402+ :: std:: option:: Option :: map( #value, :: yew:: virtual_dom:: AttributeOrProperty :: Attribute )
403+ }
404+ } ,
405+ } ;
406+ quote ! { ( :: std:: convert:: Into :: into( #k) , #value) }
407+ } ) ;
408+ quote ! {
409+ :: yew:: virtual_dom:: Attributes :: IndexMap (
410+ :: std:: rc:: Rc :: new(
411+ :: std:: iter:: Iterator :: collect(
412+ :: std:: iter:: Iterator :: filter_map(
413+ :: std:: iter:: IntoIterator :: into_iter( [ #( #results) , * ] ) ,
414+ |( k, v) | v. map( |v| ( k, v) )
415+ )
416+ )
417+ )
418+ )
344419 }
345420 } )
346421 } ;
@@ -349,7 +424,8 @@ impl ToTokens for HtmlElement {
349424 quote ! { :: yew:: virtual_dom:: listeners:: Listeners :: None }
350425 } else {
351426 let listeners_it = listeners. iter ( ) . map ( |Prop { label, value, .. } | {
352- let name = & label. name ;
427+ // TODO: consider making a `ListenerProp` that has dashed name's name and value
428+ let name = & <& HtmlDashedName >:: try_from ( label) . unwrap ( ) . name ;
353429 quote ! {
354430 :: yew:: html:: #name:: Wrapper :: __macro_new( #value)
355431 }
0 commit comments