@@ -7,7 +7,6 @@ import { b, x } from 'code-red';
7
7
import Expression from '../../../nodes/shared/Expression' ;
8
8
import Text from '../../../nodes/Text' ;
9
9
import { changed } from '../shared/changed' ;
10
- import { Literal } from 'estree' ;
11
10
12
11
export default class AttributeWrapper {
13
12
node : Attribute ;
@@ -72,85 +71,69 @@ export default class AttributeWrapper {
72
71
const is_legacy_input_type = element . renderer . component . compile_options . legacy && name === 'type' && this . parent . node . name === 'input' ;
73
72
74
73
const dependencies = this . node . get_dependencies ( ) ;
75
- if ( dependencies . length > 0 ) {
76
- let value ;
77
-
78
- // TODO some of this code is repeated in Tag.ts — would be good to
79
- // DRY it out if that's possible without introducing crazy indirection
80
- if ( this . node . chunks . length === 1 ) {
81
- // single {tag} — may be a non-string
82
- value = ( this . node . chunks [ 0 ] as Expression ) . manipulate ( block ) ;
83
- } else {
84
- value = this . node . name === 'class'
85
- ? this . get_class_name_text ( )
86
- : this . render_chunks ( ) . reduce ( ( lhs , rhs ) => x `${ lhs } + ${ rhs } ` ) ;
87
-
88
- // '{foo} {bar}' — treat as string concatenation
89
- if ( this . node . chunks [ 0 ] . type !== 'Text' ) {
90
- value = x `"" + ${ value } ` ;
91
- }
92
- }
74
+ const value = this . get_value ( block ) ;
93
75
94
- const is_select_value_attribute =
95
- name === 'value' && element . node . name === 'select' ;
76
+ const is_select_value_attribute =
77
+ name === 'value' && element . node . name === 'select' ;
96
78
97
- const should_cache = is_select_value_attribute ; // TODO is this necessary?
79
+ const should_cache = is_select_value_attribute ; // TODO is this necessary?
98
80
99
- const last = should_cache && block . get_unique_name (
100
- `${ element . var . name } _${ name . replace ( / [ ^ a - z A - Z _ $ ] / g, '_' ) } _value`
101
- ) ;
81
+ const last = should_cache && block . get_unique_name (
82
+ `${ element . var . name } _${ name . replace ( / [ ^ a - z A - Z _ $ ] / g, '_' ) } _value`
83
+ ) ;
102
84
103
- if ( should_cache ) block . add_variable ( last ) ;
104
-
105
- let updater ;
106
- const init = should_cache ? x `${ last } = ${ value } ` : value ;
107
-
108
- if ( is_legacy_input_type ) {
109
- block . chunks . hydrate . push (
110
- b `@set_input_type(${ element . var } , ${ init } );`
111
- ) ;
112
- updater = b `@set_input_type(${ element . var } , ${ should_cache ? last : value } );` ;
113
- } else if ( is_select_value_attribute ) {
114
- // annoying special case
115
- const is_multiple_select = element . node . get_static_attribute_value ( 'multiple' ) ;
116
- const i = block . get_unique_name ( 'i' ) ;
117
- const option = block . get_unique_name ( 'option' ) ;
118
-
119
- const if_statement = is_multiple_select
120
- ? b `
121
- ${ option } .selected = ~${ last } .indexOf(${ option } .__value);`
122
- : b `
123
- if (${ option } .__value === ${ last } ) {
124
- ${ option } .selected = true;
125
- ${ { type : 'BreakStatement' } } ;
126
- }` ; // TODO the BreakStatement is gross, but it's unsyntactic otherwise...
127
-
128
- updater = b `
129
- for (var ${ i } = 0; ${ i } < ${ element . var } .options.length; ${ i } += 1) {
130
- var ${ option } = ${ element . var } .options[${ i } ];
131
-
132
- ${ if_statement }
133
- }
134
- ` ;
135
-
136
- block . chunks . mount . push ( b `
137
- ${ last } = ${ value } ;
138
- ${ updater }
139
- ` ) ;
140
- } else if ( property_name ) {
141
- block . chunks . hydrate . push (
142
- b `${ element . var } .${ property_name } = ${ init } ;`
143
- ) ;
144
- updater = block . renderer . options . dev
145
- ? b `@prop_dev(${ element . var } , "${ property_name } ", ${ should_cache ? last : value } );`
146
- : b `${ element . var } .${ property_name } = ${ should_cache ? last : value } ;` ;
147
- } else {
148
- block . chunks . hydrate . push (
149
- b `${ method } (${ element . var } , "${ name } ", ${ init } );`
150
- ) ;
151
- updater = b `${ method } (${ element . var } , "${ name } ", ${ should_cache ? last : value } );` ;
152
- }
85
+ if ( should_cache ) block . add_variable ( last ) ;
86
+
87
+ let updater ;
88
+ const init = should_cache ? x `${ last } = ${ value } ` : value ;
89
+
90
+ if ( is_legacy_input_type ) {
91
+ block . chunks . hydrate . push (
92
+ b `@set_input_type(${ element . var } , ${ init } );`
93
+ ) ;
94
+ updater = b `@set_input_type(${ element . var } , ${ should_cache ? last : value } );` ;
95
+ } else if ( is_select_value_attribute ) {
96
+ // annoying special case
97
+ const is_multiple_select = element . node . get_static_attribute_value ( 'multiple' ) ;
98
+ const i = block . get_unique_name ( 'i' ) ;
99
+ const option = block . get_unique_name ( 'option' ) ;
100
+
101
+ const if_statement = is_multiple_select
102
+ ? b `
103
+ ${ option } .selected = ~${ last } .indexOf(${ option } .__value);`
104
+ : b `
105
+ if (${ option } .__value === ${ last } ) {
106
+ ${ option } .selected = true;
107
+ ${ { type : 'BreakStatement' } } ;
108
+ }` ; // TODO the BreakStatement is gross, but it's unsyntactic otherwise...
109
+
110
+ updater = b `
111
+ for (var ${ i } = 0; ${ i } < ${ element . var } .options.length; ${ i } += 1) {
112
+ var ${ option } = ${ element . var } .options[${ i } ];
113
+
114
+ ${ if_statement }
115
+ }
116
+ ` ;
117
+
118
+ block . chunks . mount . push ( b `
119
+ ${ last } = ${ value } ;
120
+ ${ updater }
121
+ ` ) ;
122
+ } else if ( property_name ) {
123
+ block . chunks . hydrate . push (
124
+ b `${ element . var } .${ property_name } = ${ init } ;`
125
+ ) ;
126
+ updater = block . renderer . options . dev
127
+ ? b `@prop_dev(${ element . var } , "${ property_name } ", ${ should_cache ? last : value } );`
128
+ : b `${ element . var } .${ property_name } = ${ should_cache ? last : value } ;` ;
129
+ } else {
130
+ block . chunks . hydrate . push (
131
+ b `${ method } (${ element . var } , "${ name } ", ${ init } );`
132
+ ) ;
133
+ updater = b `${ method } (${ element . var } , "${ name } ", ${ should_cache ? last : value } );` ;
134
+ }
153
135
136
+ if ( dependencies . length > 0 ) {
154
137
let condition = changed ( dependencies ) ;
155
138
156
139
if ( should_cache ) {
@@ -165,23 +148,11 @@ export default class AttributeWrapper {
165
148
if (${ condition } ) {
166
149
${ updater }
167
150
}` ) ;
168
- } else {
169
- const value = this . node . get_value ( block ) ;
170
-
171
- const statement = (
172
- is_legacy_input_type
173
- ? b `@set_input_type(${ element . var } , ${ value } );`
174
- : property_name
175
- ? b `${ element . var } .${ property_name } = ${ value } ;`
176
- : b `${ method } (${ element . var } , "${ name } ", ${ value . type === 'Literal' && ( value as Literal ) . value === true ? x `""` : value } );`
177
- ) ;
178
-
179
- block . chunks . hydrate . push ( statement ) ;
151
+ }
180
152
181
- // special case – autofocus. has to be handled in a bit of a weird way
182
- if ( this . node . is_true && name === 'autofocus' ) {
183
- block . autofocus = element . var ;
184
- }
153
+ // special case – autofocus. has to be handled in a bit of a weird way
154
+ if ( this . node . is_true && name === 'autofocus' ) {
155
+ block . autofocus = element . var ;
185
156
}
186
157
187
158
if ( is_indirectly_bound_value ) {
@@ -199,6 +170,36 @@ export default class AttributeWrapper {
199
170
return metadata ;
200
171
}
201
172
173
+ get_value ( block ) {
174
+ if ( this . node . is_true ) {
175
+ const metadata = this . get_metadata ( ) ;
176
+ if ( metadata && boolean_attribute . has ( metadata . property_name . toLowerCase ( ) ) ) {
177
+ return x `true` ;
178
+ }
179
+ return x `""` ;
180
+ }
181
+ if ( this . node . chunks . length === 0 ) return x `""` ;
182
+
183
+ // TODO some of this code is repeated in Tag.ts — would be good to
184
+ // DRY it out if that's possible without introducing crazy indirection
185
+ if ( this . node . chunks . length === 1 ) {
186
+ return this . node . chunks [ 0 ] . type === 'Text'
187
+ ? string_literal ( ( this . node . chunks [ 0 ] as Text ) . data )
188
+ : ( this . node . chunks [ 0 ] as Expression ) . manipulate ( block ) ;
189
+ }
190
+
191
+ let value = this . node . name === 'class'
192
+ ? this . get_class_name_text ( )
193
+ : this . render_chunks ( ) . reduce ( ( lhs , rhs ) => x `${ lhs } + ${ rhs } ` ) ;
194
+
195
+ // '{foo} {bar}' — treat as string concatenation
196
+ if ( this . node . chunks [ 0 ] . type !== 'Text' ) {
197
+ value = x `"" + ${ value } ` ;
198
+ }
199
+
200
+ return value ;
201
+ }
202
+
202
203
get_class_name_text ( ) {
203
204
const scoped_css = this . node . chunks . some ( ( chunk : Text ) => chunk . synthetic ) ;
204
205
const rendered = this . render_chunks ( ) ;
@@ -292,3 +293,32 @@ Object.keys(attribute_lookup).forEach(name => {
292
293
const metadata = attribute_lookup [ name ] ;
293
294
if ( ! metadata . property_name ) metadata . property_name = name ;
294
295
} ) ;
296
+
297
+ // source: https://html.spec.whatwg.org/multipage/indices.html
298
+ const boolean_attribute = new Set ( [
299
+ 'allowfullscreen' ,
300
+ 'allowpaymentrequest' ,
301
+ 'async' ,
302
+ 'autofocus' ,
303
+ 'autoplay' ,
304
+ 'checked' ,
305
+ 'controls' ,
306
+ 'default' ,
307
+ 'defer' ,
308
+ 'disabled' ,
309
+ 'formnovalidate' ,
310
+ 'hidden' ,
311
+ 'ismap' ,
312
+ 'itemscope' ,
313
+ 'loop' ,
314
+ 'multiple' ,
315
+ 'muted' ,
316
+ 'nomodule' ,
317
+ 'novalidate' ,
318
+ 'open' ,
319
+ 'playsinline' ,
320
+ 'readonly' ,
321
+ 'required' ,
322
+ 'reversed' ,
323
+ 'selected'
324
+ ] ) ;
0 commit comments