implement bind:duration and bind:paused

pull/496/head
Rich-Harris 9 years ago
parent 359e67617d
commit 1040db97cb

@ -13,16 +13,17 @@ export default function visitBinding ( generator, block, state, node, attribute
if ( !~state.allUsedContexts.indexOf( context ) ) state.allUsedContexts.push( context ); if ( !~state.allUsedContexts.indexOf( context ) ) state.allUsedContexts.push( context );
}); });
const handler = block.getUniqueName( `${state.parentNode}_change_handler` ); const eventName = getBindingEventName( node, attribute );
const handler = block.getUniqueName( `${state.parentNode}_${eventName}_handler` );
const isMultipleSelect = node.name === 'select' && node.attributes.find( attr => attr.name.toLowerCase() === 'multiple' ); // TODO use getStaticAttributeValue const isMultipleSelect = node.name === 'select' && node.attributes.find( attr => attr.name.toLowerCase() === 'multiple' ); // TODO use getStaticAttributeValue
const type = getStaticAttributeValue( node, 'type' ); const type = getStaticAttributeValue( node, 'type' );
const bindingGroup = attribute.name === 'group' ? getBindingGroup( generator, keypath ) : null; const bindingGroup = attribute.name === 'group' ? getBindingGroup( generator, keypath ) : null;
const value = getBindingValue( generator, block, state, node, attribute, isMultipleSelect, bindingGroup, type ); const value = getBindingValue( generator, block, state, node, attribute, isMultipleSelect, bindingGroup, type );
const eventName = getBindingEventName( node, attribute );
let setter = getSetter({ block, name, keypath, context: '_svelte', attribute, dependencies, value }); let setter = getSetter({ block, name, keypath, context: '_svelte', attribute, dependencies, value });
let updateElement; let updateElement = `${state.parentNode}.${attribute.name} = ${snippet};`;
const lock = block.getUniqueName( `${state.parentNode}_${attribute.name}_updating` );
let updateCondition = `!${lock}`;
// <select> special case // <select> special case
if ( node.name === 'select' ) { if ( node.name === 'select' ) {
@ -77,33 +78,35 @@ export default function visitBinding ( generator, block, state, node, attribute
updateElement = `${state.parentNode}.checked = ${condition};`; updateElement = `${state.parentNode}.checked = ${condition};`;
} }
// <audio bind:currentTime> special case else if ( node.name === 'audio' || node.name === 'video' ) {
else if ( attribute.name === 'currentTime' ) {
generator.hasComplexBindings = true; generator.hasComplexBindings = true;
block.builders.create.addBlock( `${block.component}._bindings.push( ${handler} );` ); block.builders.create.addBlock( `${block.component}._bindings.push( ${handler} );` );
if ( attribute.name === 'currentTime' ) {
setter = deindent` setter = deindent`
if ( ${state.parentNode}.playing ) requestAnimationFrame( ${handler} ); if ( !${state.parentNode}.paused ) requestAnimationFrame( ${handler} );
${setter} ${setter}
`; `;
updateElement = `${state.parentNode}.currentTime = ${snippet};`; updateCondition += ` && !isNaN( ${snippet} )`;
} }
// everything else else if ( attribute.name === 'duration' ) {
else { updateCondition = null;
updateElement = `${state.parentNode}.${attribute.name} = ${snippet};`;
} }
const updating = block.getUniqueName( `${state.parentNode}_updating` ); else if ( attribute.name === 'paused' ) {
updateElement = `${state.parentNode}[ ${snippet} ? 'pause' : 'play' ]()`;
}
}
block.builders.create.addBlock( deindent` block.builders.create.addBlock( deindent`
var ${updating} = false; var ${lock} = false;
function ${handler} () { function ${handler} () {
${updating} = true; ${lock} = true;
${setter} ${setter}
${updating} = false; ${lock} = false;
} }
${generator.helper( 'addEventListener' )}( ${state.parentNode}, '${eventName}', ${handler} ); ${generator.helper( 'addEventListener' )}( ${state.parentNode}, '${eventName}', ${handler} );
@ -111,15 +114,23 @@ export default function visitBinding ( generator, block, state, node, attribute
if ( node.name !== 'audio' && node.name !== 'video' ) node.initialUpdate = updateElement; if ( node.name !== 'audio' && node.name !== 'video' ) node.initialUpdate = updateElement;
block.builders.update.addLine( deindent` if ( updateCondition !== null ) {
if ( !${updating} ) { // audio/video duration is read-only, it never updates
block.builders.update.addBlock( deindent`
if ( ${updateCondition} ) {
${updateElement} ${updateElement}
} }
` ); ` );
}
block.builders.destroy.addLine( deindent` block.builders.destroy.addLine( deindent`
${generator.helper( 'removeEventListener' )}( ${state.parentNode}, '${eventName}', ${handler} ); ${generator.helper( 'removeEventListener' )}( ${state.parentNode}, '${eventName}', ${handler} );
` ); ` );
if ( attribute.name === 'paused' ) {
block.builders.create.addLine( `${generator.helper( 'addEventListener' )}( ${state.parentNode}, 'play', ${handler} );` );
block.builders.destroy.addLine( `${generator.helper( 'removeEventListener' )}( ${state.parentNode}, 'play', ${handler} );` );
}
} }
function getBindingEventName ( node, attribute ) { function getBindingEventName ( node, attribute ) {
@ -130,14 +141,10 @@ function getBindingEventName ( node, attribute ) {
return type === 'checkbox' || type === 'radio' ? 'change' : 'input'; return type === 'checkbox' || type === 'radio' ? 'change' : 'input';
} }
if ( node.name === 'textarea' ) { if ( node.name === 'textarea' ) return 'input';
return 'input'; if ( attribute.name === 'currentTime' ) return 'timeupdate';
} if ( attribute.name === 'duration' ) return 'durationchange';
if ( attribute.name === 'paused' ) return 'pause';
// <audio bind:currentTime>
if ( attribute.name === 'currentTime' ) {
return 'timeupdate';
}
return 'change'; return 'change';
} }

