Newer
Older
function isOverview() {
return dom.wrapper.classList.contains( 'overview' );
/**
* Checks if the current or specified slide is vertical
* (nested within another slide).
*
* @param {HTMLElement} slide [optional] The slide to check
* orientation of
*/
function isVerticalSlide( slide ) {
// Prefer slide argument, otherwise use current slide
slide = slide ? slide : currentSlide;
return slide && !!slide.parentNode.nodeName.match( /section/i );
}
/**
* Handling the fullscreen functionality via the fullscreen API
*
* @see http://fullscreen.spec.whatwg.org/
* @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
*/
function enterFullscreen() {
// Check which implementation is available
var requestMethod = element.requestFullScreen ||

Marc van Gend
committed
element.webkitRequestFullscreen ||
element.webkitRequestFullScreen ||
element.mozRequestFullScreen ||
element.msRequestFullScreen;
if( requestMethod ) {
requestMethod.apply( element );
}

Hakim El Hattab
committed
/**
* Enters the paused mode which fades everything on screen to

Hakim El Hattab
committed
* black.
*/
function pause() {
var wasPaused = dom.wrapper.classList.contains( 'paused' );
cancelAutoSlide();

Hakim El Hattab
committed
dom.wrapper.classList.add( 'paused' );
if( wasPaused === false ) {
dispatchEvent( 'paused' );
}

Hakim El Hattab
committed
}
/**
* Exits from the paused mode.
*/
function resume() {
var wasPaused = dom.wrapper.classList.contains( 'paused' );
dom.wrapper.classList.remove( 'paused' );
if( wasPaused ) {
dispatchEvent( 'resumed' );
}

Hakim El Hattab
committed
}
/**
* Toggles the paused mode on and off.
*/
function togglePause() {

Hakim El Hattab
committed
if( isPaused() ) {
resume();
}
else {
pause();
}

Hakim El Hattab
committed
}
/**
* Checks if we are currently in the paused mode.
*/
function isPaused() {

Hakim El Hattab
committed
return dom.wrapper.classList.contains( 'paused' );

Hakim El Hattab
committed
}
* Steps from the current point in the presentation to the
* slide which matches the specified horizontal and vertical
* indices.

Hakim El Hattab
committed
*
* @param {int} h Horizontal index of the target slide
* @param {int} v Vertical index of the target slide
* @param {int} f Optional index of a fragment within the
* target slide to activate
* @param {int} o Optional origin for use in multimaster environments

Hakim El Hattab
committed
// Remember where we were at before
previousSlide = currentSlide;
// Query all horizontal slides in the deck
var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR );
// If no vertical index is specified and the upcoming slide is a
// stack, resume at its previous vertical index

Hakim El Hattab
committed
if( v === undefined ) {
v = getPreviousVerticalIndex( horizontalSlides[ h ] );
// If we were on a vertical stack, remember what vertical index
// it was on so we can resume at the same position when returning
if( previousSlide && previousSlide.parentNode && previousSlide.parentNode.classList.contains( 'stack' ) ) {

Hakim El Hattab
committed
setPreviousVerticalIndex( previousSlide.parentNode, indexv );
// Remember the state before this slide
var stateBefore = state.concat();
// Reset the state array
state.length = 0;

Hakim El Hattab
committed
var indexhBefore = indexh,
indexvBefore = indexv;
// Activate and transition to the new slide
indexh = updateSlides( HORIZONTAL_SLIDES_SELECTOR, h === undefined ? indexh : h );
indexv = updateSlides( VERTICAL_SLIDES_SELECTOR, v === undefined ? indexv : v );

Hakim El Hattab
committed
// Apply the new state
stateLoop: for( var i = 0, len = state.length; i < len; i++ ) {
// Check if this state existed on the previous slide. If it
// did, we will avoid adding it repeatedly
for( var j = 0; j < stateBefore.length; j++ ) {
if( stateBefore[j] === state[i] ) {
stateBefore.splice( j, 1 );
continue stateLoop;
}
}

Hakim El Hattab
committed
document.documentElement.classList.add( state[i] );

Hakim El Hattab
committed
// Dispatch custom event matching the state's name
dispatchEvent( state[i] );
// Clean up the remains of the previous state
while( stateBefore.length ) {
document.documentElement.classList.remove( stateBefore.pop() );

Hakim El Hattab
committed
}
// If the overview is active, re-activate it to update positions
if( isOverview() ) {
activateOverview();
}
// Update the URL hash after a delay since updating it mid-transition
// is likely to cause visual lag
writeURL( 1500 );

Hakim El Hattab
committed

Hakim El Hattab
committed
// Find the current horizontal slide and any possible vertical slides
// within it
var currentHorizontalSlide = horizontalSlides[ indexh ],
currentVerticalSlides = currentHorizontalSlide.querySelectorAll( 'section' );

Hakim El Hattab
committed

Hakim El Hattab
committed
// Store references to the previous and current slides
currentSlide = currentVerticalSlides[ indexv ] || currentHorizontalSlide;

Hakim El Hattab
committed
// Show fragment, if specified
if( typeof f !== 'undefined' ) {

Hakim El Hattab
committed
var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment' ) );
toArray( fragments ).forEach( function( fragment, indexf ) {
if( indexf < f ) {
fragment.classList.add( 'visible' );
}
else {
fragment.classList.remove( 'visible' );
}
} );
}

Hakim El Hattab
committed
// Dispatch an event if the slide changed
if( indexh !== indexhBefore || indexv !== indexvBefore ) {
dispatchEvent( 'slidechanged', {

Hakim El Hattab
committed
'indexv': indexv,

Hakim El Hattab
committed
'previousSlide': previousSlide,
'currentSlide': currentSlide,
'origin': o
} );
}

Hakim El Hattab
committed
else {
// Ensure that the previous slide is never the same as the current
previousSlide = null;
}

Hakim El Hattab
committed
// Solves an edge case where the previous slide maintains the
// 'present' class when navigating between adjacent vertical

Hakim El Hattab
committed
// stacks
if( previousSlide ) {
previousSlide.classList.remove( 'present' );
// Reset all slides upon navigate to home
// Issue: #285
if ( document.querySelector( HOME_SLIDE_SELECTOR ).classList.contains( 'present' ) ) {
// Launch async task
setTimeout( function () {
var slides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR + '.stack') ), i;
for( i in slides ) {
if( slides[i] ) {
// Reset stack
setPreviousVerticalIndex( slides[i], 0 );
}
}
}, 0 );
}

Hakim El Hattab
committed
}
// Handle embedded content
stopEmbeddedContent( previousSlide );
startEmbeddedContent( currentSlide );
updateControls();
updateProgress();
updateBackground();
/**
* Syncs the presentation with the current DOM. Useful
* when new slides or control elements are added or when
* the configuration has changed.
*/
function sync() {
// Subscribe to input
removeEventListeners();
addEventListeners();
// Force a layout to make sure the current config is accounted for
layout();
// Reflect the current autoSlide value
autoSlide = config.autoSlide;
// Start auto-sliding if it's enabled
cueAutoSlide();
// Re-create the slide backgrounds
createBackgrounds();
updateControls();
updateProgress();
updateBackground();

Hakim El Hattab
committed
/**
* Updates one dimension of slides by showing the slide
* with the specified index.

Hakim El Hattab
committed
* @param {String} selector A CSS selector that will fetch
* the group of slides we are working with
* @param {Number} index The index of the slide that should be
* shown

Hakim El Hattab
committed
* @return {Number} The index of the slide that is now shown,
* might differ from the passed in index if it was out of

Hakim El Hattab
committed
* bounds.
*/
function updateSlides( selector, index ) {

Hakim El Hattab
committed
// Select all slides and convert the NodeList result to
// an array
var slides = toArray( document.querySelectorAll( selector ) ),

Hakim El Hattab
committed
slidesLength = slides.length;

Hakim El Hattab
committed
if( slidesLength ) {
// Should the index loop?
if( config.loop ) {
index %= slidesLength;
if( index < 0 ) {
index = slidesLength + index;
}
}

Hakim El Hattab
committed
// Enforce max and minimum index bounds
index = Math.max( Math.min( index, slidesLength - 1 ), 0 );

Hakim El Hattab
committed
for( var i = 0; i < slidesLength; i++ ) {
var element = slides[i];
// Optimization; hide all slides that are three or more steps

Hakim El Hattab
committed
// away from the present slide
if( isOverview() === false ) {

Hakim El Hattab
committed
// The distance loops so that it measures 1 between the first
// and last slides
var distance = Math.abs( ( index - i ) % ( slidesLength - 3 ) ) || 0;
element.style.display = distance > 3 ? 'none' : 'block';
}
var reverse = config.rtl && !isVerticalSlide( element );
element.classList.remove( 'past' );
element.classList.remove( 'present' );
element.classList.remove( 'future' );

Hakim El Hattab
committed
if( i < index ) {
// Any element previous to index is given the 'past' class
element.classList.add( reverse ? 'future' : 'past' );

Hakim El Hattab
committed
}
else if( i > index ) {
// Any element subsequent to index is given the 'future' class
element.classList.add( reverse ? 'past' : 'future' );

Hakim El Hattab
committed
}
// If this element contains vertical slides
if( element.querySelector( 'section' ) ) {
element.classList.add( 'stack' );

Hakim El Hattab
committed
}
}
// Mark the current slide as present
slides[index].classList.add( 'present' );
// If this slide has a state associated with it, add it
// onto the current state of the deck
var slideState = slides[index].getAttribute( 'data-state' );
if( slideState ) {
state = state.concat( slideState.split( ' ' ) );
}
// If this slide has a data-autoslide attribtue associated use this as

Hakim El Hattab
committed
// autoSlide value otherwise use the global configured time
var slideAutoSlide = slides[index].getAttribute( 'data-autoslide' );
if( slideAutoSlide ) {
autoSlide = parseInt( slideAutoSlide, 10 );

Hakim El Hattab
committed
}
}
else {
// Since there are no slides we can't be anywhere beyond the

Hakim El Hattab
committed
// zeroth index
index = 0;
}

Hakim El Hattab
committed
return index;

Hakim El Hattab
committed
}
* Updates the progress bar to reflect the current slide.
*/
function updateProgress() {
// Update progress if enabled
if( config.progress && dom.progress ) {
var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );
// The number of past and total slides
var totalCount = document.querySelectorAll( SLIDES_SELECTOR + ':not(.stack)' ).length;
var pastCount = 0;
// Step through all slides and count the past ones
mainLoop: for( var i = 0; i < horizontalSlides.length; i++ ) {
var horizontalSlide = horizontalSlides[i];
var verticalSlides = toArray( horizontalSlide.querySelectorAll( 'section' ) );
for( var j = 0; j < verticalSlides.length; j++ ) {
// Stop as soon as we arrive at the present
if( verticalSlides[j].classList.contains( 'present' ) ) {
break mainLoop;
}
}
// Stop as soon as we arrive at the present
if( horizontalSlide.classList.contains( 'present' ) ) {
break;
}
// Don't count the wrapping section for vertical slides
if( horizontalSlide.classList.contains( 'stack' ) === false ) {
pastCount++;
}
}
dom.progressbar.style.width = ( pastCount / ( totalCount - 1 ) ) * window.innerWidth + 'px';
}
}
/**
* Updates the state of all control/navigation arrows.
*/
function updateControls() {

Hakim El Hattab
committed
if ( config.controls && dom.controls ) {

Hakim El Hattab
committed
var routes = availableRoutes();
var fragments = availableFragments();

Hakim El Hattab
committed
// Remove the 'enabled' class from all directions

Hakim El Hattab
committed
dom.controlsLeft.concat( dom.controlsRight )
.concat( dom.controlsUp )
.concat( dom.controlsDown )
.concat( dom.controlsPrev )
.concat( dom.controlsNext ).forEach( function( node ) {

Hakim El Hattab
committed
node.classList.remove( 'enabled' );
node.classList.remove( 'fragmented' );

Hakim El Hattab
committed
} );
// Add the 'enabled' class to the available routes
if( routes.left ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.right ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.up ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.down ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'enabled' ); } );

Hakim El Hattab
committed
// Prev/next buttons
if( routes.left || routes.up ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'enabled' ); } );
if( routes.right || routes.down ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'enabled' ); } );

