@ -59,27 +59,23 @@ function get_component_name(filename) {
}
}
/ * *
/ * *
* @ param { Pick < import ( '#compiler' ) . OnDirective , 'expression' | 'name' | 'modifiers' > & { type : string } } node
* Checks if given event attribute can be delegated / hoisted and returns the corresponding info if so
* @ param { string } event _name
* @ param { import ( 'estree' ) . Expression | null } handler
* @ param { import ( './types' ) . Context } context
* @ param { import ( './types' ) . Context } context
* @ returns { null | import ( '#compiler' ) . DelegatedEvent }
* @ returns { null | import ( '#compiler' ) . DelegatedEvent }
* /
* /
function get _delegated _event ( node , context ) {
function get _delegated _event ( event _name , handler , context ) {
const handler = node . expression ;
const event _name = node . name ;
// Handle delegated event handlers. Bail-out if not a delegated event.
// Handle delegated event handlers. Bail-out if not a delegated event.
if ( ! handler || node . modifiers . includes ( 'capture' ) || ! DelegatedEvents . includes ( event _name ) ) {
if ( ! handler || ! DelegatedEvents . includes ( event _name ) ) {
return null ;
return null ;
}
}
// If we are not working with a RegularElement, then bail-out.
// If we are not working with a RegularElement, then bail-out.
const element = context . path . at ( - 1 ) ;
const element = context . path . at ( - 1 ) ;
if ( element ? . type !== 'RegularElement' ) {
if ( element ? . type !== 'RegularElement' ) {
return null ;
return null ;
}
}
// If element says we can't delegate because we have multiple OnDirectives of the same type, bail-out.
if ( ! element . metadata . can _delegate _events ) {
return null ;
}
/** @type {import('#compiler').DelegatedEvent} */
/** @type {import('#compiler').DelegatedEvent} */
const non _hoistable = { type : 'non-hoistable' } ;
const non _hoistable = { type : 'non-hoistable' } ;
@ -87,7 +83,7 @@ function get_delegated_event(node, context) {
let target _function = null ;
let target _function = null ;
let binding = null ;
let binding = null ;
if ( node. type === 'Attribute' && element. metadata . has _spread ) {
if ( element. metadata . has _spread ) {
// event attribute becomes part of the dynamic spread array
// event attribute becomes part of the dynamic spread array
return non _hoistable ;
return non _hoistable ;
}
}
@ -123,8 +119,7 @@ function get_delegated_event(node, context) {
if ( element && event _name ) {
if ( element && event _name ) {
if (
if (
element . type !== 'RegularElement' ||
element . type !== 'RegularElement' ||
! determine _element _spread _and _delegatable ( element ) . metadata . can _delegate _events ||
determine _element _spread ( element ) . metadata . has _spread ||
( element . metadata . has _spread && node . type === 'Attribute' ) ||
! DelegatedEvents . includes ( event _name )
! DelegatedEvents . includes ( event _name )
) {
) {
return non _hoistable ;
return non _hoistable ;
@ -183,7 +178,8 @@ function get_delegated_event(node, context) {
) {
) {
return non _hoistable ;
return non _hoistable ;
}
}
// If we referebnce the index within an each block, then bail-out.
// If we reference the index within an each block, then bail-out.
if ( binding !== null && binding . initial ? . type === 'EachBlock' ) {
if ( binding !== null && binding . initial ? . type === 'EachBlock' ) {
return non _hoistable ;
return non _hoistable ;
}
}
@ -204,6 +200,7 @@ function get_delegated_event(node, context) {
}
}
visited _references . add ( reference ) ;
visited _references . add ( reference ) ;
}
}
return { type : 'hoistable' , function : target _function } ;
return { type : 'hoistable' , function : target _function } ;
}
}
@ -858,21 +855,9 @@ const common_visitors = {
} ) ;
} ) ;
if ( is _event _attribute ( node ) ) {
if ( is _event _attribute ( node ) ) {
/** @type {string[]} */
const modifiers = [ ] ;
const expression = node . value [ 0 ] . expression ;
const expression = node . value [ 0 ] . expression ;
let name = node . name . slice ( 2 ) ;
const delegated _event = get _delegated _event ( node . name . slice ( 2 ) , expression , context ) ;
if ( is _capture _event ( name ) ) {
name = name . slice ( 0 , - 7 ) ;
modifiers . push ( 'capture' ) ;
}
const delegated _event = get _delegated _event (
{ type : node . type , name , expression , modifiers } ,
context
) ;
if ( delegated _event !== null ) {
if ( delegated _event !== null ) {
if ( delegated _event . type === 'hoistable' ) {
if ( delegated _event . type === 'hoistable' ) {
@ -1032,18 +1017,6 @@ const common_visitors = {
)
)
} ;
} ;
} ,
} ,
OnDirective ( node , context ) {
node . metadata = { delegated : null } ;
context . next ( ) ;
const delegated _event = get _delegated _event ( node , context ) ;
if ( delegated _event !== null ) {
if ( delegated _event . type === 'hoistable' ) {
delegated _event . function . metadata . hoistable = true ;
}
node . metadata . delegated = delegated _event ;
}
} ,
ArrowFunctionExpression : function _visitor ,
ArrowFunctionExpression : function _visitor ,
FunctionExpression : function _visitor ,
FunctionExpression : function _visitor ,
FunctionDeclaration : function _visitor ,
FunctionDeclaration : function _visitor ,
@ -1052,7 +1025,7 @@ const common_visitors = {
node . metadata . svg = true ;
node . metadata . svg = true ;
}
}
determine _element _spread _and _delegatable ( node ) ;
determine _element _spread ( node ) ;
// Special case: Move the children of <textarea> into a value attribute if they are dynamic
// Special case: Move the children of <textarea> into a value attribute if they are dynamic
if (
if (
@ -1110,51 +1083,15 @@ const common_visitors = {
} ;
} ;
/ * *
/ * *
* Check if events on this element can theoretically be delegated . They can if there ' s no
* possibility of an OnDirective and an event attribute on the same element , and if there ' s
* no OnDirectives of the same type ( the latter is a bit too strict because ` on:click on:click on:keyup `
* means that ` on:keyup ` can be delegated but we gloss over this edge case ) .
* @ param { import ( '#compiler' ) . RegularElement } node
* @ param { import ( '#compiler' ) . RegularElement } node
* /
* /
function determine _element _spread _and _delegatable ( node ) {
function determine _element _spread ( node ) {
if ( typeof node . metadata . can _delegate _events === 'boolean' ) {
return node ; // did this already
}
let events = new Map ( ) ;
let has _spread = false ;
let has _spread = false ;
let has _on = false ;
let has _action _or _bind = false ;
for ( const attribute of node . attributes ) {
for ( const attribute of node . attributes ) {
if (
if ( ! has _spread && attribute . type === 'SpreadAttribute' ) {
attribute . type === 'OnDirective' ||
( attribute . type === 'Attribute' && is _event _attribute ( attribute ) )
) {
let event _name = attribute . name ;
if ( attribute . type === 'Attribute' ) {
event _name = get _attribute _event _name ( event _name ) ;
}
events . set ( event _name , ( events . get ( event _name ) || 0 ) + 1 ) ;
if ( ! has _on && attribute . type === 'OnDirective' ) {
has _on = true ;
}
} else if ( ! has _spread && attribute . type === 'SpreadAttribute' ) {
has _spread = true ;
has _spread = true ;
} else if (
! has _action _or _bind &&
( ( attribute . type === 'BindDirective' && attribute . name !== 'this' ) ||
attribute . type === 'UseDirective' )
) {
has _action _or _bind = true ;
}
}
}
}
node . metadata . can _delegate _events =
// Actions/bindings need the old on:-events to fire in order
! has _action _or _bind &&
// spreading events means we don't know if there's an event attribute with the same name as an on:-event
! ( has _spread && has _on ) &&
// multiple on:-events/event attributes with the same name
! [ ... events . values ( ) ] . some ( ( count ) => count > 1 ) ;
node . metadata . has _spread = has _spread ;
node . metadata . has _spread = has _spread ;
return node ;
return node ;