@@ -24,7 +24,8 @@ import {
24
24
get_attribute_name ,
25
25
build_attribute_value ,
26
26
build_class_directives ,
27
- build_style_directives
27
+ build_style_directives ,
28
+ build_set_attributes
28
29
} from './shared/element.js' ;
29
30
import { process_children } from './shared/fragment.js' ;
30
31
import {
@@ -95,7 +96,7 @@ export function RegularElement(node, context) {
95
96
/** @type {Map<string, AST.BindDirective> } */
96
97
const bindings = new Map ( ) ;
97
98
98
- let has_spread = false ;
99
+ let has_spread = node . metadata . has_spread ;
99
100
let has_use = false ;
100
101
101
102
for ( const attribute of node . attributes ) {
@@ -105,6 +106,16 @@ export function RegularElement(node, context) {
105
106
break ;
106
107
107
108
case 'Attribute' :
109
+ // `is` attributes need to be part of the template, otherwise they break
110
+ if ( attribute . name === 'is' && context . state . metadata . namespace === 'html' ) {
111
+ const { value } = build_attribute_value ( attribute . value , context ) ;
112
+
113
+ if ( value . type === 'Literal' && typeof value . value === 'string' ) {
114
+ context . state . template . push ( ` is="${ escape_html ( value . value , true ) } "` ) ;
115
+ continue ;
116
+ }
117
+ }
118
+
108
119
attributes . push ( attribute ) ;
109
120
lookup . set ( attribute . name , attribute ) ;
110
121
break ;
@@ -129,7 +140,6 @@ export function RegularElement(node, context) {
129
140
130
141
case 'SpreadAttribute' :
131
142
attributes . push ( attribute ) ;
132
- has_spread = true ;
133
143
break ;
134
144
135
145
case 'StyleDirective' :
@@ -194,17 +204,40 @@ export function RegularElement(node, context) {
194
204
const node_id = context . state . node ;
195
205
196
206
// Then do attributes
197
- let is_attributes_reactive = false ;
198
- if ( node . metadata . has_spread ) {
199
- build_element_spread_attributes (
207
+ let is_attributes_reactive = has_spread ;
208
+
209
+ if ( has_spread ) {
210
+ const attributes_id = b . id ( context . state . scope . generate ( 'attributes' ) ) ;
211
+
212
+ build_set_attributes (
200
213
attributes ,
201
214
context ,
202
215
node ,
203
216
node_id ,
204
- // If value binding exists, that one takes care of calling $.init_select
205
- node . name === 'select' && ! bindings . has ( 'value' )
217
+ attributes_id ,
218
+ ( node . metadata . svg || node . metadata . mathml || is_custom_element_node ( node ) ) && b . true ,
219
+ node . name . includes ( '-' ) && b . true
206
220
) ;
207
- is_attributes_reactive = true ;
221
+
222
+ // If value binding exists, that one takes care of calling $.init_select
223
+ if ( node . name === 'select' && ! bindings . has ( 'value' ) ) {
224
+ context . state . init . push (
225
+ b . stmt ( b . call ( '$.init_select' , node_id , b . thunk ( b . member ( attributes_id , 'value' ) ) ) )
226
+ ) ;
227
+
228
+ context . state . update . push (
229
+ b . if (
230
+ b . binary ( 'in' , b . literal ( 'value' ) , attributes_id ) ,
231
+ b . block ( [
232
+ // This ensures a one-way street to the DOM in case it's <select {value}>
233
+ // and not <select bind:value>. We need it in addition to $.init_select
234
+ // because the select value is not reflected as an attribute, so the
235
+ // mutation observer wouldn't notice.
236
+ b . stmt ( b . call ( '$.select_option' , node_id , b . member ( attributes_id , 'value' ) ) )
237
+ ] )
238
+ )
239
+ ) ;
240
+ }
208
241
} else {
209
242
/** If true, needs `__value` for inputs */
210
243
const needs_special_value_handling =
@@ -229,7 +262,7 @@ export function RegularElement(node, context) {
229
262
attribute . name !== 'autofocus' &&
230
263
( attribute . value === true || is_text_attribute ( attribute ) )
231
264
) {
232
- const name = get_attribute_name ( node , attribute , context ) ;
265
+ const name = get_attribute_name ( node , attribute ) ;
233
266
const value = is_text_attribute ( attribute ) ? attribute . value [ 0 ] . data : true ;
234
267
235
268
if ( name !== 'class' || value ) {
@@ -258,7 +291,7 @@ export function RegularElement(node, context) {
258
291
node_id ,
259
292
context ,
260
293
is_attributes_reactive ,
261
- lookup . has ( 'style' ) || node . metadata . has_spread
294
+ lookup . has ( 'style' ) || has_spread
262
295
) ;
263
296
264
297
// Apply the src and loading attributes for <img> elements after the element is appended to the document
@@ -448,109 +481,6 @@ function setup_select_synchronization(value_binding, context) {
448
481
) ;
449
482
}
450
483
451
- /**
452
- * @param {Array<AST.Attribute | AST.SpreadAttribute> } attributes
453
- * @param {ComponentContext } context
454
- * @param {AST.RegularElement } element
455
- * @param {Identifier } element_id
456
- * @param {boolean } needs_select_handling
457
- */
458
- function build_element_spread_attributes (
459
- attributes ,
460
- context ,
461
- element ,
462
- element_id ,
463
- needs_select_handling
464
- ) {
465
- let needs_isolation = false ;
466
-
467
- /** @type {ObjectExpression['properties'] } */
468
- const values = [ ] ;
469
-
470
- for ( const attribute of attributes ) {
471
- if ( attribute . type === 'Attribute' ) {
472
- const name = get_attribute_name ( element , attribute , context ) ;
473
- // TODO: handle has_call
474
- const { value } = build_attribute_value ( attribute . value , context ) ;
475
-
476
- if (
477
- name === 'is' &&
478
- value . type === 'Literal' &&
479
- context . state . metadata . namespace === 'html'
480
- ) {
481
- context . state . template . push ( ` is="${ escape_html ( value . value , true ) } "` ) ;
482
- continue ;
483
- }
484
-
485
- if (
486
- is_event_attribute ( attribute ) &&
487
- ( get_attribute_expression ( attribute ) . type === 'ArrowFunctionExpression' ||
488
- get_attribute_expression ( attribute ) . type === 'FunctionExpression' )
489
- ) {
490
- // Give the event handler a stable ID so it isn't removed and readded on every update
491
- const id = context . state . scope . generate ( 'event_handler' ) ;
492
- context . state . init . push ( b . var ( id , value ) ) ;
493
- values . push ( b . init ( attribute . name , b . id ( id ) ) ) ;
494
- } else {
495
- values . push ( b . init ( name , value ) ) ;
496
- }
497
- } else {
498
- values . push ( b . spread ( /** @type {Expression } */ ( context . visit ( attribute ) ) ) ) ;
499
- }
500
-
501
- needs_isolation ||=
502
- attribute . type === 'SpreadAttribute' && attribute . metadata . expression . has_call ;
503
- }
504
-
505
- const preserve_attribute_case =
506
- element . metadata . svg || element . metadata . mathml || is_custom_element_node ( element ) ;
507
- const id = b . id ( context . state . scope . generate ( 'attributes' ) ) ;
508
-
509
- const update = b . stmt (
510
- b . assignment (
511
- '=' ,
512
- id ,
513
- b . call (
514
- '$.set_attributes' ,
515
- element_id ,
516
- id ,
517
- b . object ( values ) ,
518
- context . state . analysis . css . hash !== '' && b . literal ( context . state . analysis . css . hash ) ,
519
- preserve_attribute_case && b . true ,
520
- is_ignored ( element , 'hydration_attribute_changed' ) && b . true ,
521
- element . name . includes ( '-' ) && b . true
522
- )
523
- )
524
- ) ;
525
-
526
- context . state . init . push ( b . let ( id ) ) ;
527
-
528
- // objects could contain reactive getters -> play it safe and always assume spread attributes are reactive
529
- if ( needs_isolation ) {
530
- context . state . init . push ( build_update ( update ) ) ;
531
- } else {
532
- context . state . update . push ( update ) ;
533
- }
534
-
535
- if ( needs_select_handling ) {
536
- context . state . init . push (
537
- b . stmt ( b . call ( '$.init_select' , element_id , b . thunk ( b . member ( id , 'value' ) ) ) )
538
- ) ;
539
- context . state . update . push (
540
- b . if (
541
- b . binary ( 'in' , b . literal ( 'value' ) , id ) ,
542
- b . block ( [
543
- // This ensures a one-way street to the DOM in case it's <select {value}>
544
- // and not <select bind:value>. We need it in addition to $.init_select
545
- // because the select value is not reflected as an attribute, so the
546
- // mutation observer wouldn't notice.
547
- b . stmt ( b . call ( '$.select_option' , element_id , b . member ( id , 'value' ) ) )
548
- ] )
549
- )
550
- ) ;
551
- }
552
- }
553
-
554
484
/**
555
485
* Serializes an assignment to an element property by adding relevant statements to either only
556
486
* the init or the the init and update arrays, depending on whether or not the value is dynamic.
@@ -581,7 +511,7 @@ function build_element_spread_attributes(
581
511
*/
582
512
function build_element_attribute_update_assignment ( element , node_id , attribute , context ) {
583
513
const state = context . state ;
584
- const name = get_attribute_name ( element , attribute , context ) ;
514
+ const name = get_attribute_name ( element , attribute ) ;
585
515
const is_svg = context . state . metadata . namespace === 'svg' || element . name === 'svg' ;
586
516
const is_mathml = context . state . metadata . namespace === 'mathml' ;
587
517
let { has_call, value } = build_attribute_value ( attribute . value , context ) ;
0 commit comments