@ -158,26 +158,6 @@ function serialize_class_directives(class_directives, element_id, context, is_at
}
}
}
}
/ * *
*
* @ param { string | null } spread _id
* @ param { import ( '#compiler' ) . RegularElement } node
* @ param { import ( '../types.js' ) . ComponentContext } context
* @ param { import ( 'estree' ) . Identifier } node _id
* /
function add _select _to _spread _update ( spread _id , node , context , node _id ) {
if ( spread _id !== null && node . name === 'select' ) {
context . state . update . push ( {
grouped : b . if (
b . binary ( 'in' , b . literal ( 'value' ) , b . id ( spread _id ) ) ,
b . block ( [
b . stmt ( b . call ( '$.select_option' , node _id , b . member ( b . id ( spread _id ) , b . id ( 'value' ) ) ) )
] )
)
} ) ;
}
}
/ * *
/ * *
* @ param { import ( '#compiler' ) . Binding [ ] } references
* @ param { import ( '#compiler' ) . Binding [ ] } references
* @ param { import ( '../types.js' ) . ComponentContext } context
* @ param { import ( '../types.js' ) . ComponentContext } context
@ -223,6 +203,8 @@ function collect_transitive_dependencies(binding, seen = new Set()) {
* @ param { import ( '../types.js' ) . ComponentContext } context
* @ param { import ( '../types.js' ) . ComponentContext } context
* /
* /
function setup _select _synchronization ( value _binding , context ) {
function setup _select _synchronization ( value _binding , context ) {
if ( context . state . analysis . runes ) return ;
let bound = value _binding . expression ;
let bound = value _binding . expression ;
while ( bound . type === 'MemberExpression' ) {
while ( bound . type === 'MemberExpression' ) {
bound = /** @type {import('estree').Identifier | import('estree').MemberExpression} */ (
bound = /** @type {import('estree').Identifier | import('estree').MemberExpression} */ (
@ -243,7 +225,6 @@ function setup_select_synchronization(value_binding, context) {
}
}
}
}
if ( ! context . state . analysis . runes ) {
const invalidator = b . call (
const invalidator = b . call (
'$.invalidate_inner_signals' ,
'$.invalidate_inner_signals' ,
b . thunk (
b . thunk (
@ -272,30 +253,21 @@ function setup_select_synchronization(value_binding, context) {
)
)
) ;
) ;
}
}
}
/ * *
/ * *
* Serializes element attribute assignments that contain spreads to either only
* the init or the the init and update arrays , depending on whether or not the value is dynamic .
* Resulting code for static looks something like this :
* ` ` ` js
* $ . spread _attributes ( element , null , [ ... ] ) ;
* ` ` `
* Resulting code for dynamic looks something like this :
* ` ` ` js
* let value ;
* $ . render _effect ( ( ) => {
* value = $ . spread _attributes ( element , value , [ ... ] )
* } ) ;
* ` ` `
* Returns the id of the spread _attribute variable if spread isn ' t isolated , ` null ` otherwise .
* @ param { Array < import ( '#compiler' ) . Attribute | import ( '#compiler' ) . SpreadAttribute > } attributes
* @ param { Array < import ( '#compiler' ) . Attribute | import ( '#compiler' ) . SpreadAttribute > } attributes
* @ param { import ( '../types.js' ) . ComponentContext } context
* @ param { import ( '../types.js' ) . ComponentContext } context
* @ param { import ( '#compiler' ) . RegularElement } element
* @ param { import ( '#compiler' ) . RegularElement } element
* @ param { import ( 'estree' ) . Identifier } element _id
* @ param { import ( 'estree' ) . Identifier } element _id
* @ returns { string | null }
* @ param { boolean } needs _select _handling
* /
* /
function serialize _element _spread _attributes ( attributes , context , element , element _id ) {
function serialize _element _spread _attributes (
attributes ,
context ,
element ,
element _id ,
needs _select _handling
) {
let needs _isolation = false ;
let needs _isolation = false ;
/** @type {import('estree').Expression[]} */
/** @type {import('estree').Expression[]} */
@ -317,8 +289,9 @@ function serialize_element_spread_attributes(attributes, context, element, eleme
const lowercase _attributes =
const lowercase _attributes =
element . metadata . svg || is _custom _element _node ( element ) ? b . false : b . true ;
element . metadata . svg || is _custom _element _node ( element ) ? b . false : b . true ;
const id = context . state . scope . generate ( 'spread_attributes' ) ;
const isolated = b . stmt (
const standalone = b . stmt (
b . call (
b . call (
'$.spread_attributes_effect' ,
'$.spread_attributes_effect' ,
element _id ,
element _id ,
@ -327,17 +300,7 @@ function serialize_element_spread_attributes(attributes, context, element, eleme
b . literal ( context . state . analysis . css . hash )
b . literal ( context . state . analysis . css . hash )
)
)
) ;
) ;
const inside _effect = b . stmt (
// objects could contain reactive getters -> play it safe and always assume spread attributes are reactive
if ( needs _isolation ) {
context . state . update _effects . push ( isolated ) ;
return null ;
} else {
const id = context . state . scope . generate ( 'spread_attributes' ) ;
context . state . init . push ( b . let ( id ) ) ;
context . state . update . push ( {
singular : isolated ,
grouped : b . stmt (
b . assignment (
b . assignment (
'=' ,
'=' ,
b . id ( id ) ,
b . id ( id ) ,
@ -350,9 +313,44 @@ function serialize_element_spread_attributes(attributes, context, element, eleme
b . literal ( context . state . analysis . css . hash )
b . literal ( context . state . analysis . css . hash )
)
)
)
)
) ;
if ( ! needs _isolation || needs _select _handling ) {
context . state . init . push ( b . let ( id ) ) ;
}
// objects could contain reactive getters -> play it safe and always assume spread attributes are reactive
if ( needs _isolation ) {
if ( needs _select _handling ) {
context . state . update _effects . push (
b . stmt ( b . call ( '$.render_effect' , b . arrow ( [ ] , b . block ( [ inside _effect ] ) ) ) )
) ;
} else {
context . state . update _effects . push ( standalone ) ;
}
} else {
context . state . update . push ( {
singular : needs _select _handling ? undefined : standalone ,
grouped : inside _effect
} ) ;
}
if ( needs _select _handling ) {
context . state . init . push (
b . stmt ( b . call ( '$.init_select' , element _id , b . thunk ( b . member ( b . id ( id ) , b . id ( 'value' ) ) ) ) )
) ;
context . state . update . push ( {
grouped : b . if (
b . binary ( 'in' , b . literal ( 'value' ) , b . id ( id ) ) ,
b . block ( [
// This ensures a one-way street to the DOM in case it's <select {value}>
// and not <select bind:value>. We need it in addition to $.init_select
// because the select value is not reflected as an attribute, so the
// mutation observer wouldn't notice.
b . stmt ( b . call ( '$.select_option' , element _id , b . member ( b . id ( id ) , b . id ( 'value' ) ) ) )
] )
)
)
} ) ;
} ) ;
return id ;
}
}
}
}
@ -644,27 +642,27 @@ function serialize_element_special_value_attribute(element, node_id, attribute,
)
)
) ;
) ;
const is _reactive = attribute . metadata . dynamic ;
const is _reactive = attribute . metadata . dynamic ;
const needs _selected _call =
const is _select _with _value =
element === 'option' && ( is _reactive || collect _parent _each _blocks ( context ) . length > 0 ) ;
// attribute.metadata.dynamic would give false negatives because even if the value does not change,
const needs _option _call = element === 'select' && is _reactive ;
// the inner options could still change, so we need to always treat it as reactive
element === 'select' && attribute . value !== true && ! is _text _attribute ( attribute ) ;
const assignment = b . stmt (
const assignment = b . stmt (
needs _selected _call
is _select _with _value
? b . sequence ( [
inner _assignment ,
// This ensures things stay in sync with the select binding
// in case of updates to the option value or new values appearing
b . call ( '$.selected' , node _id )
] )
: needs _option _call
? b . sequence ( [
? b . sequence ( [
inner _assignment ,
inner _assignment ,
// This ensures a one-way street to the DOM in case it's <select {value}>
// This ensures a one-way street to the DOM in case it's <select {value}>
// and not <select bind:value>
// and not <select bind:value>. We need it in addition to $.init_select
// because the select value is not reflected as an attribute, so the
// mutation observer wouldn't notice.
b . call ( '$.select_option' , node _id , value )
b . call ( '$.select_option' , node _id , value )
] )
] )
: inner _assignment
: inner _assignment
) ;
) ;
if ( is _select _with _value ) {
state . init . push ( b . stmt ( b . call ( '$.init_select' , node _id , b . thunk ( value ) ) ) ) ;
}
if ( is _reactive ) {
if ( is _reactive ) {
const id = state . scope . generate ( ` ${ node _id . name } _value ` ) ;
const id = state . scope . generate ( ` ${ node _id . name } _value ` ) ;
serialize _update _assignment (
serialize _update _assignment (
@ -2082,11 +2080,15 @@ export const template_visitors = {
// Then do attributes
// Then do attributes
let is _attributes _reactive = false ;
let is _attributes _reactive = false ;
if ( node . metadata . has _spread ) {
if ( node . metadata . has _spread ) {
const spread _id = serialize _element _spread _attributes ( attributes , context , node , node _id ) ;
serialize _element _spread _attributes (
if ( child _metadata . namespace !== 'foreign' ) {
attributes ,
add _select _to _spread _update ( spread _id , node , context , node _id ) ;
context ,
}
node ,
is _attributes _reactive = spread _id !== null ;
node _id ,
// If value binding exists, that one takes care of calling $.init_select
value _binding === null && node . name === 'select' && child _metadata . namespace !== 'foreign'
) ;
is _attributes _reactive = true ;
} else {
} else {
for ( const attribute of /** @type {import('#compiler').Attribute[]} */ ( attributes ) ) {
for ( const attribute of /** @type {import('#compiler').Attribute[]} */ ( attributes ) ) {
if ( is _event _attribute ( attribute ) ) {
if ( is _event _attribute ( attribute ) ) {