@@ -8,6 +8,13 @@ import { State } from '../../interfaces';
8
8
import getObject from '../../../../utils/getObject' ;
9
9
import getTailSnippet from '../../../../utils/getTailSnippet' ;
10
10
11
+ const readOnlyMediaAttributes = new Set ( [
12
+ 'duration' ,
13
+ 'buffered' ,
14
+ 'seekable' ,
15
+ 'played'
16
+ ] ) ;
17
+
11
18
export default function visitBinding (
12
19
generator : DomGenerator ,
13
20
block : Block ,
@@ -25,9 +32,9 @@ export default function visitBinding(
25
32
state . allUsedContexts . push ( context ) ;
26
33
} ) ;
27
34
28
- const eventName = getBindingEventName ( node , attribute ) ;
35
+ const eventNames = getBindingEventName ( node , attribute ) ;
29
36
const handler = block . getUniqueName (
30
- `${ state . parentNode } _${ eventName } _handler`
37
+ `${ state . parentNode } _${ eventNames . join ( '_' ) } _handler`
31
38
) ;
32
39
const isMultipleSelect =
33
40
node . name === 'select' &&
@@ -38,24 +45,28 @@ export default function visitBinding(
38
45
const bindingGroup = attribute . name === 'group'
39
46
? getBindingGroup ( generator , attribute . value )
40
47
: null ;
48
+
49
+ const isMediaElement = node . name === 'audio' || node . name === 'video' ;
50
+ const isReadOnly = isMediaElement && readOnlyMediaAttributes . has ( attribute . name )
51
+
41
52
const value = getBindingValue (
42
53
generator ,
43
54
block ,
44
55
state ,
45
56
node ,
46
57
attribute ,
47
58
isMultipleSelect ,
59
+ isMediaElement ,
48
60
bindingGroup ,
49
61
type
50
62
) ;
51
63
52
64
let setter = getSetter ( block , name , snippet , state . parentNode , attribute , dependencies , value ) ;
53
65
let updateElement = `${ state . parentNode } .${ attribute . name } = ${ snippet } ;` ;
54
66
55
- const needsLock = node . name !== 'input' || ! / r a d i o | c h e c k b o x | r a n g e | c o l o r / . test ( type ) ; // TODO others?
67
+ const needsLock = ! isReadOnly && node . name !== 'input' || ! / r a d i o | c h e c k b o x | r a n g e | c o l o r / . test ( type ) ; // TODO others?
56
68
const lock = `#${ state . parentNode } _updating` ;
57
69
let updateConditions = needsLock ? [ `!${ lock } ` ] : [ ] ;
58
- let readOnly = false ;
59
70
60
71
if ( needsLock ) block . addVariable ( lock , 'false' ) ;
61
72
@@ -115,7 +126,7 @@ export default function visitBinding(
115
126
) ;
116
127
117
128
updateElement = `${ state . parentNode } .checked = ${ condition } ;` ;
118
- } else if ( node . name === 'audio' || node . name === 'video' ) {
129
+ } else if ( isMediaElement ) {
119
130
generator . hasComplexBindings = true ;
120
131
block . builders . hydrate . addBlock ( `#component._root._beforecreate.push(${ handler } );` ) ;
121
132
@@ -129,37 +140,13 @@ export default function visitBinding(
129
140
` ;
130
141
131
142
updateConditions . push ( `!isNaN(${ snippet } )` ) ;
132
- } else if ( attribute . name === 'duration' ) {
133
- readOnly = true ;
134
143
} else if ( attribute . name === 'paused' ) {
135
144
// this is necessary to prevent the audio restarting by itself
136
145
const last = block . getUniqueName ( `${ state . parentNode } _paused_value` ) ;
137
146
block . addVariable ( last , 'true' ) ;
138
147
139
148
updateConditions = [ `${ last } !== (${ last } = ${ snippet } )` ] ;
140
149
updateElement = `${ state . parentNode } [${ last } ? "pause" : "play"]();` ;
141
- } else if ( attribute . name === 'buffered' ) {
142
- const frame = block . getUniqueName ( `${ state . parentNode } _animationframe` ) ;
143
- block . addVariable ( frame ) ;
144
- setter = deindent `
145
- cancelAnimationFrame(${ frame } );
146
- ${ frame } = requestAnimationFrame(${ handler } );
147
- ${ setter }
148
- ` ;
149
-
150
- updateConditions . push ( `${ snippet } .start` ) ;
151
- readOnly = true ;
152
- } else if ( attribute . name === 'seekable' || attribute . name === 'played' ) {
153
- const frame = block . getUniqueName ( `${ state . parentNode } _animationframe` ) ;
154
- block . addVariable ( frame ) ;
155
- setter = deindent `
156
- cancelAnimationFrame(${ frame } );
157
- if (!${ state . parentNode } .paused) ${ frame } = requestAnimationFrame(${ handler } );
158
- ${ setter }
159
- ` ;
160
-
161
- updateConditions . push ( `${ snippet } .start` ) ;
162
- readOnly = true ;
163
150
}
164
151
}
165
152
@@ -183,21 +170,23 @@ export default function visitBinding(
183
170
@removeListener(${ state . parentNode } , "change", ${ handler } );
184
171
` ) ;
185
172
} else {
186
- block . builders . hydrate . addLine (
187
- `@addListener(${ state . parentNode } , "${ eventName } ", ${ handler } );`
188
- ) ;
189
-
190
- block . builders . destroy . addLine (
191
- `@removeListener(${ state . parentNode } , "${ eventName } ", ${ handler } );`
192
- ) ;
173
+ eventNames . forEach ( eventName => {
174
+ block . builders . hydrate . addLine (
175
+ `@addListener(${ state . parentNode } , "${ eventName } ", ${ handler } );`
176
+ ) ;
177
+
178
+ block . builders . destroy . addLine (
179
+ `@removeListener(${ state . parentNode } , "${ eventName } ", ${ handler } );`
180
+ ) ;
181
+ } ) ;
193
182
}
194
183
195
- if ( node . name !== 'audio' && node . name !== 'video' ) {
184
+ if ( ! isMediaElement ) {
196
185
node . initialUpdate = updateElement ;
197
186
node . initialUpdateNeedsStateObject = ! block . contexts . has ( name ) ;
198
187
}
199
188
200
- if ( ! readOnly ) { // audio/video duration is read-only, it never updates
189
+ if ( ! isReadOnly ) { // audio/video duration is read-only, it never updates
201
190
if ( updateConditions . length ) {
202
191
block . builders . update . addBlock ( deindent `
203
192
if (${ updateConditions . join ( ' && ' ) } ) {
@@ -228,18 +217,18 @@ function getBindingEventName(node: Node, attribute: Node) {
228
217
) ;
229
218
const type = typeAttribute ? typeAttribute . value [ 0 ] . data : 'text' ; // TODO in validation, should throw if type attribute is not static
230
219
231
- return type === 'checkbox' || type === 'radio' ? 'change' : 'input' ;
220
+ return [ type === 'checkbox' || type === 'radio' ? 'change' : 'input' ] ;
232
221
}
233
222
234
- if ( node . name === 'textarea' ) return 'input' ;
235
- if ( attribute . name === 'currentTime' ) return 'timeupdate' ;
236
- if ( attribute . name === 'duration' ) return 'durationchange' ;
237
- if ( attribute . name === 'paused' ) return 'pause' ;
238
- if ( attribute . name === 'buffered' ) return 'progress' ;
239
- if ( attribute . name === 'seekable' ) return 'timeupdate' ;
240
- if ( attribute . name === 'played' ) return 'timeupdate' ;
223
+ if ( node . name === 'textarea' ) return [ 'input' ] ;
224
+ if ( attribute . name === 'currentTime' ) return [ 'timeupdate' ] ;
225
+ if ( attribute . name === 'duration' ) return [ 'durationchange' ] ;
226
+ if ( attribute . name === 'paused' ) return [ 'pause' ] ;
227
+ if ( attribute . name === 'buffered' ) return [ 'progress' , 'loadedmetadata' ] ;
228
+ if ( attribute . name === 'seekable' ) return [ 'loadedmetadata' ] ;
229
+ if ( attribute . name === 'played' ) return [ 'timeupdate' ] ;
241
230
242
- return 'change' ;
231
+ return [ 'change' ] ;
243
232
}
244
233
245
234
function getBindingValue (
@@ -249,6 +238,7 @@ function getBindingValue(
249
238
node : Node ,
250
239
attribute : Node ,
251
240
isMultipleSelect : boolean ,
241
+ isMediaElement : boolean ,
252
242
bindingGroup : number ,
253
243
type : string
254
244
) {
@@ -276,6 +266,10 @@ function getBindingValue(
276
266
return `@toNumber(${ state . parentNode } .${ attribute . name } )` ;
277
267
}
278
268
269
+ if ( isMediaElement && attribute . name === 'buffered' || attribute . name === 'seekable' || attribute . name === 'played' ) {
270
+ return `@timeRangesToArray(${ state . parentNode } .${ attribute . name } )`
271
+ }
272
+
279
273
// everything else
280
274
return `${ state . parentNode } .${ attribute . name } ` ;
281
275
}
0 commit comments