import { aClass, rClass } from "../../DOM/Class"; import { extend } from "../../Helpers/Extend"; import { on, off } from "../../Events/EventsManager"; import Tab from "./Tab"; const DEFAULT_OPTIONS = { tabSelector: "button[aria-expanded]", allowMultipleTab: false, atLeastOneOpen: false, animations: { open: function ($TAB, $TAB_PANNEL) { aClass([$TAB, $TAB_PANNEL], "on"); return Promise.resolve(); }, close: function ($TAB, $TAB_PANNEL) { rClass([$TAB, $TAB_PANNEL], "on"); return Promise.resolve(); }, destroy: function ($TAB, $TAB_PANNEL) { rClass([$TAB, $TAB_PANNEL], "on"); return Promise.resolve(); }, }, }; /** * Accordion * * @example * ```ts * new Accordion( document.querySelector( '.accordion' ), { * "tabSelector": ".tab", * "allowMultipleTab": false, * "atLeastOneOpen": false, * "animations": { * "open": function( $tab, $panel ) { * aClass( [ $tab, $panel ], 'on' ); * * return Promise.resolve(); * }, * "close": function( $tab, $panel ) { * rClass( [ $tab, $panel ], 'on' ); * * return Promise.resolve(); * }, * "destroy": function( $tab, $panel ) { * rClass( [ $tab, $panel ], 'on' ); * * return Promise.resolve(); * } * }, * "onOpenAtStart": function( $tab, $panel ) { * console.log( 'open: ', $tab, $panel ); * }, * "onOpen": function( $tab, $panel ) { * console.log( 'open: ', $tab, $panel ); * }, * "onClose": function( $tab, $panel ) { * console.log( 'close: ', $tab, $panel ); * } * } ); * ``` * * HTML: * * ```html *
* *
*

Content

*
* * *
*

Content

*
*
* ``` * * Set aria-expanded to "true" on the tab you want open at start */ export default class Accordion { #$accordionWrapper: HTMLElement; #options: FLib.Accordion.Options; #$tabs: NodeListOf; #tablist = new Map(); #status: string; #lastOpenedTabs = new Set(); #STATUS_ON = "STATUS_ON"; #STATUS_OFF = "STATUS_OFF"; constructor( $accordionWrapper: HTMLElement, userOptions: FLib.Accordion.OptionsInit, ) { this.#$accordionWrapper = $accordionWrapper; this.#options = extend(DEFAULT_OPTIONS, userOptions); this.#$tabs = $accordionWrapper.querySelectorAll( this.#options.tabSelector, ); this.#status = this.#STATUS_OFF; this.#on(); } #on = (): void => { if (this.#status === this.#STATUS_ON) { return; } this.#status = this.#STATUS_ON; this.#$tabs.forEach(($tab, index) => { const tab = new Tab($tab, { ...this.#options, index, }); this.#tablist.set($tab, tab); if (tab.isOpen) { this.#lastOpenedTabs.add(tab); } }); on(this.#$accordionWrapper, { eventsName: "click", selector: this.#options.tabSelector, callback: this.#toggleTab, }); }; #off = (): void => { if (this.#status === this.#STATUS_OFF) { return; } this.#status = this.#STATUS_OFF; this.#tablist.forEach((tab) => { tab.destroy(); }); this.#tablist.clear(); off(this.#$accordionWrapper, { eventsName: "click", callback: this.#toggleTab, }); }; #toggleTab = (e: Event, $target: HTMLElement): void => { e.preventDefault(); const tab = this.#tablist.get($target); if (!tab) { return; } if (tab.isOpen) { this.closeTab(tab); return; } this.openTab(tab); }; closeTab(tab: Tab): this { if (this.#options.atLeastOneOpen && this.#lastOpenedTabs.size < 2) { return this; } tab.close(false); this.#lastOpenedTabs.delete(tab); return this; } openTab(tab: Tab): this { if (!this.#options.allowMultipleTab && this.#lastOpenedTabs.size > 0) { this.#lastOpenedTabs.forEach((tab) => { tab.close(true); }); this.#lastOpenedTabs.clear(); } tab.open(); this.#lastOpenedTabs.add(tab); return this; } openTabByElement($tab: HTMLElement): this { const tab = this.#tablist.get($tab); if (tab) { this.openTab(tab); } return this; } /** * Remove all events, css class, ... */ destroy(): this { this.#off(); return this; } /** * Restart the module */ update(): this { this.#off(); this.#on(); return this; } }