import { queryAll, queryOne } from '@ecl/dom-utils';import EventManager from '@ecl/event-manager';/** * @param {HTMLElement} element DOM element for component instantiation and scope * @param {Object} options * @param {String} options.toggleSelector Selector for toggling element * @param {String} options.iconSelector Selector for icon element * @param {Boolean} options.attachClickListener Whether or not to bind click events on toggle */export class Accordion { /** * @static * Shorthand for instance creation and initialisation. * * @param {HTMLElement} root DOM element for component instantiation and scope * * @return {Accordion} An instance of Accordion. */ static autoInit(root, { ACCORDION: defaultOptions = {} } = {}) { const accordion = new Accordion(root, defaultOptions); accordion.init(); root.ECLAccordion = accordion; return accordion; } /** * An array of supported events for this component. * * @type {Array<string>} * @event Accordion#onToggle * @memberof Accordion */ supportedEvents = ['onToggle']; constructor( element, { toggleSelector = '[data-ecl-accordion-toggle]', iconSelector = '[data-ecl-accordion-icon]', attachClickListener = true, } = {}, ) { // Check element if (!element || element.nodeType !== Node.ELEMENT_NODE) { throw new TypeError( 'DOM element should be given to initialize this widget.', ); } this.element = element; this.eventManager = new EventManager(); // Options this.toggleSelector = toggleSelector; this.iconSelector = iconSelector; this.attachClickListener = attachClickListener; // Private variables this.toggles = null; this.forceClose = false; this.target = null; // Bind `this` for use in callbacks this.handleClickOutside = this.handleClickOutside.bind(this); this.handleClickOnToggle = this.handleClickOnToggle.bind(this); } /** * Initialise component. */ init() { if (!ECL) { throw new TypeError('Called init but ECL is not present'); } ECL.components = ECL.components || new Map(); document.addEventListener('click', this.handleClickOutside); this.toggles = queryAll(this.toggleSelector, this.element); // Bind click event on toggles if (this.attachClickListener && this.toggles) { this.toggles.forEach((toggle) => { toggle.addEventListener('click', (event) => this.handleClickOnToggle(event, toggle), ); }); } // Set ecl initialized attribute this.element.setAttribute('data-ecl-auto-initialized', 'true'); ECL.components.set(this.element, this); } /** * Register a callback function for a specific event. * * @param {string} eventName - The name of the event to listen for. * @param {Function} callback - The callback function to be invoked when the event occurs. * @returns {void} * @memberof Accordion * @instance * * @example * // Registering a callback for the 'click' event * accordion.on('onToggle', (event) => { * console.log('Toggle event occurred!', event); * }); */ on(eventName, callback) { this.eventManager.on(eventName, callback); } /** * Trigger a component event. * * @param {string} eventName - The name of the event to trigger. * @param {any} eventData - Data associated with the event. * * @memberof Accordion */ trigger(eventName, eventData) { this.eventManager.trigger(eventName, eventData); } /** * Destroy component. */ destroy() { document.removeEventListener('click', this.handleClickOutside); if (this.attachClickListener && this.toggles) { this.toggles.forEach((toggle) => { toggle.replaceWith(toggle.cloneNode(true)); }); } if (this.element) { this.element.removeAttribute('data-ecl-auto-initialized'); ECL.components.delete(this.element); } } /** * @param {e} Event */ handleClickOutside(e) { if (e.target && this.toggles && !this.element.contains(e.target)) { this.toggles.forEach((item) => item.classList.remove('ecl-accordion__toggle--active'), ); } } /** * @param {HTMLElement} toggle Target element to toggle. * * @fires Accordion#onToggle */ handleClickOnToggle(event, toggle) { let isOpening = false; // Get target element const target = queryOne( `#${toggle.getAttribute('aria-controls')}`, this.element, ); // Exit if no target found if (!target) { throw new TypeError( 'Target has to be provided for accordion (aria-controls)', ); } // Get current status const isExpanded = this.forceClose === true || toggle.getAttribute('aria-expanded') === 'true'; // Toggle the expandable/collapsible toggle.setAttribute('aria-expanded', isExpanded ? 'false' : 'true'); if (isExpanded) { target.hidden = true; } else { target.hidden = false; isOpening = true; } const eventData = { item: target, isOpening }; this.trigger('onToggle', eventData); // Toggle icon const iconElement = queryOne(this.iconSelector, toggle); if (iconElement) { const useNode = queryOne('use', iconElement); if (useNode) { const originalXlinkHref = useNode.getAttribute('xlink:href'); let newXlinkHref = ''; if (isExpanded) { newXlinkHref = originalXlinkHref.replace('minus', 'plus'); } else { newXlinkHref = originalXlinkHref.replace('plus', 'minus'); } useNode.setAttribute('xlink:href', newXlinkHref); } } this.toggles.forEach((item) => item.classList.remove('ecl-accordion__toggle--active'), ); // This is the way we distinguish the click from a press on Enter if (event.detail > 0) { toggle.classList.add('ecl-accordion__toggle--active'); } }}export default Accordion;