@ -27,6 +27,11 @@ import EventHandler from './EventHandler';
import { extract_names } from 'periscopic' ;
import { extract_names } from 'periscopic' ;
import Action from '../../../nodes/Action' ;
import Action from '../../../nodes/Action' ;
interface BindingGroup {
events : string [ ] ;
bindings : Binding [ ] ;
}
const events = [
const events = [
{
{
event_names : [ 'input' ] ,
event_names : [ 'input' ] ,
@ -405,7 +410,7 @@ export default class ElementWrapper extends Wrapper {
get_render_statement ( block : Block ) {
get_render_statement ( block : Block ) {
const { name , namespace } = this . node ;
const { name , namespace } = this . node ;
if ( namespace === 'http://www.w3.org/2000/svg' ) {
if ( namespace === namespaces . svg ) {
return x ` @svg_element(" ${ name } ") ` ;
return x ` @svg_element(" ${ name } ") ` ;
}
}
@ -436,14 +441,9 @@ export default class ElementWrapper extends Wrapper {
}
}
add_directives_in_order ( block : Block ) {
add_directives_in_order ( block : Block ) {
interface BindingGroup {
events : string [ ] ;
bindings : Binding [ ] ;
}
type OrderedAttribute = EventHandler | BindingGroup | Binding | Action ;
type OrderedAttribute = EventHandler | BindingGroup | Binding | Action ;
const binding G roups = events
const binding_groups = events
. map ( event = > ( {
. map ( event = > ( {
events : event.event_names ,
events : event.event_names ,
bindings : this.bindings
bindings : this.bindings
@ -467,7 +467,7 @@ export default class ElementWrapper extends Wrapper {
}
}
( [
( [
. . . binding G roups,
. . . binding _g roups,
. . . this . event_handlers ,
. . . this . event_handlers ,
this_binding ,
this_binding ,
. . . this . node . actions
. . . this . node . actions
@ -487,144 +487,141 @@ export default class ElementWrapper extends Wrapper {
} ) ;
} ) ;
}
}
add_bindings ( block : Block , binding Group) {
add_bindings ( block : Block , binding _group: Binding Group) {
const { renderer } = this ;
const { renderer } = this ;
if ( binding G roup. bindings . length === 0 ) return ;
if ( binding _g roup. bindings . length === 0 ) return ;
renderer . component . has_reactive_assignments = true ;
renderer . component . has_reactive_assignments = true ;
const lock = binding G roup. bindings . some ( binding = > binding . needs_lock ) ?
const lock = binding _g roup. bindings . some ( binding = > binding . needs_lock ) ?
block . get_unique_name ( ` ${ this . var . name } _updating ` ) :
block . get_unique_name ( ` ${ this . var . name } _updating ` ) :
null ;
null ;
if ( lock ) block . add_variable ( lock , x ` false ` ) ;
if ( lock ) block . add_variable ( lock , x ` false ` ) ;
[ bindingGroup ] . forEach ( group = > {
const handler = renderer . component . get_unique_name ( ` ${ this . var . name } _ ${ binding_group . events . join ( '_' ) } _handler ` ) ;
const handler = renderer . component . get_unique_name ( ` ${ this . var . name } _ ${ group . events . join ( '_' ) } _handler ` ) ;
renderer . add_to_context ( handler . name ) ;
renderer . add_to_context ( handler . name ) ;
// TODO figure out how to handle locks
// TODO figure out how to handle locks
const needs_lock = group. bindings . some ( binding = > binding . needs_lock ) ;
const needs_lock = binding_ group. bindings . some ( binding = > binding . needs_lock ) ;
const dependencies : Set < string > = new Set ( ) ;
const dependencies : Set < string > = new Set ( ) ;
const contextual_dependencies : Set < string > = new Set ( ) ;
const contextual_dependencies : Set < string > = new Set ( ) ;
group . bindings . forEach ( binding = > {
binding_group . bindings . forEach ( binding = > {
// TODO this is a mess
// TODO this is a mess
add_to_set ( dependencies , binding . get_dependencies ( ) ) ;
add_to_set ( dependencies , binding . get_dependencies ( ) ) ;
add_to_set ( contextual_dependencies , binding . node . expression . contextual_dependencies ) ;
add_to_set ( contextual_dependencies , binding . handler . contextual_dependencies ) ;
add_to_set ( contextual_dependencies , binding . handler . contextual_dependencies ) ;
binding . render ( block , lock ) ;
binding . render ( block , lock ) ;
} ) ;
} ) ;
// media bindings — awkward special case. The native timeupdate events
// media bindings — awkward special case. The native timeupdate events
// fire too infrequently, so we need to take matters into our
// fire too infrequently, so we need to take matters into our
// own hands
// own hands
let animation_frame ;
let animation_frame ;
if ( group. events [ 0 ] === 'timeupdate' ) {
if ( binding_ group. events [ 0 ] === 'timeupdate' ) {
animation_frame = block . get_unique_name ( ` ${ this . var . name } _animationframe ` ) ;
animation_frame = block . get_unique_name ( ` ${ this . var . name } _animationframe ` ) ;
block . add_variable ( animation_frame ) ;
block . add_variable ( animation_frame ) ;
}
}
const has_local_function = contextual_dependencies . size > 0 || needs_lock || animation_frame ;
const has_local_function = contextual_dependencies . size > 0 || needs_lock || animation_frame ;
let callee = renderer . reference ( handler ) ;
let callee = renderer . reference ( handler ) ;
// TODO dry this out — similar code for event handlers and component bindings
// TODO dry this out — similar code for event handlers and component bindings
if ( has_local_function ) {
if ( has_local_function ) {
const args = Array . from ( contextual_dependencies ) . map ( name = > renderer . reference ( name ) ) ;
const args = Array . from ( contextual_dependencies ) . map ( name = > renderer . reference ( name ) ) ;
// need to create a block-local function that calls an instance-level function
// need to create a block-local function that calls an instance-level function
if ( animation_frame ) {
if ( animation_frame ) {
block . chunks . init . push ( b `
block . chunks . init . push ( b `
function $ { handler } ( ) {
function $ { handler } ( ) {
@_cancelAnimationFrame ( $ { animation_frame } ) ;
@_cancelAnimationFrame ( $ { animation_frame } ) ;
if ( ! $ { this . var } . paused ) {
if ( ! $ { this . var } . paused ) {
$ { animation_frame } = @raf ( $ { handler } ) ;
$ { animation_frame } = @raf ( $ { handler } ) ;
$ { needs_lock && b ` ${ lock } = true; ` }
}
$ { callee } . call ( $ { this . var } , $ { args } ) ;
}
` );
} else {
block . chunks . init . push ( b `
function $ { handler } ( ) {
$ { needs_lock && b ` ${ lock } = true; ` }
$ { needs_lock && b ` ${ lock } = true; ` }
$ { callee } . call ( $ { this . var } , $ { args } ) ;
}
}
` );
$ { callee } . call ( $ { this . var } , $ { args } ) ;
}
}
` );
callee = handler ;
} else {
block . chunks . init . push ( b `
function $ { handler } ( ) {
$ { needs_lock && b ` ${ lock } = true; ` }
$ { callee } . call ( $ { this . var } , $ { args } ) ;
}
` );
}
}
const params = Array . from ( contextual_dependencies ) . map ( name = > ( {
callee = handler ;
type : 'Identifier' ,
}
name
} ) ) ;
this . renderer . component . partly_hoisted . push ( b `
function $ { handler } ( $ { params } ) {
$ { group . bindings . map ( b = > b . handler . mutation ) }
$ { Array . from ( dependencies )
. filter ( dep = > dep [ 0 ] !== '$' )
. filter ( dep = > ! contextual_dependencies . has ( dep ) )
. map ( dep = > b ` ${ this . renderer . invalidate ( dep ) } ; ` ) }
}
` );
group . events . forEach ( name = > {
if ( name === 'elementresize' ) {
// special case
const resize_listener = block . get_unique_name ( ` ${ this . var . name } _resize_listener ` ) ;
block . add_variable ( resize_listener ) ;
block . chunks . mount . push (
b ` ${ resize_listener } = @add_resize_listener( ${ this . var } , ${ callee } .bind( ${ this . var } )); `
) ;
block . chunks . destroy . push (
const params = Array . from ( contextual_dependencies ) . map ( name = > ( {
b ` ${ resize_listener } (); `
type : 'Identifier' ,
) ;
name
} else {
} ) ) ;
block . event_listeners . push (
x ` @listen( ${ this . var } , " ${ name } ", ${ callee } ) `
this . renderer . component . partly_hoisted . push ( b `
) ;
function $ { handler } ( $ { params } ) {
}
$ { binding_group . bindings . map ( b = > b . handler . mutation ) }
} ) ;
$ { Array . from ( dependencies )
. filter ( dep = > dep [ 0 ] !== '$' )
. filter ( dep = > ! contextual_dependencies . has ( dep ) )
. map ( dep = > b ` ${ this . renderer . invalidate ( dep ) } ; ` ) }
}
` );
const some_initial_state_is_undefined = group . bindings
binding_group . events . forEach ( name = > {
. map ( binding = > x ` ${ binding . snippet } === void 0 ` )
if ( name === 'elementresize' ) {
. reduce ( ( lhs , rhs ) = > x ` ${ lhs } || ${ rhs } ` ) ;
// special case
const resize_listener = block . get_unique_name ( ` ${ this . var . name } _resize_listener ` ) ;
const should_initialise = (
block . add_variable ( resize_listener ) ;
this . node . name === 'select' ||
group . bindings . find ( binding = > {
return (
binding . node . name === 'indeterminate' ||
binding . node . name === 'textContent' ||
binding . node . name === 'innerHTML' ||
binding . is_readonly_media_attribute ( )
) ;
} )
) ;
if ( should_initialise ) {
block . chunks . mount . push (
const callback = has_local_function ? handler : x ` () => ${ callee } .call( ${ this . var } ) ` ;
b ` ${ resize_listener } = @add_resize_listener( ${ this . var } , ${ callee } .bind( ${ this . var } )); `
block . chunks . hydrate . push (
b ` if ( ${ some_initial_state_is_undefined } ) @add_render_callback( ${ callback } ); `
) ;
) ;
}
if ( group . events [ 0 ] === 'elementresize' ) {
block . chunks . destroy . push (
block . chunks . hydrate . push (
b ` ${ resize_listener } (); `
b ` @add_render_callback(() => ${ callee } .call( ${ this . var } )); `
) ;
} else {
block . event_listeners . push (
x ` @listen( ${ this . var } , " ${ name } ", ${ callee } ) `
) ;
) ;
}
}
} ) ;
} ) ;
const some_initial_state_is_undefined = binding_group . bindings
. map ( binding = > x ` ${ binding . snippet } === void 0 ` )
. reduce ( ( lhs , rhs ) = > x ` ${ lhs } || ${ rhs } ` ) ;
const should_initialise = (
this . node . name === 'select' ||
binding_group . bindings . find ( binding = > {
return (
binding . node . name === 'indeterminate' ||
binding . node . name === 'textContent' ||
binding . node . name === 'innerHTML' ||
binding . is_readonly_media_attribute ( )
) ;
} )
) ;
if ( should_initialise ) {
const callback = has_local_function ? handler : x ` () => ${ callee } .call( ${ this . var } ) ` ;
block . chunks . hydrate . push (
b ` if ( ${ some_initial_state_is_undefined } ) @add_render_callback( ${ callback } ); `
) ;
}
if ( binding_group . events [ 0 ] === 'elementresize' ) {
block . chunks . hydrate . push (
b ` @add_render_callback(() => ${ callee } .call( ${ this . var } )); `
) ;
}
if ( lock ) {
if ( lock ) {
block . chunks . update . push ( b ` ${ lock } = false; ` ) ;
block . chunks . update . push ( b ` ${ lock } = false; ` ) ;
}
}
@ -635,7 +632,7 @@ export default class ElementWrapper extends Wrapper {
renderer . component . has_reactive_assignments = true ;
renderer . component . has_reactive_assignments = true ;
const binding_callback = bind_this ( renderer . component , block , this_binding .node , this . var ) ;
const binding_callback = bind_this ( renderer . component , block , this_binding , this . var ) ;
block . chunks . mount . push ( binding_callback ) ;
block . chunks . mount . push ( binding_callback ) ;
}
}