modules_SnackBar_index.js

import {
	consoleError,
	getNodes,
	setAttributes,
	speak,
	trigger,
} from '@gravityforms/utils';

export const el = {
	closeTrigger: null,
	container: null,
	target: null,
};

export const state = {
	hideTimer: () => {},
	hideAnimationTimer: () => {},
};

export const defaults = {
	attributes: {},
	autoHide: true,
	autoHideDelay: 4000,
	closeButton: true,
	closeTitle: '',
	container: '',
	ctaLink: '',
	ctaTarget: '_self',
	ctaText: '',
	icon: '',
	message: '',
	onClose: () => {},
	onReveal: () => {},
	position: 'bottomleft',
	speak: true,
	type: 'normal',
	wrapperClasses: 'gform-snackbar',
};

export let options = {};

/**
 * @function snackBarHtml
 * @description Uses our options to render the snackbar.
 *
 * @return {string} The html for the snackbar.
 */

export const snackBarHtml = () => ( `
	<article
		class="${ options.wrapperClasses } gform-snackbar--${ options.position } gform-snackbar--${ options.type }${ options.closeButton ? ` gform-snackbar--has-close` : `` }" 
		data-js="gform-snackbar"
	>
		${ options.icon ? `<span class="gform-snackbar__icon gform-icon gform-icon--${ options.icon }"></span>` : `` }
		${ options.message ? `<span class="gform-snackbar__message">${ options.message }</span>` : `` }
		${ options.ctaLink ? `
		<a 
			class="gform-snackbar__cta"
			href="${ options.ctaLink }"
			target="${ options.ctaTarget }"
			${ options.ctaTarget === '_blank' ? `rel="noopener"` : `` }
		>
			${ options.ctaText }
		</a>
		` : `` }
		${ options.closeButton ? `
		<button 
			class="gform-snackbar__close gform-icon gform-icon--delete"
			data-js="gform-snackbar-close"
			title="${ options.closeTitle }"
		></button>
		` : `` }
	</article>
` );

/**
 * @function cleanUp
 * @description Clears timeouts and does all needed garbage cleanup.
 *
 */

export const cleanUp = () => {
	if ( ! el.container ) {
		return;
	}
	el.target.style.position = '';
	el.container.parentNode.removeChild( el.container );
	if ( el.closeTrigger ) {
		el.closeTrigger.removeEventListener( 'click', close );
	}
	clearTimeout( state.hideTimer );
	clearTimeout( state.hideAnimationTimer );
	el.container = null;
	el.closeTrigger = null;
	el.target = null;
};

/**
 * @function close
 * @description Animates out the snackbar and then removes it from dom.
 *
 */

export const close = () => {
	el.container.classList.remove( 'gform-snackbar--reveal' );

	state.hideAnimationTimer = setTimeout( () => {
		trigger( { event: 'gform/snackbar/close', native: false, data: {
			el,
			options,
			state,
		},
		} );

		cleanUp();
	}, 300 );
};

/**
 * @function reveal
 * @description Reveals the snackbar and if autohide true it starts the hide timer. If speak is true, announces the
 * message as well to screenreaders.
 *
 */

export const reveal = () => {
	trigger( { event: 'gform/snackbar/pre_reveal', native: false, data: {
		el,
		options,
		state,
	},
	} );
	setTimeout( () => {
		el.container.classList.add( 'gform-snackbar--reveal' );

		if ( options.autoHide ) {
			state.hideTimer = setTimeout( () => {
				close();
			}, options.autoHideDelay );
		}

		if ( options.speak ) {
			speak( options.message );
		}

		options.onReveal();
	}, 20 );
};

/**
 * @function render
 * @description Renders the snackbar in the passed container option.
 *
 */

export const render = () => {
	el.target = getNodes( options.container, false, document, true )[ 0 ];
	if ( ! el.target ) {
		consoleError( `Gform snackBar couldn't find ${ options.container } to instantiate in.` );
	}
	el.target.style.position = 'relative';
	el.target.insertAdjacentHTML( 'beforeend', snackBarHtml() );
	el.container = getNodes( 'gform-snackbar', false, el.target )[ 0 ];
	el.closeTrigger = getNodes( 'gform-snackbar-close', false, el.target )[ 0 ];
	setAttributes( el.container, options.attributes );
};

/**
 * @function setOptions
 * @description Merges the default options with the passed args.
 *
 */

export const setOptions = ( args = {} ) => {
	options = Object.assign( {}, defaults, args );
	trigger( { event: 'gform/snackbar/pre_init', native: false, data: options } );
};

/**
 * @function bindEvents
 * @description Bind event handlers
 *
 */

const bindEvents = () => {
	if ( el.closeTrigger ) {
		el.closeTrigger.addEventListener( 'click', close );
	}
};

/**
 * @function init
 * @description Initialize and execute a snackbar.
 *
 */

const init = ( args ) => {
	cleanUp();
	setOptions( args );
	render();
	reveal();
	bindEvents();
};

export default init;