/** * -------------------------------------------- * @file AdminLTE treeview.ts * @description Treeview plugin for AdminLTE. * @license MIT * -------------------------------------------- */ import { BaseComponent, dispatchCustomEvent } from './base-component' import { onDOMContentLoaded, slideDown, slideUp } from './util/index' /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ const NAME = 'treeview' const EVENT_KEY = `.lte.${NAME}` // Cancelable "before" events (dispatched on the nav-item as an action starts) const EVENT_EXPAND = `expand${EVENT_KEY}` const EVENT_COLLAPSE = `collapse${EVENT_KEY}` // "After" events (dispatched on the nav-item when the animation finished) const EVENT_EXPANDED = `expanded${EVENT_KEY}` const EVENT_COLLAPSED = `collapsed${EVENT_KEY}` const EVENT_LOAD_DATA_API = `load${EVENT_KEY}` const CLASS_NAME_MENU_OPEN = 'menu-open' const SELECTOR_NAV_ITEM = '.nav-item' const SELECTOR_NAV_LINK = '.nav-link' const SELECTOR_TREEVIEW_MENU = '.nav-treeview' const SELECTOR_DATA_TOGGLE = '[data-lte-toggle="treeview"]' const Default = { animationSpeed: 300, accordion: true } type Config = { animationSpeed: number; accordion: boolean; } /** * Reflect a submenu's open state on its toggle link so screen readers can * announce the treeview as expandable/expanded (WCAG 4.1.2). */ const setAriaExpanded = (navItem: Element, expanded: boolean): void => { const link = navItem.querySelector(`:scope > ${SELECTOR_NAV_LINK}`) link?.setAttribute('aria-expanded', String(expanded)) } /** * Class Definition * ==================================================== */ class Treeview extends BaseComponent { static get NAME(): string { return NAME } static getInstance(element: Element | null | undefined): Treeview | null { return this._getInstance(element) as Treeview | null } static getOrCreateInstance(element: HTMLElement, config: Partial = {}): Treeview { return this.getInstance(element) ?? new this(element, config) } _config: Config constructor(element: HTMLElement, config: Partial = {}) { super(element) this._config = { ...Default, ...config } } open(): void { if (dispatchCustomEvent(this._element, EVENT_EXPAND, { cancelable: true }).defaultPrevented) { return } if (this._config.accordion) { const openMenuList = this._element.parentElement?.querySelectorAll(`${SELECTOR_NAV_ITEM}.${CLASS_NAME_MENU_OPEN}`) openMenuList?.forEach(openMenu => { // Skip the item being opened and anything nested inside it. The old // guard compared against the parent