123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 |
- import { queryAll } from '../utils/util.js'
- import { isAndroid } from '../utils/device.js'
- /**
- * Manages our presentation controls. This includes both
- * the built-in control arrows as well as event monitoring
- * of any elements within the presentation with either of the
- * following helper classes:
- * - .navigate-up
- * - .navigate-right
- * - .navigate-down
- * - .navigate-left
- * - .navigate-next
- * - .navigate-prev
- */
- export default class Controls {
- constructor( Reveal ) {
- this.Reveal = Reveal;
- this.onNavigateLeftClicked = this.onNavigateLeftClicked.bind( this );
- this.onNavigateRightClicked = this.onNavigateRightClicked.bind( this );
- this.onNavigateUpClicked = this.onNavigateUpClicked.bind( this );
- this.onNavigateDownClicked = this.onNavigateDownClicked.bind( this );
- this.onNavigatePrevClicked = this.onNavigatePrevClicked.bind( this );
- this.onNavigateNextClicked = this.onNavigateNextClicked.bind( this );
- }
- render() {
- const rtl = this.Reveal.getConfig().rtl;
- const revealElement = this.Reveal.getRevealElement();
- this.element = document.createElement( 'aside' );
- this.element.className = 'controls';
- this.element.innerHTML =
- `<button class="navigate-left" aria-label="${ rtl ? 'next slide' : 'previous slide' }"><div class="controls-arrow"></div></button>
- <button class="navigate-right" aria-label="${ rtl ? 'previous slide' : 'next slide' }"><div class="controls-arrow"></div></button>
- <button class="navigate-up" aria-label="above slide"><div class="controls-arrow"></div></button>
- <button class="navigate-down" aria-label="below slide"><div class="controls-arrow"></div></button>`;
- this.Reveal.getRevealElement().appendChild( this.element );
- // There can be multiple instances of controls throughout the page
- this.controlsLeft = queryAll( revealElement, '.navigate-left' );
- this.controlsRight = queryAll( revealElement, '.navigate-right' );
- this.controlsUp = queryAll( revealElement, '.navigate-up' );
- this.controlsDown = queryAll( revealElement, '.navigate-down' );
- this.controlsPrev = queryAll( revealElement, '.navigate-prev' );
- this.controlsNext = queryAll( revealElement, '.navigate-next' );
- // The left, right and down arrows in the standard reveal.js controls
- this.controlsRightArrow = this.element.querySelector( '.navigate-right' );
- this.controlsLeftArrow = this.element.querySelector( '.navigate-left' );
- this.controlsDownArrow = this.element.querySelector( '.navigate-down' );
- }
- /**
- * Called when the reveal.js config is updated.
- */
- configure( config, oldConfig ) {
- this.element.style.display = config.controls ? 'block' : 'none';
- this.element.setAttribute( 'data-controls-layout', config.controlsLayout );
- this.element.setAttribute( 'data-controls-back-arrows', config.controlsBackArrows );
- }
- bind() {
- // Listen to both touch and click events, in case the device
- // supports both
- let pointerEvents = [ 'touchstart', 'click' ];
- // Only support touch for Android, fixes double navigations in
- // stock browser
- if( isAndroid ) {
- pointerEvents = [ 'touchstart' ];
- }
- pointerEvents.forEach( eventName => {
- this.controlsLeft.forEach( el => el.addEventListener( eventName, this.onNavigateLeftClicked, false ) );
- this.controlsRight.forEach( el => el.addEventListener( eventName, this.onNavigateRightClicked, false ) );
- this.controlsUp.forEach( el => el.addEventListener( eventName, this.onNavigateUpClicked, false ) );
- this.controlsDown.forEach( el => el.addEventListener( eventName, this.onNavigateDownClicked, false ) );
- this.controlsPrev.forEach( el => el.addEventListener( eventName, this.onNavigatePrevClicked, false ) );
- this.controlsNext.forEach( el => el.addEventListener( eventName, this.onNavigateNextClicked, false ) );
- } );
- }
- unbind() {
- [ 'touchstart', 'click' ].forEach( eventName => {
- this.controlsLeft.forEach( el => el.removeEventListener( eventName, this.onNavigateLeftClicked, false ) );
- this.controlsRight.forEach( el => el.removeEventListener( eventName, this.onNavigateRightClicked, false ) );
- this.controlsUp.forEach( el => el.removeEventListener( eventName, this.onNavigateUpClicked, false ) );
- this.controlsDown.forEach( el => el.removeEventListener( eventName, this.onNavigateDownClicked, false ) );
- this.controlsPrev.forEach( el => el.removeEventListener( eventName, this.onNavigatePrevClicked, false ) );
- this.controlsNext.forEach( el => el.removeEventListener( eventName, this.onNavigateNextClicked, false ) );
- } );
- }
- /**
- * Updates the state of all control/navigation arrows.
- */
- update() {
- let routes = this.Reveal.availableRoutes();
- // Remove the 'enabled' class from all directions
- [...this.controlsLeft, ...this.controlsRight, ...this.controlsUp, ...this.controlsDown, ...this.controlsPrev, ...this.controlsNext].forEach( node => {
- node.classList.remove( 'enabled', 'fragmented' );
- // Set 'disabled' attribute on all directions
- node.setAttribute( 'disabled', 'disabled' );
- } );
- // Add the 'enabled' class to the available routes; remove 'disabled' attribute to enable buttons
- if( routes.left ) this.controlsLeft.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
- if( routes.right ) this.controlsRight.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
- if( routes.up ) this.controlsUp.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
- if( routes.down ) this.controlsDown.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
- // Prev/next buttons
- if( routes.left || routes.up ) this.controlsPrev.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
- if( routes.right || routes.down ) this.controlsNext.forEach( el => { el.classList.add( 'enabled' ); el.removeAttribute( 'disabled' ); } );
- // Highlight fragment directions
- let currentSlide = this.Reveal.getCurrentSlide();
- if( currentSlide ) {
- let fragmentsRoutes = this.Reveal.fragments.availableRoutes();
- // Always apply fragment decorator to prev/next buttons
- if( fragmentsRoutes.prev ) this.controlsPrev.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
- if( fragmentsRoutes.next ) this.controlsNext.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
- // Apply fragment decorators to directional buttons based on
- // what slide axis they are in
- if( this.Reveal.isVerticalSlide( currentSlide ) ) {
- if( fragmentsRoutes.prev ) this.controlsUp.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
- if( fragmentsRoutes.next ) this.controlsDown.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
- }
- else {
- if( fragmentsRoutes.prev ) this.controlsLeft.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
- if( fragmentsRoutes.next ) this.controlsRight.forEach( el => { el.classList.add( 'fragmented', 'enabled' ); el.removeAttribute( 'disabled' ); } );
- }
- }
- if( this.Reveal.getConfig().controlsTutorial ) {
- let indices = this.Reveal.getIndices();
- // Highlight control arrows with an animation to ensure
- // that the viewer knows how to navigate
- if( !this.Reveal.hasNavigatedVertically() && routes.down ) {
- this.controlsDownArrow.classList.add( 'highlight' );
- }
- else {
- this.controlsDownArrow.classList.remove( 'highlight' );
- if( this.Reveal.getConfig().rtl ) {
- if( !this.Reveal.hasNavigatedHorizontally() && routes.left && indices.v === 0 ) {
- this.controlsLeftArrow.classList.add( 'highlight' );
- }
- else {
- this.controlsLeftArrow.classList.remove( 'highlight' );
- }
- } else {
- if( !this.Reveal.hasNavigatedHorizontally() && routes.right && indices.v === 0 ) {
- this.controlsRightArrow.classList.add( 'highlight' );
- }
- else {
- this.controlsRightArrow.classList.remove( 'highlight' );
- }
- }
- }
- }
- }
- /**
- * Event handlers for navigation control buttons.
- */
- onNavigateLeftClicked( event ) {
- event.preventDefault();
- this.Reveal.onUserInput();
- if( this.Reveal.getConfig().navigationMode === 'linear' ) {
- this.Reveal.prev();
- }
- else {
- this.Reveal.left();
- }
- }
- onNavigateRightClicked( event ) {
- event.preventDefault();
- this.Reveal.onUserInput();
- if( this.Reveal.getConfig().navigationMode === 'linear' ) {
- this.Reveal.next();
- }
- else {
- this.Reveal.right();
- }
- }
- onNavigateUpClicked( event ) {
- event.preventDefault();
- this.Reveal.onUserInput();
- this.Reveal.up();
- }
- onNavigateDownClicked( event ) {
- event.preventDefault();
- this.Reveal.onUserInput();
- this.Reveal.down();
- }
- onNavigatePrevClicked( event ) {
- event.preventDefault();
- this.Reveal.onUserInput();
- this.Reveal.prev();
- }
- onNavigateNextClicked( event ) {
- event.preventDefault();
- this.Reveal.onUserInput();
- this.Reveal.next();
- }
- }
|