@@ -158,26 +158,6 @@ function serialize_class_directives(class_directives, element_id, context, is_at
158
158
}
159
159
}
160
160
161
- /**
162
- *
163
- * @param {string | null } spread_id
164
- * @param {import('#compiler').RegularElement } node
165
- * @param {import('../types.js').ComponentContext } context
166
- * @param {import('estree').Identifier } node_id
167
- */
168
- function add_select_to_spread_update ( spread_id , node , context , node_id ) {
169
- if ( spread_id !== null && node . name === 'select' ) {
170
- context . state . update . push ( {
171
- grouped : b . if (
172
- b . binary ( 'in' , b . literal ( 'value' ) , b . id ( spread_id ) ) ,
173
- b . block ( [
174
- b . stmt ( b . call ( '$.select_option' , node_id , b . member ( b . id ( spread_id ) , b . id ( 'value' ) ) ) )
175
- ] )
176
- )
177
- } ) ;
178
- }
179
- }
180
-
181
161
/**
182
162
* @param {import('#compiler').Binding[] } references
183
163
* @param {import('../types.js').ComponentContext } context
@@ -223,6 +203,8 @@ function collect_transitive_dependencies(binding, seen = new Set()) {
223
203
* @param {import('../types.js').ComponentContext } context
224
204
*/
225
205
function setup_select_synchronization ( value_binding , context ) {
206
+ if ( context . state . analysis . runes ) return ;
207
+
226
208
let bound = value_binding . expression ;
227
209
while ( bound . type === 'MemberExpression' ) {
228
210
bound = /** @type {import('estree').Identifier | import('estree').MemberExpression } */ (
@@ -243,59 +225,49 @@ function setup_select_synchronization(value_binding, context) {
243
225
}
244
226
}
245
227
246
- if ( ! context . state . analysis . runes ) {
247
- const invalidator = b . call (
248
- '$.invalidate_inner_signals' ,
249
- b . thunk (
250
- b . block (
251
- names . map ( ( name ) => {
252
- const serialized = serialize_get_binding ( b . id ( name ) , context . state ) ;
253
- return b . stmt ( serialized ) ;
254
- } )
255
- )
228
+ const invalidator = b . call (
229
+ '$.invalidate_inner_signals' ,
230
+ b . thunk (
231
+ b . block (
232
+ names . map ( ( name ) => {
233
+ const serialized = serialize_get_binding ( b . id ( name ) , context . state ) ;
234
+ return b . stmt ( serialized ) ;
235
+ } )
256
236
)
257
- ) ;
237
+ )
238
+ ) ;
258
239
259
- context . state . init . push (
260
- b . stmt (
261
- b . call (
262
- '$.invalidate_effect' ,
263
- b . thunk (
264
- b . block ( [
265
- b . stmt (
266
- /** @type {import('estree').Expression } */ ( context . visit ( value_binding . expression ) )
267
- ) ,
268
- b . stmt ( invalidator )
269
- ] )
270
- )
240
+ context . state . init . push (
241
+ b . stmt (
242
+ b . call (
243
+ '$.invalidate_effect' ,
244
+ b . thunk (
245
+ b . block ( [
246
+ b . stmt (
247
+ /** @type {import('estree').Expression } */ ( context . visit ( value_binding . expression ) )
248
+ ) ,
249
+ b . stmt ( invalidator )
250
+ ] )
271
251
)
272
252
)
273
- ) ;
274
- }
253
+ )
254
+ ) ;
275
255
}
276
256
277
257
/**
278
- * Serializes element attribute assignments that contain spreads to either only
279
- * the init or the the init and update arrays, depending on whether or not the value is dynamic.
280
- * Resulting code for static looks something like this:
281
- * ```js
282
- * $.spread_attributes(element, null, [...]);
283
- * ```
284
- * Resulting code for dynamic looks something like this:
285
- * ```js
286
- * let value;
287
- * $.render_effect(() => {
288
- * value = $.spread_attributes(element, value, [...])
289
- * });
290
- * ```
291
- * Returns the id of the spread_attribute variable if spread isn't isolated, `null` otherwise.
292
258
* @param {Array<import('#compiler').Attribute | import('#compiler').SpreadAttribute> } attributes
293
259
* @param {import('../types.js').ComponentContext } context
294
260
* @param {import('#compiler').RegularElement } element
295
261
* @param {import('estree').Identifier } element_id
296
- * @returns { string | null }
262
+ * @param { boolean } needs_select_handling
297
263
*/
298
- function serialize_element_spread_attributes ( attributes , context , element , element_id ) {
264
+ function serialize_element_spread_attributes (
265
+ attributes ,
266
+ context ,
267
+ element ,
268
+ element_id ,
269
+ needs_select_handling
270
+ ) {
299
271
let needs_isolation = false ;
300
272
301
273
/** @type {import('estree').Expression[] } */
@@ -317,8 +289,9 @@ function serialize_element_spread_attributes(attributes, context, element, eleme
317
289
318
290
const lowercase_attributes =
319
291
element . metadata . svg || is_custom_element_node ( element ) ? b . false : b . true ;
292
+ const id = context . state . scope . generate ( 'spread_attributes' ) ;
320
293
321
- const isolated = b . stmt (
294
+ const standalone = b . stmt (
322
295
b . call (
323
296
'$.spread_attributes_effect' ,
324
297
element_id ,
@@ -327,32 +300,57 @@ function serialize_element_spread_attributes(attributes, context, element, eleme
327
300
b . literal ( context . state . analysis . css . hash )
328
301
)
329
302
) ;
303
+ const inside_effect = b . stmt (
304
+ b . assignment (
305
+ '=' ,
306
+ b . id ( id ) ,
307
+ b . call (
308
+ '$.spread_attributes' ,
309
+ element_id ,
310
+ b . id ( id ) ,
311
+ b . array ( values ) ,
312
+ lowercase_attributes ,
313
+ b . literal ( context . state . analysis . css . hash )
314
+ )
315
+ )
316
+ ) ;
317
+
318
+ if ( ! needs_isolation || needs_select_handling ) {
319
+ context . state . init . push ( b . let ( id ) ) ;
320
+ }
330
321
331
322
// objects could contain reactive getters -> play it safe and always assume spread attributes are reactive
332
323
if ( needs_isolation ) {
333
- context . state . update_effects . push ( isolated ) ;
334
- return null ;
324
+ if ( needs_select_handling ) {
325
+ context . state . update_effects . push (
326
+ b . stmt ( b . call ( '$.render_effect' , b . arrow ( [ ] , b . block ( [ inside_effect ] ) ) ) )
327
+ ) ;
328
+ } else {
329
+ context . state . update_effects . push ( standalone ) ;
330
+ }
335
331
} else {
336
- const id = context . state . scope . generate ( 'spread_attributes' ) ;
337
- context . state . init . push ( b . let ( id ) ) ;
338
332
context . state . update . push ( {
339
- singular : isolated ,
340
- grouped : b . stmt (
341
- b . assignment (
342
- '=' ,
343
- b . id ( id ) ,
344
- b . call (
345
- '$.spread_attributes' ,
346
- element_id ,
347
- b . id ( id ) ,
348
- b . array ( values ) ,
349
- lowercase_attributes ,
350
- b . literal ( context . state . analysis . css . hash )
351
- )
352
- )
333
+ singular : needs_select_handling ? undefined : standalone ,
334
+ grouped : inside_effect
335
+ } ) ;
336
+ }
337
+
338
+ if ( needs_select_handling ) {
339
+ context . state . init . push (
340
+ b . stmt ( b . call ( '$.init_select' , element_id , b . thunk ( b . member ( b . id ( id ) , b . id ( 'value' ) ) ) ) )
341
+ ) ;
342
+ context . state . update . push ( {
343
+ grouped : b . if (
344
+ b . binary ( 'in' , b . literal ( 'value' ) , b . id ( id ) ) ,
345
+ b . block ( [
346
+ // This ensures a one-way street to the DOM in case it's <select {value}>
347
+ // and not <select bind:value>. We need it in addition to $.init_select
348
+ // because the select value is not reflected as an attribute, so the
349
+ // mutation observer wouldn't notice.
350
+ b . stmt ( b . call ( '$.select_option' , element_id , b . member ( b . id ( id ) , b . id ( 'value' ) ) ) )
351
+ ] )
353
352
)
354
353
} ) ;
355
- return id ;
356
354
}
357
355
}
358
356
@@ -644,27 +642,27 @@ function serialize_element_special_value_attribute(element, node_id, attribute,
644
642
)
645
643
) ;
646
644
const is_reactive = attribute . metadata . dynamic ;
647
- const needs_selected_call =
648
- element === 'option' && ( is_reactive || collect_parent_each_blocks ( context ) . length > 0 ) ;
649
- const needs_option_call = element === 'select' && is_reactive ;
645
+ const is_select_with_value =
646
+ // attribute.metadata.dynamic would give false negatives because even if the value does not change,
647
+ // the inner options could still change, so we need to always treat it as reactive
648
+ element === 'select' && attribute . value !== true && ! is_text_attribute ( attribute ) ;
650
649
const assignment = b . stmt (
651
- needs_selected_call
650
+ is_select_with_value
652
651
? b . sequence ( [
653
652
inner_assignment ,
654
- // This ensures things stay in sync with the select binding
655
- // in case of updates to the option value or new values appearing
656
- b . call ( '$.selected' , node_id )
653
+ // This ensures a one-way street to the DOM in case it's <select {value}>
654
+ // and not <select bind:value>. We need it in addition to $.init_select
655
+ // because the select value is not reflected as an attribute, so the
656
+ // mutation observer wouldn't notice.
657
+ b . call ( '$.select_option' , node_id , value )
657
658
] )
658
- : needs_option_call
659
- ? b . sequence ( [
660
- inner_assignment ,
661
- // This ensures a one-way street to the DOM in case it's <select {value}>
662
- // and not <select bind:value>
663
- b . call ( '$.select_option' , node_id , value )
664
- ] )
665
- : inner_assignment
659
+ : inner_assignment
666
660
) ;
667
661
662
+ if ( is_select_with_value ) {
663
+ state . init . push ( b . stmt ( b . call ( '$.init_select' , node_id , b . thunk ( value ) ) ) ) ;
664
+ }
665
+
668
666
if ( is_reactive ) {
669
667
const id = state . scope . generate ( `${ node_id . name } _value` ) ;
670
668
serialize_update_assignment (
@@ -2083,11 +2081,15 @@ export const template_visitors = {
2083
2081
// Then do attributes
2084
2082
let is_attributes_reactive = false ;
2085
2083
if ( node . metadata . has_spread ) {
2086
- const spread_id = serialize_element_spread_attributes ( attributes , context , node , node_id ) ;
2087
- if ( child_metadata . namespace !== 'foreign' ) {
2088
- add_select_to_spread_update ( spread_id , node , context , node_id ) ;
2089
- }
2090
- is_attributes_reactive = spread_id !== null ;
2084
+ serialize_element_spread_attributes (
2085
+ attributes ,
2086
+ context ,
2087
+ node ,
2088
+ node_id ,
2089
+ // If value binding exists, that one takes care of calling $.init_select
2090
+ value_binding === null && node . name === 'select' && child_metadata . namespace !== 'foreign'
2091
+ ) ;
2092
+ is_attributes_reactive = true ;
2091
2093
} else {
2092
2094
for ( const attribute of /** @type {import('#compiler').Attribute[] } */ ( attributes ) ) {
2093
2095
if ( is_event_attribute ( attribute ) ) {
0 commit comments