import throttle from 'lodash/throttle';
import InView from '../utils/classes/InView';
import customEvent from '../utils/customEvent';
import supportsPassiveEvent from '../utils/supportsPassiveEvent';
/**
* Show chapters selection when article marker is in view
*
* @module Chapters
* @prop {object} selectors - Chapters CSS selectors
* @prop {object} states - css and animation states for chapters module
* @prop {NodeList} $elem - chapters element
* @prop {number} currentScrolPos - store current scroll position to determine scroll direction
* @prop {string} currentScrollDirection - 'up' or 'down'
* @prop {function} scrollListener - event handler for handling scroll event
*/
const Chapters = {
selectors: {
elem: '.c-longform-chapters',
marker: '.l-longform-article__marker',
hero: '.c-longform-hero',
panel: '.c-longform-chapters__panel',
desktopPanel: '.c-longform-chapters__panel--desktop',
pagination: '.c-longform-chapters__pagination',
},
states: {
opened: 'c-longform-chapters--opened',
active: 'c-longform-chapters--active',
shifted: 'c-longform-chapters--shifted',
fading: {
in: 'is-slid-and-faded-in',
},
},
$elem: false,
currentScrollPos: 0,
currentScrollDirection: '',
scrollListener: false,
/**
* Set up inview listener to handle show / hide of chapters
*
* @method init
*/
init() {
const $marker = document.querySelector( this.selectors.marker );
this.$elem = document.querySelector( this.selectors.elem );
if ( this.$elem && $marker ) {
// bind event to capture scroll listener
this.scrollListener = throttle( () => {
this.monitorScroll();
}, 200 );
// Initialize InView watcher
const watcher = new InView({});
watcher.init();
// Watch article midway marker
$marker.dataset.alwaysObserve = 'true';
$marker.setAttribute( 'style', 'width:100%;position:absolute;top:auto;pointer-events:none;height:300px;' );
$marker.addEventListener( customEvent.IN_VIEW, evt => this.showChapters( evt ) );
watcher.startWatching( $marker );
const $hero = document.querySelector( this.selectors.hero );
if ( $hero ) {
// Watch article hero
$hero.dataset.alwaysObserve = 'true';
$hero.addEventListener( customEvent.IN_VIEW, evt => this.hideChapters( evt ) );
watcher.startWatching( $hero );
}
// Show chapters immediately when scrollY is below article marker
if ( window.scrollY >= $marker.offsetTop ) {
this.showChapters( false, true );
}
// Watch panel attribute change to adjust next / prev item style
const observer = new MutationObserver( () => this.updateState() );
const $panels = document.querySelectorAll( this.selectors.panel );
[].forEach.call( $panels, ( $panel ) => {
observer.observe( $panel, {
attributes: true,
childList: false,
subtree: false,
});
});
// Set top position of the chapters panel
const $desktopPanel = document.querySelector( this.selectors.desktopPanel );
const $pagination = document.querySelector( this.selectors.pagination );
if ( $desktopPanel && $pagination ) {
const style = $desktopPanel.getAttribute( 'style' );
$desktopPanel.setAttribute( 'style', `${style || ''}top:${$pagination.offsetHeight}px;` );
}
}
},
/**
* Monitor scroll position to determine if the chapters widget
* should be anchored to the top of stacked below sticky header
*
* @method monitorScroll
*/
monitorScroll() {
const direction = window.pageYOffset > this.currentScrollPos ? 'down' : 'up';
// adjust css class when scrolling in opposite direction
if ( this.currentScrollDirection !== direction ) {
if ( 'down' === direction ) {
this.$elem.classList.remove( this.states.shifted );
} else {
this.$elem.classList.add( this.states.shifted );
}
}
this.currentScrollDirection = direction;
this.currentScrollPos = window.pageYOffset;
},
/**
* Show chapters widget when marker element is in view
* or when forced via forcedShow flag (triggered on page load)
*
* @method showChatpers
* @param {object} evt - in view event
* @param {boolean} forcedShow
*/
showChapters( evt, forcedShow ) {
if ( ( ( evt && evt.detail.isInView ) || forcedShow )
&& ! this.$elem.classList.contains( this.states.active ) ) {
this.$elem.classList.add( this.states.active );
this.$elem.classList.add( this.states.shifted );
this.$elem.classList.add( this.states.fading.in );
// If browser supports passive event, add passive option
window.addEventListener( 'scroll', this.scrollListener, supportsPassiveEvent ? { passive: true } : false );
}
},
/**
* Hide chapters widget when hero area is in view
*
* @method hideChatpers
* @param {object} evt - in view event
*/
hideChapters( evt ) {
if ( evt.detail.isInView ) {
this.$elem.classList.remove( this.states.active );
this.$elem.classList.remove( this.states.fading.in );
this.currentScrollPos = 0;
this.currentScrollDirection = '';
window.removeEventListener( 'scroll', this.scrollListener );
}
},
/**
* Update next / previous item states when panel is opened / closed
*
* @method updateState
*/
updateState() {
const $openedPanel = document.querySelector( `${this.selectors.panel}.${this.states.fading.in}` );
if ( $openedPanel ) {
this.$elem.classList.add( this.states.opened );
} else {
this.$elem.classList.remove( this.states.opened );
}
},
};
export default Chapters;