1
- /** @import { Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */
1
+ /** @import { ArrayExpression, Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */
2
2
/** @import { AST } from '#compiler' */
3
3
/** @import { SourceLocation } from '#shared' */
4
4
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
@@ -20,9 +20,9 @@ import { build_getter } from '../utils.js';
20
20
import {
21
21
get_attribute_name ,
22
22
build_attribute_value ,
23
- build_style_directives ,
24
23
build_set_attributes ,
25
- build_set_class
24
+ build_set_class ,
25
+ build_set_style
26
26
} from './shared/element.js' ;
27
27
import { process_children } from './shared/fragment.js' ;
28
28
import {
@@ -215,13 +215,18 @@ export function RegularElement(node, context) {
215
215
216
216
const node_id = context . state . node ;
217
217
218
- // Then do attributes
219
- let is_attributes_reactive = has_spread ;
220
-
221
218
if ( has_spread ) {
222
219
const attributes_id = b . id ( context . state . scope . generate ( 'attributes' ) ) ;
223
220
224
- build_set_attributes ( attributes , class_directives , context , node , node_id , attributes_id ) ;
221
+ build_set_attributes (
222
+ attributes ,
223
+ class_directives ,
224
+ style_directives ,
225
+ context ,
226
+ node ,
227
+ node_id ,
228
+ attributes_id
229
+ ) ;
225
230
226
231
// If value binding exists, that one takes care of calling $.init_select
227
232
if ( node . name === 'select' && ! bindings . has ( 'value' ) ) {
@@ -262,11 +267,13 @@ export function RegularElement(node, context) {
262
267
}
263
268
264
269
const name = get_attribute_name ( node , attribute ) ;
270
+
265
271
if (
266
272
! is_custom_element &&
267
273
! cannot_be_set_statically ( attribute . name ) &&
268
274
( attribute . value === true || is_text_attribute ( attribute ) ) &&
269
- ( name !== 'class' || class_directives . length === 0 )
275
+ ( name !== 'class' || class_directives . length === 0 ) &&
276
+ ( name !== 'style' || style_directives . length === 0 )
270
277
) {
271
278
let value = is_text_attribute ( attribute ) ? attribute . value [ 0 ] . data : true ;
272
279
@@ -287,27 +294,30 @@ export function RegularElement(node, context) {
287
294
} `
288
295
) ;
289
296
}
290
- continue ;
291
- }
297
+ } else if ( name === 'autofocus' ) {
298
+ let { value } = build_attribute_value ( attribute . value , context ) ;
299
+ context . state . init . push ( b . stmt ( b . call ( '$.autofocus' , node_id , value ) ) ) ;
300
+ } else if ( name === 'class' ) {
301
+ const is_html = context . state . metadata . namespace === 'html' && node . name !== 'svg' ;
302
+ build_set_class ( node , node_id , attribute , class_directives , context , is_html ) ;
303
+ } else if ( name === 'style' ) {
304
+ build_set_style ( node_id , attribute , style_directives , context ) ;
305
+ } else if ( is_custom_element ) {
306
+ build_custom_element_attribute_update_assignment ( node_id , attribute , context ) ;
307
+ } else {
308
+ const { value, has_state } = build_attribute_value (
309
+ attribute . value ,
310
+ context ,
311
+ ( value , metadata ) => ( metadata . has_call ? get_expression_id ( context . state , value ) : value )
312
+ ) ;
313
+
314
+ const update = build_element_attribute_update ( node , node_id , name , value , attributes ) ;
292
315
293
- const is =
294
- is_custom_element && name !== 'class'
295
- ? build_custom_element_attribute_update_assignment ( node_id , attribute , context )
296
- : build_element_attribute_update_assignment (
297
- node ,
298
- node_id ,
299
- attribute ,
300
- attributes ,
301
- class_directives ,
302
- context
303
- ) ;
304
- if ( is ) is_attributes_reactive = true ;
316
+ ( has_state ? context . state . update : context . state . init ) . push ( b . stmt ( update ) ) ;
317
+ }
305
318
}
306
319
}
307
320
308
- // style directives must be applied last since they could override class/style attributes
309
- build_style_directives ( style_directives , node_id , context , is_attributes_reactive ) ;
310
-
311
321
if (
312
322
is_load_error_element ( node . name ) &&
313
323
( has_spread || has_use || lookup . has ( 'onload' ) || lookup . has ( 'onerror' ) )
@@ -519,6 +529,36 @@ export function build_class_directives_object(class_directives, context) {
519
529
return b . object ( properties ) ;
520
530
}
521
531
532
+ /**
533
+ * @param {AST.StyleDirective[] } style_directives
534
+ * @param {ComponentContext } context
535
+ * @return {ObjectExpression | ArrayExpression }}
536
+ */
537
+ export function build_style_directives_object ( style_directives , context ) {
538
+ let normal_properties = [ ] ;
539
+ let important_properties = [ ] ;
540
+
541
+ for ( const directive of style_directives ) {
542
+ const expression =
543
+ directive . value === true
544
+ ? build_getter ( { name : directive . name , type : 'Identifier' } , context . state )
545
+ : build_attribute_value ( directive . value , context , ( value , metadata ) =>
546
+ metadata . has_call ? get_expression_id ( context . state , value ) : value
547
+ ) . value ;
548
+ const property = b . init ( directive . name , expression ) ;
549
+
550
+ if ( directive . modifiers . includes ( 'important' ) ) {
551
+ important_properties . push ( property ) ;
552
+ } else {
553
+ normal_properties . push ( property ) ;
554
+ }
555
+ }
556
+
557
+ return important_properties . length
558
+ ? b . array ( [ b . object ( normal_properties ) , b . object ( important_properties ) ] )
559
+ : b . object ( normal_properties ) ;
560
+ }
561
+
522
562
/**
523
563
* Serializes an assignment to an element property by adding relevant statements to either only
524
564
* the init or the the init and update arrays, depending on whether or not the value is dynamic.
@@ -543,73 +583,29 @@ export function build_class_directives_object(class_directives, context) {
543
583
* Returns true if attribute is deemed reactive, false otherwise.
544
584
* @param {AST.RegularElement } element
545
585
* @param {Identifier } node_id
546
- * @param {AST.Attribute } attribute
586
+ * @param {string } name
587
+ * @param {Expression } value
547
588
* @param {Array<AST.Attribute | AST.SpreadAttribute> } attributes
548
- * @param {AST.ClassDirective[] } class_directives
549
- * @param {ComponentContext } context
550
- * @returns {boolean }
551
589
*/
552
- function build_element_attribute_update_assignment (
553
- element ,
554
- node_id ,
555
- attribute ,
556
- attributes ,
557
- class_directives ,
558
- context
559
- ) {
560
- const state = context . state ;
561
- const name = get_attribute_name ( element , attribute ) ;
562
- const is_svg = context . state . metadata . namespace === 'svg' || element . name === 'svg' ;
563
- const is_mathml = context . state . metadata . namespace === 'mathml' ;
564
-
565
- const is_autofocus = name === 'autofocus' ;
566
-
567
- let { value, has_state } = build_attribute_value ( attribute . value , context , ( value , metadata ) =>
568
- metadata . has_call
569
- ? // if it's autofocus we will not add this to a template effect so we don't want to get the expression id
570
- // but separately memoize the expression
571
- is_autofocus
572
- ? memoize_expression ( state , value )
573
- : get_expression_id ( state , value )
574
- : value
575
- ) ;
590
+ function build_element_attribute_update ( element , node_id , name , value , attributes ) {
591
+ if ( name === 'muted' ) {
592
+ // Special case for Firefox who needs it set as a property in order to work
593
+ return b . assignment ( '=' , b . member ( node_id , b . id ( 'muted' ) ) , value ) ;
594
+ }
576
595
577
- if ( is_autofocus ) {
578
- state . init . push ( b . stmt ( b . call ( '$.autofocus' , node_id , value ) ) ) ;
579
- return false ;
596
+ if ( name === 'value' ) {
597
+ return b . call ( '$.set_value' , node_id , value ) ;
580
598
}
581
599
582
- // Special case for Firefox who needs it set as a property in order to work
583
- if ( name === 'muted' ) {
584
- if ( ! has_state ) {
585
- state . init . push ( b . stmt ( b . assignment ( '=' , b . member ( node_id , b . id ( 'muted' ) ) , value ) ) ) ;
586
- return false ;
587
- }
588
- state . update . push ( b . stmt ( b . assignment ( '=' , b . member ( node_id , b . id ( 'muted' ) ) , value ) ) ) ;
589
- return false ;
600
+ if ( name === 'checked' ) {
601
+ return b . call ( '$.set_checked' , node_id , value ) ;
590
602
}
591
603
592
- /** @type {Statement } */
593
- let update ;
604
+ if ( name === 'selected' ) {
605
+ return b . call ( '$.set_selected' , node_id , value ) ;
606
+ }
594
607
595
- if ( name === 'class' ) {
596
- return build_set_class (
597
- element ,
598
- node_id ,
599
- attribute ,
600
- value ,
601
- has_state ,
602
- class_directives ,
603
- context ,
604
- ! is_svg && ! is_mathml
605
- ) ;
606
- } else if ( name === 'value' ) {
607
- update = b . stmt ( b . call ( '$.set_value' , node_id , value ) ) ;
608
- } else if ( name === 'checked' ) {
609
- update = b . stmt ( b . call ( '$.set_checked' , node_id , value ) ) ;
610
- } else if ( name === 'selected' ) {
611
- update = b . stmt ( b . call ( '$.set_selected' , node_id , value ) ) ;
612
- } else if (
608
+ if (
613
609
// If we would just set the defaultValue property, it would override the value property,
614
610
// because it is set in the template which implicitly means it's also setting the default value,
615
611
// and if one updates the default value while the input is pristine it will also update the
@@ -620,62 +616,49 @@ function build_element_attribute_update_assignment(
620
616
) ||
621
617
( element . name === 'textarea' && element . fragment . nodes . length > 0 ) )
622
618
) {
623
- update = b . stmt ( b . call ( '$.set_default_value' , node_id , value ) ) ;
624
- } else if (
619
+ return b . call ( '$.set_default_value' , node_id , value ) ;
620
+ }
621
+
622
+ if (
625
623
// See defaultValue comment
626
624
name === 'defaultChecked' &&
627
625
attributes . some (
628
626
( attr ) => attr . type === 'Attribute' && attr . name === 'checked' && attr . value === true
629
627
)
630
628
) {
631
- update = b . stmt ( b . call ( '$.set_default_checked' , node_id , value ) ) ;
632
- } else if ( is_dom_property ( name ) ) {
633
- update = b . stmt ( b . assignment ( '=' , b . member ( node_id , name ) , value ) ) ;
634
- } else {
635
- const callee = name . startsWith ( 'xlink' ) ? '$.set_xlink_attribute' : '$.set_attribute' ;
636
- update = b . stmt (
637
- b . call (
638
- callee ,
639
- node_id ,
640
- b . literal ( name ) ,
641
- value ,
642
- is_ignored ( element , 'hydration_attribute_changed' ) && b . true
643
- )
644
- ) ;
629
+ return b . call ( '$.set_default_checked' , node_id , value ) ;
645
630
}
646
631
647
- if ( has_state ) {
648
- state . update . push ( update ) ;
649
- return true ;
650
- } else {
651
- state . init . push ( update ) ;
652
- return false ;
632
+ if ( is_dom_property ( name ) ) {
633
+ return b . assignment ( '=' , b . member ( node_id , name ) , value ) ;
653
634
}
635
+
636
+ return b . call (
637
+ name . startsWith ( 'xlink' ) ? '$.set_xlink_attribute' : '$.set_attribute' ,
638
+ node_id ,
639
+ b . literal ( name ) ,
640
+ value ,
641
+ is_ignored ( element , 'hydration_attribute_changed' ) && b . true
642
+ ) ;
654
643
}
655
644
656
645
/**
657
- * Like `build_element_attribute_update_assignment ` but without any special attribute treatment.
646
+ * Like `build_element_attribute_update ` but without any special attribute treatment.
658
647
* @param {Identifier } node_id
659
648
* @param {AST.Attribute } attribute
660
649
* @param {ComponentContext } context
661
- * @returns {boolean }
662
650
*/
663
651
function build_custom_element_attribute_update_assignment ( node_id , attribute , context ) {
664
- const state = context . state ;
665
- const name = attribute . name ; // don't lowercase, as we set the element's property, which might be case sensitive
666
- let { value, has_state } = build_attribute_value ( attribute . value , context ) ;
652
+ const { value, has_state } = build_attribute_value ( attribute . value , context ) ;
667
653
668
- const update = b . stmt ( b . call ( '$.set_custom_element_data' , node_id , b . literal ( name ) , value ) ) ;
654
+ // don't lowercase name, as we set the element's property, which might be case sensitive
655
+ const call = b . call ( '$.set_custom_element_data' , node_id , b . literal ( attribute . name ) , value ) ;
669
656
670
- if ( has_state ) {
671
- // this is different from other updates — it doesn't get grouped,
672
- // because set_custom_element_data may not be idempotent
673
- state . init . push ( b . stmt ( b . call ( '$.template_effect' , b . thunk ( update . expression ) ) ) ) ;
674
- return true ;
675
- } else {
676
- state . init . push ( update ) ;
677
- return false ;
678
- }
657
+ // this is different from other updates — it doesn't get grouped,
658
+ // because set_custom_element_data may not be idempotent
659
+ const update = has_state ? b . call ( '$.template_effect' , b . thunk ( call ) ) : call ;
660
+
661
+ context . state . init . push ( b . stmt ( update ) ) ;
679
662
}
680
663
681
664
/**
@@ -686,7 +669,6 @@ function build_custom_element_attribute_update_assignment(node_id, attribute, co
686
669
* @param {Identifier } node_id
687
670
* @param {AST.Attribute } attribute
688
671
* @param {ComponentContext } context
689
- * @returns {boolean }
690
672
*/
691
673
function build_element_special_value_attribute ( element , node_id , attribute , context ) {
692
674
const state = context . state ;
@@ -699,7 +681,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont
699
681
metadata . has_call
700
682
? // if is a select with value we will also invoke `init_select` which need a reference before the template effect so we memoize separately
701
683
is_select_with_value
702
- ? memoize_expression ( context . state , value )
684
+ ? memoize_expression ( state , value )
703
685
: get_expression_id ( state , value )
704
686
: value
705
687
) ;
@@ -743,9 +725,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont
743
725
value ,
744
726
update
745
727
) ;
746
- return true ;
747
728
} else {
748
729
state . init . push ( update ) ;
749
- return false ;
750
730
}
751
731
}
0 commit comments