123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- import { extend, queryAll } from '../utils/util.js'
- /**
- * Handles sorting and navigation of slide fragments.
- * Fragments are elements within a slide that are
- * revealed/animated incrementally.
- */
- export default class Fragments {
- constructor( Reveal ) {
- this.Reveal = Reveal;
- }
- /**
- * Called when the reveal.js config is updated.
- */
- configure( config, oldConfig ) {
- if( config.fragments === false ) {
- this.disable();
- }
- else if( oldConfig.fragments === false ) {
- this.enable();
- }
- }
- /**
- * If fragments are disabled in the deck, they should all be
- * visible rather than stepped through.
- */
- disable() {
- queryAll( this.Reveal.getSlidesElement(), '.fragment' ).forEach( element => {
- element.classList.add( 'visible' );
- element.classList.remove( 'current-fragment' );
- } );
- }
- /**
- * Reverse of #disable(). Only called if fragments have
- * previously been disabled.
- */
- enable() {
- queryAll( this.Reveal.getSlidesElement(), '.fragment' ).forEach( element => {
- element.classList.remove( 'visible' );
- element.classList.remove( 'current-fragment' );
- } );
- }
- /**
- * Returns an object describing the available fragment
- * directions.
- *
- * @return {{prev: boolean, next: boolean}}
- */
- availableRoutes() {
- let currentSlide = this.Reveal.getCurrentSlide();
- if( currentSlide && this.Reveal.getConfig().fragments ) {
- let fragments = currentSlide.querySelectorAll( '.fragment:not(.disabled)' );
- let hiddenFragments = currentSlide.querySelectorAll( '.fragment:not(.disabled):not(.visible)' );
- return {
- prev: fragments.length - hiddenFragments.length > 0,
- next: !!hiddenFragments.length
- };
- }
- else {
- return { prev: false, next: false };
- }
- }
- /**
- * Return a sorted fragments list, ordered by an increasing
- * "data-fragment-index" attribute.
- *
- * Fragments will be revealed in the order that they are returned by
- * this function, so you can use the index attributes to control the
- * order of fragment appearance.
- *
- * To maintain a sensible default fragment order, fragments are presumed
- * to be passed in document order. This function adds a "fragment-index"
- * attribute to each node if such an attribute is not already present,
- * and sets that attribute to an integer value which is the position of
- * the fragment within the fragments list.
- *
- * @param {object[]|*} fragments
- * @param {boolean} grouped If true the returned array will contain
- * nested arrays for all fragments with the same index
- * @return {object[]} sorted Sorted array of fragments
- */
- sort( fragments, grouped = false ) {
- fragments = Array.from( fragments );
- let ordered = [],
- unordered = [],
- sorted = [];
- // Group ordered and unordered elements
- fragments.forEach( fragment => {
- if( fragment.hasAttribute( 'data-fragment-index' ) ) {
- let index = parseInt( fragment.getAttribute( 'data-fragment-index' ), 10 );
- if( !ordered[index] ) {
- ordered[index] = [];
- }
- ordered[index].push( fragment );
- }
- else {
- unordered.push( [ fragment ] );
- }
- } );
- // Append fragments without explicit indices in their
- // DOM order
- ordered = ordered.concat( unordered );
- // Manually count the index up per group to ensure there
- // are no gaps
- let index = 0;
- // Push all fragments in their sorted order to an array,
- // this flattens the groups
- ordered.forEach( group => {
- group.forEach( fragment => {
- sorted.push( fragment );
- fragment.setAttribute( 'data-fragment-index', index );
- } );
- index ++;
- } );
- return grouped === true ? ordered : sorted;
- }
- /**
- * Sorts and formats all of fragments in the
- * presentation.
- */
- sortAll() {
- this.Reveal.getHorizontalSlides().forEach( horizontalSlide => {
- let verticalSlides = queryAll( horizontalSlide, 'section' );
- verticalSlides.forEach( ( verticalSlide, y ) => {
- this.sort( verticalSlide.querySelectorAll( '.fragment' ) );
- }, this );
- if( verticalSlides.length === 0 ) this.sort( horizontalSlide.querySelectorAll( '.fragment' ) );
- } );
- }
- /**
- * Refreshes the fragments on the current slide so that they
- * have the appropriate classes (.visible + .current-fragment).
- *
- * @param {number} [index] The index of the current fragment
- * @param {array} [fragments] Array containing all fragments
- * in the current slide
- *
- * @return {{shown: array, hidden: array}}
- */
- update( index, fragments ) {
- let changedFragments = {
- shown: [],
- hidden: []
- };
- let currentSlide = this.Reveal.getCurrentSlide();
- if( currentSlide && this.Reveal.getConfig().fragments ) {
- fragments = fragments || this.sort( currentSlide.querySelectorAll( '.fragment' ) );
- if( fragments.length ) {
- let maxIndex = 0;
- if( typeof index !== 'number' ) {
- let currentFragment = this.sort( currentSlide.querySelectorAll( '.fragment.visible' ) ).pop();
- if( currentFragment ) {
- index = parseInt( currentFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );
- }
- }
- Array.from( fragments ).forEach( ( el, i ) => {
- if( el.hasAttribute( 'data-fragment-index' ) ) {
- i = parseInt( el.getAttribute( 'data-fragment-index' ), 10 );
- }
- maxIndex = Math.max( maxIndex, i );
- // Visible fragments
- if( i <= index ) {
- let wasVisible = el.classList.contains( 'visible' )
- el.classList.add( 'visible' );
- el.classList.remove( 'current-fragment' );
- if( i === index ) {
- // Announce the fragments one by one to the Screen Reader
- this.Reveal.announceStatus( this.Reveal.getStatusText( el ) );
- el.classList.add( 'current-fragment' );
- this.Reveal.slideContent.startEmbeddedContent( el );
- }
- if( !wasVisible ) {
- changedFragments.shown.push( el )
- this.Reveal.dispatchEvent({
- target: el,
- type: 'visible',
- bubbles: false
- });
- }
- }
- // Hidden fragments
- else {
- let wasVisible = el.classList.contains( 'visible' )
- el.classList.remove( 'visible' );
- el.classList.remove( 'current-fragment' );
- if( wasVisible ) {
- this.Reveal.slideContent.stopEmbeddedContent( el );
- changedFragments.hidden.push( el );
- this.Reveal.dispatchEvent({
- target: el,
- type: 'hidden',
- bubbles: false
- });
- }
- }
- } );
- // Write the current fragment index to the slide <section>.
- // This can be used by end users to apply styles based on
- // the current fragment index.
- index = typeof index === 'number' ? index : -1;
- index = Math.max( Math.min( index, maxIndex ), -1 );
- currentSlide.setAttribute( 'data-fragment', index );
- }
- }
- return changedFragments;
- }
- /**
- * Formats the fragments on the given slide so that they have
- * valid indices. Call this if fragments are changed in the DOM
- * after reveal.js has already initialized.
- *
- * @param {HTMLElement} slide
- * @return {Array} a list of the HTML fragments that were synced
- */
- sync( slide = this.Reveal.getCurrentSlide() ) {
- return this.sort( slide.querySelectorAll( '.fragment' ) );
- }
- /**
- * Navigate to the specified slide fragment.
- *
- * @param {?number} index The index of the fragment that
- * should be shown, -1 means all are invisible
- * @param {number} offset Integer offset to apply to the
- * fragment index
- *
- * @return {boolean} true if a change was made in any
- * fragments visibility as part of this call
- */
- goto( index, offset = 0 ) {
- let currentSlide = this.Reveal.getCurrentSlide();
- if( currentSlide && this.Reveal.getConfig().fragments ) {
- let fragments = this.sort( currentSlide.querySelectorAll( '.fragment:not(.disabled)' ) );
- if( fragments.length ) {
- // If no index is specified, find the current
- if( typeof index !== 'number' ) {
- let lastVisibleFragment = this.sort( currentSlide.querySelectorAll( '.fragment:not(.disabled).visible' ) ).pop();
- if( lastVisibleFragment ) {
- index = parseInt( lastVisibleFragment.getAttribute( 'data-fragment-index' ) || 0, 10 );
- }
- else {
- index = -1;
- }
- }
- // Apply the offset if there is one
- index += offset;
- let changedFragments = this.update( index, fragments );
- if( changedFragments.hidden.length ) {
- this.Reveal.dispatchEvent({
- type: 'fragmenthidden',
- data: {
- fragment: changedFragments.hidden[0],
- fragments: changedFragments.hidden
- }
- });
- }
- if( changedFragments.shown.length ) {
- this.Reveal.dispatchEvent({
- type: 'fragmentshown',
- data: {
- fragment: changedFragments.shown[0],
- fragments: changedFragments.shown
- }
- });
- }
- this.Reveal.controls.update();
- this.Reveal.progress.update();
- if( this.Reveal.getConfig().fragmentInURL ) {
- this.Reveal.location.writeURL();
- }
- return !!( changedFragments.shown.length || changedFragments.hidden.length );
- }
- }
- return false;
- }
- /**
- * Navigate to the next slide fragment.
- *
- * @return {boolean} true if there was a next fragment,
- * false otherwise
- */
- next() {
- return this.goto( null, 1 );
- }
- /**
- * Navigate to the previous slide fragment.
- *
- * @return {boolean} true if there was a previous fragment,
- * false otherwise
- */
- prev() {
- return this.goto( null, -1 );
- }
- }
|