ui/Accordion.js

import { createEvents } from '@/core/modules/createEvents'
import mitt from 'mitt'

/** *
 * @namespace Accordion
 * @class Accordion
 * @desc This class handles accordions... 
 * @example 
 * 
 * js: 
 * 
 * new Accordion(document.getElementById('accordion'), { 
 * 	closeOthers: true 
 * }, 'ui-key').mount()
 * 
 * Required markup:
 * 
 * <ul data-ui="Accordion" data-ui-options='{"close-others": true, "active-index": 1}' data-ui-key="downloads-accordion">
		<li>
			<a href="#bt1" data-accordion-button>Datasheets</a>
			<ul class="list-reset hidden" data-accordion-dropdown id="bt1">...</ul>
		</li>
		<li>
			<a href="#bt2" data-accordion-button>Datasheets</a>
			<ul class="list-reset hidden" data-accordion-dropdown id="bt2">...</ul>
		</li>
		<li>
			<a href="#bt3" data-accordion-button>Datasheets</a>
			<ul class="list-reset hidden" data-accordion-dropdown id="bt3">...</ul>
		</li>
	</ul>
 *

 * @param {HTMLElement} el - node to bind to
 * @param {Object} options - options
 * @param {String} key - key name
 *
 * @property {Boolean} options.closeOthers - only allow one accordion to be open at a time
 * @property {Number} options.activeIndex - set one of the accordions to be open
 * 
 * @return {Accordion}
 */

export default class Accordion {
	defaults = {
		closeOthers: false,
		activeIndex: undefined
	}

	constructor(el, options, key) {
		this.options = { ...this.defaults, ...options }
		this.key = key

		Object.assign(this, mitt())

		this.$el = el
		// bind the dom events
		this.$$events = createEvents.call(this, this.$el, this.events)

		this.$selectedIndex = undefined

		this.$panels = [
			...this.$el.querySelectorAll('[data-accordion-button]')
		].map((button, index) => {
			const href = button.getAttribute('href')
			const target = this.$el.querySelector(href)

			button.setAttribute('data-accordion-key', index)

			return {
				button,
				target,
				machine: {
					open: { CLICK: 'close' },
					close: { CLICK: 'open' }
				},
				state: this.options.activeIndex === index ? 'open' : 'close'
			}
		})

		if (typeof this.options.activeIndex !== 'undefined') {
			this.open(this.options.activeIndex)
			this.$selectedIndex = this.options.activeIndex
		}
	}

	/** *
	 * @memberof Accordion
	 * @method mount
	 * @desc Add the events
	 *
	 * @return {void}
	 */
	mount = () => {
		this.$$events.attachAll()
	}

	events = {
		'click [data-accordion-button]': 'onClick'
	}

	/** *
	 * @memberof Accordion
	 * @method unmount
	 * @desc remove the events
	 *
	 * @return {void}
	 */
	unmount = () => {
		this.$$events.destroy()
	}

	/** *
	 * @memberof Accordion
	 * @method onClick
	 * @desc the click event...
	 * @param {Object} e : the event object
	 * @param {HTMLElement} elm : the node clicked
	 *
	 * @return {void}
	 */
	onClick = (e, elm) => {
		e.preventDefault()
		const { closeOthers } = this.options
		const { accordionKey } = elm.dataset
		const key = parseInt(accordionKey, 10)
		// get the current state
		const { state } = this.$panels[key]
		// get the new action
		const action = this.$panels[key].machine[state].CLICK

		// if close others, and we have some to close... close it
		if (
			closeOthers &&
			typeof this.$selectedIndex !== 'undefined' &&
			this.$selectedIndex !== key
		) {
			this.close(this.$selectedIndex)
		}

		// do the new action
		this[action](key)

		// update the selected item ref
		this.$selectedIndex = key
	}

	/** *
	 * @memberof Accordion
	 * @method open
	 * @desc Open the accordion
	 * @param {Number} index : index of the accordion to open
	 *
	 * @return {void}
	 */
	open = index => {
		this.$panels[index].state = 'open'
		const { button, target } = this.$panels[index]

		button.classList.add('is-active')
		target.style.display = 'block'
		// target.querySelectorAll('a')[0].focus()
	}

	/** *
	 * @memberof Accordion
	 * @method close
	 * @desc Close the accordion
	 * @param {Number} index : index of the accordion to close
	 *
	 * @return {void}
	 */
	close = index => {
		this.$panels[index].state = 'close'
		const { button, target } = this.$panels[index]

		button.classList.remove('is-active')
		button.focus()
		target.style.display = ''
	}
}