@ -7,8 +7,9 @@ import { b, x } from 'code-red';
import Expression from '../../../nodes/shared/Expression' ;
import Text from '../../../nodes/Text' ;
import handle_select_value_binding from './handle_select_value_binding' ;
import { Identifier , Node } from 'estree' ;
export default class AttributeWrapper {
export class Base AttributeWrapper {
node : Attribute ;
parent : ElementWrapper ;
@ -21,7 +22,29 @@ export default class AttributeWrapper {
parent . not_static_content ( ) ;
block . add_dependencies ( node . dependencies ) ;
}
}
render ( _block : Block ) { }
}
export default class AttributeWrapper extends BaseAttributeWrapper {
node : Attribute ;
parent : ElementWrapper ;
metadata : any ;
name : string ;
property_name : string ;
is_indirectly_bound_value : boolean ;
is_src : boolean ;
is_select_value_attribute : boolean ;
is_input_value : boolean ;
should_cache : boolean ;
last : Identifier ;
constructor ( parent : ElementWrapper , block : Block , node : Attribute ) {
super ( parent , block , node ) ;
if ( node . dependencies . size > 0 ) {
// special case — <option value={foo}> — see below
if ( this . parent . node . name === 'option' && node . name === 'value' ) {
let select : ElementWrapper = this . parent ;
@ -42,31 +65,22 @@ export default class AttributeWrapper {
handle_select_value_binding ( this , node . dependencies ) ;
}
}
}
is_indirectly_bound_value() {
const element = this . parent ;
const name = fix_attribute_casing ( this . node . name ) ;
return name === 'value' &&
( element . node . name === 'option' || // TODO check it's actually bound
( element . node . name === 'input' &&
element . node . bindings . some (
( binding ) = >
/checked|group/ . test ( binding . name )
) ) ) ;
this . name = fix_attribute_casing ( this . node . name ) ;
this . metadata = this . get_metadata ( ) ;
this . is_indirectly_bound_value = is_indirectly_bound_value ( this ) ;
this. property_name = this . is_indirectly_bound_value
? '__value'
: this . metadata && this . metadata . property_name ;
this . is_src = this . name === 'src' ; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
this . is_select_value_attribute = this . name === 'value' && this . parent . node . name === 'select' ;
this . is_input_value = this . name === 'value' && this . parent . node . name === 'input' ;
this . should_cache = should_cache ( this ) ;
}
render ( block : Block ) {
const element = this . parent ;
const name = fix_attribute_casing ( this . node . name ) ;
const metadata = this . get_metadata ( ) ;
const is_indirectly_bound_value = this . is_indirectly_bound_value ( ) ;
const property_name = is_indirectly_bound_value
? '__value'
: metadata && metadata . property_name ;
const { name , property_name , should_cache , is_indirectly_bound_value } = this ;
// xlink is a special case... we could maybe extend this to generic
// namespaced attributes but I'm not sure that's applicable in
@ -82,29 +96,15 @@ export default class AttributeWrapper {
const dependencies = this . get_dependencies ( ) ;
const value = this . get_value ( block ) ;
const is_src = this . node . name === 'src' ; // TODO retire this exception in favour of https://github.com/sveltejs/svelte/issues/3750
const is_select_value_attribute =
name === 'value' && element . node . name === 'select' ;
const is_input_value = name === 'value' && element . node . name === 'input' ;
const should_cache = is_src || this . node . should_cache ( ) ;
const last = should_cache && block . get_unique_name (
` ${ element . var . name } _ ${ name . replace ( /[^a-zA-Z_$]/g , '_' ) } _value `
) ;
if ( should_cache ) block . add_variable ( last ) ;
let updater ;
const init = should_cache ? x ` ${ last } = ${ value } ` : value ;
const init = this . get_init ( block , value ) ;
if ( is_legacy_input_type ) {
block . chunks . hydrate . push (
b ` @set_input_type( ${ element . var } , ${ init } ); `
) ;
updater = b ` @set_input_type( ${ element . var } , ${ should_cache ? last : value } ); ` ;
} else if ( is_select_value_attribute ) {
updater = b ` @set_input_type( ${ element . var } , ${ should_cache ? this . last : value } ); ` ;
} else if ( this . is_select_value_attribute ) {
// annoying special case
const is_multiple_select = element . node . get_static_attribute_value ( 'multiple' ) ;
@ -117,45 +117,37 @@ export default class AttributeWrapper {
block . chunks . mount . push ( b `
$ { updater }
` );
} else if ( is_src ) {
} else if ( this . is_src ) {
block . chunks . hydrate . push (
b ` if ( ${ element . var } .src !== ${ init } ) ${ method } ( ${ element . var } , " ${ name } ", ${ last } ); `
b ` if ( ${ element . var } .src !== ${ init } ) ${ method } ( ${ element . var } , " ${ name } ", ${ this . last } ); `
) ;
updater = b ` ${ method } ( ${ element . var } , " ${ name } ", ${ should_cache ? last : value } ); ` ;
updater = b ` ${ method } ( ${ element . var } , " ${ name } ", ${ should_cache ? this . last : value } ); ` ;
} else if ( property_name ) {
block . chunks . hydrate . push (
b ` ${ element . var } . ${ property_name } = ${ init } ; `
) ;
updater = block . renderer . options . dev
? b ` @prop_dev( ${ element . var } , " ${ property_name } ", ${ should_cache ? last : value } ); `
: b ` ${ element . var } . ${ property_name } = ${ should_cache ? last : value } ; ` ;
? b ` @prop_dev( ${ element . var } , " ${ property_name } ", ${ should_cache ? this . last : value } ); `
: b ` ${ element . var } . ${ property_name } = ${ should_cache ? this . last : value } ; ` ;
} else {
block . chunks . hydrate . push (
b ` ${ method } ( ${ element . var } , " ${ name } ", ${ init } ); `
) ;
updater = b ` ${ method } ( ${ element . var } , " ${ name } ", ${ should_cache ? last : value } ); ` ;
updater = b ` ${ method } ( ${ element . var } , " ${ name } ", ${ should_cache ? this . last : value } ); ` ;
}
if ( dependencies . length > 0 ) {
let condition = block . renderer . dirty ( dependencies ) ;
if ( should_cache ) {
condition = is_src
? x ` ${ condition } && ( ${ element . var } .src !== ( ${ last } = ${ value } )) `
: x ` ${ condition } && ( ${ last } !== ( ${ last } = ${ value } )) ` ;
}
if ( is_input_value ) {
const type = element . node . get_static_attribute_value ( 'type' ) ;
if ( is_indirectly_bound_value ) {
const update_value = b ` ${ element . var } .value = ${ element . var } .__value; ` ;
block . chunks . hydrate . push ( update_value ) ;
if ( type === null || type === "" || type === "text" || type === "email" || type === "password" ) {
condition = x ` ${ condition } && ${ element . var } . ${ property_name } !== ${ should_cache ? last : value } ` ;
}
}
updater = b `
$ { updater }
$ { update_value } ;
` ;
}
if ( block . has_outros ) {
condition = x ` !#current || ${ condition } ` ;
}
if ( dependencies . length > 0 ) {
const condition = this . get_dom_update_conditions ( block , block . renderer . dirty ( dependencies ) ) ;
block . chunks . update . push ( b `
if ( $ { condition } ) {
@ -167,13 +159,44 @@ export default class AttributeWrapper {
if ( this . node . is_true && name === 'autofocus' ) {
block . autofocus = element . var ;
}
}
if ( is_indirectly_bound_value ) {
const update_value = b ` ${ element . var } .value = ${ element . var } .__value; ` ;
get_init ( block : Block , value ) {
this . last = this . should_cache && block . get_unique_name (
` ${ this . parent . var . name } _ ${ this . name . replace ( /[^a-zA-Z_$]/g , '_' ) } _value `
) ;
block . chunks . hydrate . push ( update_value ) ;
if ( dependencies . length > 0 ) block . chunks . update . push ( update_value ) ;
if ( this . should_cache ) block . add_variable ( this . last ) ;
return this . should_cache ? x ` ${ this . last } = ${ value } ` : value ;
}
get_dom_update_conditions ( block : Block , dependency_condition : Node ) {
const { property_name , should_cache , last } = this ;
const element = this . parent ;
const value = this . get_value ( block ) ;
let condition = dependency_condition ;
if ( should_cache ) {
condition = this . is_src
? x ` ${ condition } && ( ${ element . var } .src !== ( ${ last } = ${ value } )) `
: x ` ${ condition } && ( ${ last } !== ( ${ last } = ${ value } )) ` ;
}
if ( this . is_input_value ) {
const type = element . node . get_static_attribute_value ( 'type' ) ;
if ( type === null || type === "" || type === "text" || type === "email" || type === "password" ) {
condition = x ` ${ condition } && ${ element . var } . ${ property_name } !== ${ should_cache ? last : value } ` ;
}
}
if ( block . has_outros ) {
condition = x ` !#current || ${ condition } ` ;
}
return condition ;
}
get_dependencies() {
@ -194,15 +217,14 @@ export default class AttributeWrapper {
get_metadata() {
if ( this . parent . node . namespace ) return null ;
const metadata = attribute_lookup [ fix_attribute_casing ( this . n ode. n ame) ] ;
const metadata = attribute_lookup [ this . n ame] ;
if ( metadata && metadata . applies_to && ! metadata . applies_to . includes ( this . parent . node . name ) ) return null ;
return metadata ;
}
get_value ( block ) {
if ( this . node . is_true ) {
const metadata = this . get_metadata ( ) ;
if ( metadata && boolean_attribute . has ( metadata . property_name . toLowerCase ( ) ) ) {
if ( this . metadata && boolean_attribute . has ( this . metadata . property_name . toLowerCase ( ) ) ) {
return x ` true ` ;
}
return x ` "" ` ;
@ -350,4 +372,19 @@ const boolean_attribute = new Set([
'required' ,
'reversed' ,
'selected'
] ) ;
] ) ;
function should_cache ( attribute : AttributeWrapper ) {
return attribute . is_src || attribute . node . should_cache ( ) ;
}
function is_indirectly_bound_value ( attribute : AttributeWrapper ) {
const element = attribute . parent ;
return attribute . name === 'value' &&
( element . node . name === 'option' || // TODO check it's actually bound
( element . node . name === 'input' &&
element . node . bindings . some (
( binding ) = >
/checked|group/ . test ( binding . name )
) ) ) ;
}