Hakim El Hattab
committed
// Highlight fragment directions
if( currentSlide ) {
// Always apply fragment decorator to prev/next buttons
if( fragments.prev ) dom.controlsPrev.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
if( fragments.next ) dom.controlsNext.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
// Apply fragment decorators to directional buttons based on
// what slide axis they are in
if( isVerticalSlide( currentSlide ) ) {
if( fragments.prev ) dom.controlsUp.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
if( fragments.next ) dom.controlsDown.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
}
else {
if( fragments.prev ) dom.controlsLeft.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
if( fragments.next ) dom.controlsRight.forEach( function( el ) { el.classList.add( 'fragmented', 'enabled' ); } );
}
}
* Updates the background elements to reflect the current
* slide.
*/
function updateBackground() {
// Update the classes of all backgrounds to match the
// states of their slides (past/present/future)
toArray( dom.background.childNodes ).forEach( function( backgroundh, h ) {
backgroundh.className = ( h < indexh ? 'past' : h > indexh ? 'future' : 'present' );
toArray( backgroundh.childNodes ).forEach( function( backgroundv, v ) {
backgroundv.className = ( v < indexv ? 'past' : v > indexv ? 'future' : 'present' );
} );
} );
// Allow the first background to apply without transition
setTimeout( function() {
dom.background.classList.remove( 'no-transition' );
}, 1 );
* Determine what available routes there are for navigation.
* @return {Object} containing four booleans: left/right/up/down
*/
function availableRoutes() {
var horizontalSlides = document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ),
verticalSlides = document.querySelectorAll( VERTICAL_SLIDES_SELECTOR );
var routes = {
left: indexh > 0 || config.loop,
right: indexh < horizontalSlides.length - 1 || config.loop,
up: indexv > 0,
down: indexv < verticalSlides.length - 1
};
// reverse horizontal controls for rtl
if( config.rtl ) {
var left = routes.left;
routes.left = routes.right;
routes.right = left;
}
return routes;
/**
* Returns an object describing the available fragment
* directions.
*
* @return {Object} two boolean properties: prev/next
*/
function availableFragments() {
if( currentSlide && config.fragments ) {
var fragments = currentSlide.querySelectorAll( '.fragment' );
var hiddenFragments = currentSlide.querySelectorAll( '.fragment:not(.visible)' );
return {
prev: fragments.length - hiddenFragments.length > 0,
next: !!hiddenFragments.length
};
}
else {
return { prev: false, next: false };
}
}
/**
* Start playback of any embedded content inside of
* the targeted slide.
*/
function startEmbeddedContent( slide ) {
if( slide ) {
// HTML5 media elements
toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
if( el.hasAttribute( 'data-autoplay' ) ) {
el.play();
}
} );
// YouTube embeds
toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
if( el.hasAttribute( 'data-autoplay' ) ) {
el.contentWindow.postMessage('{"event":"command","func":"playVideo","args":""}', '*');
}
});
}
}
/**
* Stop playback of any embedded content inside of
* the targeted slide.
*/
function stopEmbeddedContent( slide ) {
if( slide ) {
// HTML5 media elements
toArray( slide.querySelectorAll( 'video, audio' ) ).forEach( function( el ) {
if( !el.hasAttribute( 'data-ignore' ) ) {
el.pause();
}
} );
// YouTube embeds
toArray( slide.querySelectorAll( 'iframe[src*="youtube.com/embed/"]' ) ).forEach( function( el ) {
if( !el.hasAttribute( 'data-ignore' ) && typeof el.contentWindow.postMessage === 'function' ) {
el.contentWindow.postMessage('{"event":"command","func":"pauseVideo","args":""}', '*');
}
});
}
}
/**
* Reads the current URL (hash) and navigates accordingly.
*/
function readURL() {
var hash = window.location.hash;
// Attempt to parse the hash as either an index or name
var bits = hash.slice( 2 ).split( '/' ),
name = hash.replace( /#|\//gi, '' );
// If the first bit is invalid and there is a name we can
// assume that this is a named link
if( isNaN( parseInt( bits[0], 10 ) ) && name.length ) {
// Find the slide with the specified name

Hakim El Hattab
committed
var element = document.querySelector( '#' + name );

Hakim El Hattab
committed
if( element ) {
// Find the position of the named slide and navigate to it

Hakim El Hattab
committed
var indices = Reveal.getIndices( element );
slide( indices.h, indices.v );
}
// If the slide doesn't exist, navigate to the current slide
else {

Hakim El Hattab
committed
slide( indexh, indexv );
}
}
else {
// Read the index components of the hash
var h = parseInt( bits[0], 10 ) || 0,
v = parseInt( bits[1], 10 ) || 0;

Tobi Reiss
committed

Hakim El Hattab
committed
slide( h, v );
/**
* Updates the page URL (hash) to reflect the current
* @param {Number} delay The time in ms to wait before
* writing the hash
function writeURL( delay ) {
if( config.history ) {
// Make sure there's never more than one timeout running
clearTimeout( writeURLTimeout );
// If a delay is specified, timeout this call
if( typeof delay === 'number' ) {
writeURLTimeout = setTimeout( writeURL, delay );

Hakim El Hattab
committed
}
else {
// If the current slide has an ID, use that as a named link
if( currentSlide && typeof currentSlide.getAttribute( 'id' ) === 'string' ) {
url = '/' + currentSlide.getAttribute( 'id' );
}
// Otherwise use the /h/v index
else {
if( indexh > 0 || indexv > 0 ) url += indexh;
if( indexv > 0 ) url += '/' + indexv;
}
window.location.hash = url;
}

Hakim El Hattab
committed
/**
* Retrieves the h/v location of the current, or specified,

Hakim El Hattab
committed
* slide.
*
* @param {HTMLElement} slide If specified, the returned
* index will be for this slide rather than the currently

Hakim El Hattab
committed
* active one

Hakim El Hattab
committed
* @return {Object} { h: <int>, v: <int>, f: <int> }

Hakim El Hattab
committed
*/
function getIndices( slide ) {

Hakim El Hattab
committed
// By default, return the current indices
var h = indexh,

Hakim El Hattab
committed
v = indexv,
f;

Hakim El Hattab
committed
// If a slide is specified, return the indices of that slide
if( slide ) {
var isVertical = isVerticalSlide( slide );

Hakim El Hattab
committed
var slideh = isVertical ? slide.parentNode : slide;
// Select all horizontal slides
var horizontalSlides = toArray( document.querySelectorAll( HORIZONTAL_SLIDES_SELECTOR ) );

Hakim El Hattab
committed
// Now that we know which the horizontal slide is, get its index
h = Math.max( horizontalSlides.indexOf( slideh ), 0 );
// If this is a vertical slide, grab the vertical index
if( isVertical ) {
v = Math.max( toArray( slide.parentNode.querySelectorAll( 'section' ) ).indexOf( slide ), 0 );

Hakim El Hattab
committed
}
}

Hakim El Hattab
committed
if( !slide && currentSlide ) {
var visibleFragments = currentSlide.querySelectorAll( '.fragment.visible' );
if( visibleFragments.length ) {
f = visibleFragments.length;
}
}
return { h: h, v: v, f: f };

Hakim El Hattab
committed
}
* @return {Boolean} true if there was a next fragment,
* false otherwise
*/
if( currentSlide && config.fragments ) {
var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment:not(.visible)' ) );

Hakim El Hattab
committed
if( fragments.length ) {
// Find the index of the next fragment
var index = fragments[0].getAttribute( 'data-fragment-index' );
// Find all fragments with the same index
fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
toArray( fragments ).forEach( function( element ) {
element.classList.add( 'visible' );
// Notify subscribers of the change
dispatchEvent( 'fragmentshown', { fragment: element } );
updateControls();
}
return false;
}
/**
* Navigate to the previous slide fragment.
* @return {Boolean} true if there was a previous fragment,
* false otherwise
*/
function previousFragment() {
if( currentSlide && config.fragments ) {
var fragments = sortFragments( currentSlide.querySelectorAll( '.fragment.visible' ) );

Hakim El Hattab
committed
if( fragments.length ) {
// Find the index of the previous fragment
var index = fragments[ fragments.length - 1 ].getAttribute( 'data-fragment-index' );
// Find all fragments with the same index
fragments = currentSlide.querySelectorAll( '.fragment[data-fragment-index="'+ index +'"]' );
toArray( fragments ).forEach( function( f ) {
f.classList.remove( 'visible' );
// Notify subscribers of the change
dispatchEvent( 'fragmenthidden', { fragment: f } );
updateControls();
return true;
}
}
return false;

Hakim El Hattab
committed
/**
* Cues a new automated slide if enabled in the config.
*/

Hakim El Hattab
committed
function cueAutoSlide() {

Hakim El Hattab
committed
clearTimeout( autoSlideTimeout );
// Cue the next auto-slide if enabled
if( autoSlide && !isPaused() && !isOverview() ) {
autoSlideTimeout = setTimeout( navigateNext, autoSlide );

Hakim El Hattab
committed
}

Hakim El Hattab
committed
}
/**
* Cancels any ongoing request to auto-slide.
*/
function cancelAutoSlide() {
clearTimeout( autoSlideTimeout );
}
// Reverse for RTL
if( config.rtl ) {
if( ( isOverview() || nextFragment() === false ) && availableRoutes().left ) {
slide( indexh + 1 );
}
}
// Normal navigation
else if( ( isOverview() || previousFragment() === false ) && availableRoutes().left ) {
slide( indexh - 1 );
// Reverse for RTL
if( config.rtl ) {
if( ( isOverview() || previousFragment() === false ) && availableRoutes().right ) {
slide( indexh - 1 );
}
}
// Normal navigation
else if( ( isOverview() || nextFragment() === false ) && availableRoutes().right ) {
slide( indexh + 1 );
// Prioritize hiding fragments
if( ( isOverview() || previousFragment() === false ) && availableRoutes().up ) {
slide( indexh, indexv - 1 );
// Prioritize revealing fragments
if( ( isOverview() || nextFragment() === false ) && availableRoutes().down ) {
slide( indexh, indexv + 1 );
/**
* Navigates backwards, prioritized in the following order:
* 1) Previous fragment
* 2) Previous vertical slide
* 3) Previous horizontal slide
*/
function navigatePrev() {
// Prioritize revealing fragments
if( previousFragment() === false ) {
if( availableRoutes().up ) {
navigateUp();
}
else {
// Fetch the previous horizontal slide, if there is one
var previousSlide = document.querySelector( HORIZONTAL_SLIDES_SELECTOR + '.past:nth-child(' + indexh + ')' );
if( previousSlide ) {
indexv = ( previousSlide.querySelectorAll( 'section' ).length + 1 ) || undefined;
indexh --;
slide( indexh, indexv );
}
}
}
}
/**
* Same as #navigatePrev() but navigates forwards.
*/
function navigateNext() {
// Prioritize revealing fragments
if( nextFragment() === false ) {
availableRoutes().down ? navigateDown() : navigateRight();
}

Hakim El Hattab
committed
// If auto-sliding is enabled we need to cue up

Hakim El Hattab
committed
// another timeout
cueAutoSlide();

Hakim El Hattab
committed
// --------------------------------------------------------------------//
// ----------------------------- EVENTS -------------------------------//
// --------------------------------------------------------------------//
/**
* Handler for the document level 'keydown' event.

Hakim El Hattab
committed
* @param {Object} event
*/
function onDocumentKeyDown( event ) {
// Check if there's a focused element that could be using
// the keyboard
var activeElement = document.activeElement;
var hasFocus = !!( document.activeElement && ( document.activeElement.type || document.activeElement.href || document.activeElement.contentEditable !== 'inherit' ) );
// Disregard the event if there's a focused element or a
// keyboard modifier key is present
if( hasFocus || (event.shiftKey && event.keyCode !== 32) || event.altKey || event.ctrlKey || event.metaKey ) return;

Hakim El Hattab
committed
var triggered = true;
// while paused only allow "unpausing" keyboard events (b and .)
if( isPaused() && [66,190,191].indexOf( event.keyCode ) === -1 ) {
return false;
}

Hakim El Hattab
committed
switch( event.keyCode ) {
// p, page up
case 80: case 33: navigatePrev(); break;

Hakim El Hattab
committed
// n, page down
case 78: case 34: navigateNext(); break;
// h, left
case 72: case 37: navigateLeft(); break;
// l, right
case 76: case 39: navigateRight(); break;
// k, up
case 75: case 38: navigateUp(); break;
// j, down
case 74: case 40: navigateDown(); break;
// home
case 36: slide( 0 ); break;
// end
case 35: slide( Number.MAX_VALUE ); break;
// space
case 32: isOverview() ? deactivateOverview() : event.shiftKey ? navigatePrev() : navigateNext(); break;

Hakim El Hattab
committed
// return
case 13: isOverview() ? deactivateOverview() : triggered = false; break;
// b, period, Logitech presenter tools "black screen" button
case 66: case 190: case 191: togglePause(); break;

Hakim El Hattab
committed
// f
case 70: enterFullscreen(); break;
default:
triggered = false;
}
// If the input resulted in a triggered action we should prevent

Hakim El Hattab
committed
// the browsers default behavior
if( triggered ) {
event.preventDefault();
}
else if ( event.keyCode === 27 && supports3DTransforms ) {
toggleOverview();

Hakim El Hattab
committed
event.preventDefault();
}
// If auto-sliding is enabled we need to cue up

Hakim El Hattab
committed
// another timeout
cueAutoSlide();
}
/**
* Handler for the 'touchstart' event, enables support for
* swipe and pinch gestures.

Hakim El Hattab
committed
*/
function onTouchStart( event ) {

Hakim El Hattab
committed
touch.startX = event.touches[0].clientX;
touch.startY = event.touches[0].clientY;
touch.startCount = event.touches.length;
// If there's two touches we need to memorize the distance

Hakim El Hattab
committed
// between those two points to detect pinching
if( event.touches.length === 2 && config.overview ) {
touch.startSpan = distanceBetween( {
x: event.touches[1].clientX,
y: event.touches[1].clientY
}, {
x: touch.startX,