@ -7,7 +7,6 @@ import { b, x } from 'code-red';
import Expression from '../../../nodes/shared/Expression' ;
import Expression from '../../../nodes/shared/Expression' ;
import Text from '../../../nodes/Text' ;
import Text from '../../../nodes/Text' ;
import { changed } from '../shared/changed' ;
import { changed } from '../shared/changed' ;
import { Literal } from 'estree' ;
export default class AttributeWrapper {
export default class AttributeWrapper {
node : Attribute ;
node : Attribute ;
@ -72,89 +71,81 @@ export default class AttributeWrapper {
const is_legacy_input_type = element . renderer . component . compile_options . legacy && name === 'type' && this . parent . node . name === 'input' ;
const is_legacy_input_type = element . renderer . component . compile_options . legacy && name === 'type' && this . parent . node . name === 'input' ;
const dependencies = this . node . get_dependencies ( ) ;
const dependencies = this . node . get_dependencies ( ) ;
if ( dependencies . length > 0 ) {
const value = this . get_value ( block ) ;
let value ;
// TODO some of this code is repeated in Tag.ts — would be good to
// DRY it out if that's possible without introducing crazy indirection
if ( this . node . chunks . length === 1 ) {
// single {tag} — may be a non-string
value = ( this . node . chunks [ 0 ] as Expression ) . manipulate ( block ) ;
} else {
value = this . node . name === 'class'
? this . get_class_name_text ( )
: this . render_chunks ( ) . reduce ( ( lhs , rhs ) = > x ` ${ lhs } + ${ rhs } ` ) ;
// '{foo} {bar}' — treat as string concatenation
if ( this . node . chunks [ 0 ] . type !== 'Text' ) {
value = x ` "" + ${ value } ` ;
}
}
const is_select_value_attribute =
const is_src = this . node . name === 'src' ; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
name === 'value' && element . node . name === 'select' ;
const is_select_value_attribute =
name === 'value' && element . node . name === 'select' ;
const should_cache = is_select_value_attribute ; // TODO is this necessary?
const should_cache = is_src || this . node . should_cache ( ) || is_select_value_attribute ; // TODO is this necessary?
const last = should_cache && block . get_unique_name (
const last = should_cache && block . get_unique_name (
` ${ element . var . name } _ ${ name . replace ( /[^a-zA-Z_$]/g , '_' ) } _value `
` ${ element . var . name } _ ${ name . replace ( /[^a-zA-Z_$]/g , '_' ) } _value `
) ;
) ;
if ( should_cache ) block . add_variable ( last ) ;
if ( should_cache ) block . add_variable ( last ) ;
let updater ;
let updater ;
const init = should_cache ? x ` ${ last } = ${ value } ` : value ;
const init = should_cache ? x ` ${ last } = ${ value } ` : value ;
if ( is_legacy_input_type ) {
if ( is_legacy_input_type ) {
block . chunks . hydrate . push (
block . chunks . hydrate . push (
b ` @set_input_type( ${ element . var } , ${ init } ); `
b ` @set_input_type( ${ element . var } , ${ init } ); `
) ;
) ;
updater = b ` @set_input_type( ${ element . var } , ${ should_cache ? last : value } ); ` ;
updater = b ` @set_input_type( ${ element . var } , ${ should_cache ? last : value } ); ` ;
} else if ( is_select_value_attribute ) {
} else if ( is_select_value_attribute ) {
// annoying special case
// annoying special case
const is_multiple_select = element . node . get_static_attribute_value ( 'multiple' ) ;
const is_multiple_select = element . node . get_static_attribute_value ( 'multiple' ) ;
const i = block . get_unique_name ( 'i' ) ;
const i = block . get_unique_name ( 'i' ) ;
const option = block . get_unique_name ( 'option' ) ;
const option = block . get_unique_name ( 'option' ) ;
const if_statement = is_multiple_select
const if_statement = is_multiple_select
? b `
? b `
$ { option } . selected = ~ $ { last } . indexOf ( $ { option } . __value ) ; `
$ { option } . selected = ~ $ { last } . indexOf ( $ { option } . __value ) ; `
: b `
: b `
if ( $ { option } . __value === $ { last } ) {
if ( $ { option } . __value === $ { last } ) {
$ { option } . selected = true ;
$ { option } . selected = true ;
$ { { type : 'BreakStatement' } } ;
$ { { type : 'BreakStatement' } } ;
} ` ; // TODO the BreakStatement is gross, but it's unsyntactic otherwise...
} ` ; // TODO the BreakStatement is gross, but it's unsyntactic otherwise...
updater = b `
updater = b `
for ( var $ { i } = 0 ; $ { i } < $ { element . var } . options . length ; $ { i } += 1 ) {
for ( var $ { i } = 0 ; $ { i } < $ { element . var } . options . length ; $ { i } += 1 ) {
var $ { option } = $ { element . var } . options [ $ { i } ] ;
var $ { option } = $ { element . var } . options [ $ { i } ] ;
$ { if_statement }
$ { if_statement }
}
}
` ;
` ;
block . chunks . mount . push ( b `
block . chunks . mount . push ( b `
$ { last } = $ { value } ;
$ { last } = $ { value } ;
$ { updater }
$ { updater }
` );
` );
} else if ( property_name ) {
} else if ( is_src ) {
block . chunks . hydrate . push (
block . chunks . hydrate . push (
b ` ${ element . var } . ${ property_name } = ${ init } ; `
b ` if ( ${ element . var } .src !== ${ init } ) ${ method } ( ${ element . var } , " ${ name } ", ${ last } ); `
) ;
) ;
updater = block . renderer . options . dev
updater = b ` ${ method } ( ${ element . var } , " ${ name } ", ${ should_cache ? last : value } ); ` ;
? b ` @prop_dev( ${ element . var } , " ${ property_name } ", ${ should_cache ? last : value } ); `
} else if ( property_name ) {
: b ` ${ element . var } . ${ property_name } = ${ should_cache ? last : value } ; ` ;
block . chunks . hydrate . push (
} else {
b ` ${ element . var } . ${ property_name } = ${ init } ; `
block . chunks . hydrate . push (
) ;
b ` ${ method } ( ${ element . var } , " ${ name } ", ${ init } ); `
updater = block . renderer . options . dev
) ;
? b ` @prop_dev( ${ element . var } , " ${ property_name } ", ${ should_cache ? last : value } ); `
updater = b ` ${ method } ( ${ element . var } , " ${ name } ", ${ should_cache ? last : value } ); ` ;
: b ` ${ element . var } . ${ property_name } = ${ should_cache ? last : value } ; ` ;
}
} else {
block . chunks . hydrate . push (
b ` ${ method } ( ${ element . var } , " ${ name } ", ${ init } ); `
) ;
updater = b ` ${ method } ( ${ element . var } , " ${ name } ", ${ should_cache ? last : value } ); ` ;
}
if ( dependencies . length > 0 ) {
let condition = changed ( dependencies ) ;
let condition = changed ( dependencies ) ;
if ( should_cache ) {
if ( should_cache ) {
condition = x ` ${ condition } && ( ${ last } !== ( ${ last } = ${ value } )) ` ;
condition = is_src
? x ` ${ condition } && ( ${ element . var } .src !== ( ${ last } = ${ value } )) `
: x ` ${ condition } && ( ${ last } !== ( ${ last } = ${ value } )) ` ;
}
}
if ( block . has_outros ) {
if ( block . has_outros ) {
@ -165,23 +156,11 @@ export default class AttributeWrapper {
if ( $ { condition } ) {
if ( $ { condition } ) {
$ { updater }
$ { updater }
} ` );
} ` );
} else {
}
const value = this . node . get_value ( block ) ;
const statement = (
is_legacy_input_type
? b ` @set_input_type( ${ element . var } , ${ value } ); `
: property_name
? b ` ${ element . var } . ${ property_name } = ${ value } ; `
: b ` ${ method } ( ${ element . var } , " ${ name } ", ${ value . type === 'Literal' && ( value as Literal ) . value === true ? x ` "" ` : value } ); `
) ;
block . chunks . hydrate . push ( statement ) ;
// special case – autofocus. has to be handled in a bit of a weird way
// special case – autofocus. has to be handled in a bit of a weird way
if ( this . node . is_true && name === 'autofocus' ) {
if ( this . node . is_true && name === 'autofocus' ) {
block . autofocus = element . var ;
block . autofocus = element . var ;
}
}
}
if ( is_indirectly_bound_value ) {
if ( is_indirectly_bound_value ) {
@ -199,6 +178,36 @@ export default class AttributeWrapper {
return metadata ;
return metadata ;
}
}
get_value ( block ) {
if ( this . node . is_true ) {
const metadata = this . get_metadata ( ) ;
if ( metadata && boolean_attribute . has ( metadata . property_name . toLowerCase ( ) ) ) {
return x ` true ` ;
}
return x ` "" ` ;
}
if ( this . node . chunks . length === 0 ) return x ` "" ` ;
// TODO some of this code is repeated in Tag.ts — would be good to
// DRY it out if that's possible without introducing crazy indirection
if ( this . node . chunks . length === 1 ) {
return this . node . chunks [ 0 ] . type === 'Text'
? string_literal ( ( this . node . chunks [ 0 ] as Text ) . data )
: ( this . node . chunks [ 0 ] as Expression ) . manipulate ( block ) ;
}
let value = this . node . name === 'class'
? this . get_class_name_text ( )
: this . render_chunks ( ) . reduce ( ( lhs , rhs ) = > x ` ${ lhs } + ${ rhs } ` ) ;
// '{foo} {bar}' — treat as string concatenation
if ( this . node . chunks [ 0 ] . type !== 'Text' ) {
value = x ` "" + ${ value } ` ;
}
return value ;
}
get_class_name_text() {
get_class_name_text() {
const scoped_css = this . node . chunks . some ( ( chunk : Text ) = > chunk . synthetic ) ;
const scoped_css = this . node . chunks . some ( ( chunk : Text ) = > chunk . synthetic ) ;
const rendered = this . render_chunks ( ) ;
const rendered = this . render_chunks ( ) ;
@ -292,3 +301,32 @@ Object.keys(attribute_lookup).forEach(name => {
const metadata = attribute_lookup [ name ] ;
const metadata = attribute_lookup [ name ] ;
if ( ! metadata . property_name ) metadata . property_name = name ;
if ( ! metadata . property_name ) metadata . property_name = name ;
} ) ;
} ) ;
// source: https://html.spec.whatwg.org/multipage/indices.html
const boolean_attribute = new Set ( [
'allowfullscreen' ,
'allowpaymentrequest' ,
'async' ,
'autofocus' ,
'autoplay' ,
'checked' ,
'controls' ,
'default' ,
'defer' ,
'disabled' ,
'formnovalidate' ,
'hidden' ,
'ismap' ,
'itemscope' ,
'loop' ,
'multiple' ,
'muted' ,
'nomodule' ,
'novalidate' ,
'open' ,
'playsinline' ,
'readonly' ,
'required' ,
'reversed' ,
'selected'
] ) ;