main/Expand.js

/**
 * Reveals and hides elements on the page via a toggle. Expandable elements are initially hidden.
 *
 * @example
 * <button data-expand="#regionExpand" aria-expaneded="false">Click to reveal element below</button>
 * <div class="is-expandable">Initially hidden content</div>
 *
 * @module Expand
 * @prop {String} expandToggleSelector - selector for all expandable click toggles
 * @prop {String} expandedFlag - css class to add once an element is expanded
 * @prop {String} scrollParentSelector - default scroll target to body element
 */
const Expand = {
	expandToggleSelector: '[data-expand]',
	expandedFlag: 'is-expanded',
	scrollTargetSelector: 'body',

	/**
	 * Binds click events to any `data-expand="[selector]"` elements on the page.
	 * @method init
	 */
	init() {
		const $expandToggles = document.querySelectorAll( this.expandToggleSelector );
		[].forEach.call( $expandToggles, ( $toggle ) => {
			$toggle.addEventListener( 'click', ( e ) => {
				e.preventDefault();
				const targetId = $toggle.getAttribute( 'data-expand' );
				const $target  = document.querySelector( targetId );
				const $child   = $target.children[0];

				if ( $target.classList.contains( this.expandedFlag ) ) {
					$target.style.height = '';
					$target.classList.remove( this.expandedFlag );
					$toggle.setAttribute( 'aria-expanded', false );
				} else {
					$target.style.height = `${$child.offsetHeight}px`;
					$target.classList.add( this.expandedFlag );
					$toggle.setAttribute( 'aria-expanded', true );

					// adjust scroll position to fit expanded element
					setTimeout( () => this.setScrollPosition( $toggle, $child ), 500 );
				}
			});
		});
	},

	/**
	 * Adjusts scroll position so expanded element is in view
	 * @method setScrollPosition
	 * @param {Element} $toggle - DOM reference to the clicked toggle element
	 * @param {Element} $childe - DOM reference to the exapanding element
	 */
	setScrollPosition( $toggle, $child ) {
		// check to see if user has defined a scroll target, otherwise adjust body scrollbar
		if ( $toggle.getAttribute( 'data-scroll-target' ) ) {
			this.scrollTargetSelector = $toggle.getAttribute( 'data-scroll-target' );
		}

		// scroll offset calculation
		const $scrollTarget = document.querySelector( this.scrollTargetSelector );
		const { scrollTop } = $scrollTarget;
		const viewportHeight = window.innerHeight;
		const elemBounds = $child.getBoundingClientRect();
		const elemVisible = viewportHeight - ( elemBounds.top + scrollTop );
		const elemHidden = elemBounds.height - elemVisible;
		const scrollPadding = 40;
		const scrollOffset = elemHidden + scrollPadding;

		// scroll adjustment
		$scrollTarget.scrollTop = scrollOffset;
	},
};

export default Expand;