1- /** @import { Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */
1+ /** @import { ArrayExpression, Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */
22/** @import { AST } from '#compiler' */
33/** @import { SourceLocation } from '#shared' */
44/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
@@ -20,9 +20,9 @@ import { build_getter } from '../utils.js';
2020import {
2121 get_attribute_name ,
2222 build_attribute_value ,
23- build_style_directives ,
2423 build_set_attributes ,
25- build_set_class
24+ build_set_class ,
25+ build_set_style
2626} from './shared/element.js' ;
2727import { process_children } from './shared/fragment.js' ;
2828import {
@@ -215,13 +215,18 @@ export function RegularElement(node, context) {
215215
216216 const node_id = context . state . node ;
217217
218- // Then do attributes
219- let is_attributes_reactive = has_spread ;
220-
221218 if ( has_spread ) {
222219 const attributes_id = b . id ( context . state . scope . generate ( 'attributes' ) ) ;
223220
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+ ) ;
225230
226231 // If value binding exists, that one takes care of calling $.init_select
227232 if ( node . name === 'select' && ! bindings . has ( 'value' ) ) {
@@ -262,11 +267,13 @@ export function RegularElement(node, context) {
262267 }
263268
264269 const name = get_attribute_name ( node , attribute ) ;
270+
265271 if (
266272 ! is_custom_element &&
267273 ! cannot_be_set_statically ( attribute . name ) &&
268274 ( 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 )
270277 ) {
271278 let value = is_text_attribute ( attribute ) ? attribute . value [ 0 ] . data : true ;
272279
@@ -287,27 +294,30 @@ export function RegularElement(node, context) {
287294 } `
288295 ) ;
289296 }
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 ) ;
292315
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+ }
305318 }
306319 }
307320
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-
311321 if (
312322 is_load_error_element ( node . name ) &&
313323 ( has_spread || has_use || lookup . has ( 'onload' ) || lookup . has ( 'onerror' ) )
@@ -519,6 +529,36 @@ export function build_class_directives_object(class_directives, context) {
519529 return b . object ( properties ) ;
520530}
521531
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+
522562/**
523563 * Serializes an assignment to an element property by adding relevant statements to either only
524564 * 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) {
543583 * Returns true if attribute is deemed reactive, false otherwise.
544584 * @param {AST.RegularElement } element
545585 * @param {Identifier } node_id
546- * @param {AST.Attribute } attribute
586+ * @param {string } name
587+ * @param {Expression } value
547588 * @param {Array<AST.Attribute | AST.SpreadAttribute> } attributes
548- * @param {AST.ClassDirective[] } class_directives
549- * @param {ComponentContext } context
550- * @returns {boolean }
551589 */
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+ }
576595
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 ) ;
580598 }
581599
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 ) ;
590602 }
591603
592- /** @type {Statement } */
593- let update ;
604+ if ( name === 'selected' ) {
605+ return b . call ( '$.set_selected' , node_id , value ) ;
606+ }
594607
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 (
613609 // If we would just set the defaultValue property, it would override the value property,
614610 // because it is set in the template which implicitly means it's also setting the default value,
615611 // 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(
620616 ) ||
621617 ( element . name === 'textarea' && element . fragment . nodes . length > 0 ) )
622618 ) {
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 (
625623 // See defaultValue comment
626624 name === 'defaultChecked' &&
627625 attributes . some (
628626 ( attr ) => attr . type === 'Attribute' && attr . name === 'checked' && attr . value === true
629627 )
630628 ) {
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 ) ;
645630 }
646631
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 ) ;
653634 }
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+ ) ;
654643}
655644
656645/**
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.
658647 * @param {Identifier } node_id
659648 * @param {AST.Attribute } attribute
660649 * @param {ComponentContext } context
661- * @returns {boolean }
662650 */
663651function 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 ) ;
667653
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 ) ;
669656
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 ) ) ;
679662}
680663
681664/**
@@ -686,7 +669,6 @@ function build_custom_element_attribute_update_assignment(node_id, attribute, co
686669 * @param {Identifier } node_id
687670 * @param {AST.Attribute } attribute
688671 * @param {ComponentContext } context
689- * @returns {boolean }
690672 */
691673function build_element_special_value_attribute ( element , node_id , attribute , context ) {
692674 const state = context . state ;
@@ -699,7 +681,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont
699681 metadata . has_call
700682 ? // if is a select with value we will also invoke `init_select` which need a reference before the template effect so we memoize separately
701683 is_select_with_value
702- ? memoize_expression ( context . state , value )
684+ ? memoize_expression ( state , value )
703685 : get_expression_id ( state , value )
704686 : value
705687 ) ;
@@ -743,9 +725,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont
743725 value ,
744726 update
745727 ) ;
746- return true ;
747728 } else {
748729 state . init . push ( update ) ;
749- return false ;
750730 }
751731}
0 commit comments