@ -35,7 +35,7 @@ export default function validateElement ( validator, node ) {
} }
} }
else if ( name === 'currentTime' || name === 'duration' ) { else if ( name === 'currentTime' || name === 'duration' || name === 'paused' ) {
if ( node.name !== 'audio' && node.name !== 'video' ) { if ( node.name !== 'audio' && node.name !== 'video' ) {
validator.error( `'${name}' binding can only be used with <audio> or <video>` ); validator.error( `'${name}' binding can only be used with <audio> or <video>` );
} }

@ -0,0 +1,24 @@
export default {
solo: true,
test ( assert, component, target, window ) {
assert.equal( component.get( 't' ), 0 );
assert.equal( component.get( 'd' ), 0 );
assert.equal( component.get( 'paused' ), true );
const audio = target.querySelector( 'audio' );
const timeupdate = new window.Event( 'timeupdate' );
const durationchange = new window.Event( 'durationchange' );
audio.currentTime = 10;
audio.duration = 20;
audio.dispatchEvent( timeupdate );
audio.dispatchEvent( durationchange );
audio.play();
assert.equal( component.get( 't' ), 10 );
assert.equal( component.get( 'd' ), 0 ); // not 20, because read-only. Not sure how to test this!
assert.equal( component.get( 'paused' ), true ); // ditto...
component.destroy();
}
};

@ -0,0 +1 @@
<audio bind:currentTime='t' bind:duration='d' bind:paused src='music.mp3'></audio>

@ -1,16 +0,0 @@
export default {
solo: true,
test ( assert, component, target, window ) {
assert.equal( component.get( 't' ), 0 );
const audio = target.querySelector( 'audio' );
const event = new window.Event( 'timeupdate' );
audio.currentTime = 10;
audio.dispatchEvent( event );
assert.equal( component.get( 't' ), 10 );
component.destroy();
}
};

@ -1 +0,0 @@
<audio bind:currentTime='t' src='music.mp3'></audio>
Loading…
Cancel
Save