UNPKG

admin-lte

Version:

Responsive open source admin dashboard and control panel.

1,231 lines (1,221 loc) 55.8 kB
/*! * AdminLTE v4.1.0 (https://adminlte.io) * Copyright 2014-2026 Colorlib <https://colorlib.com> * Licensed under MIT (https://github.com/ColorlibHQ/AdminLTE/blob/master/LICENSE) */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.adminlte = {})); })(this, (function (exports) { 'use strict'; const lifecycleCallbacks = []; const lifecycleState = { controller: new AbortController(), hasInitialized: false }; const getLifecycleSignal = () => lifecycleState.controller.signal; const runLifecycleCallbacks = () => { if (lifecycleState.hasInitialized) { return; } lifecycleState.hasInitialized = true; for (const callback of lifecycleCallbacks) { callback(); } }; const onDOMContentLoaded = (callback) => { lifecycleCallbacks.push(callback); if (lifecycleState.hasInitialized) { callback(); } }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', runLifecycleCallbacks, { once: true }); } else { runLifecycleCallbacks(); } document.addEventListener('turbo:before-render', () => { lifecycleState.controller.abort(); lifecycleState.controller = new AbortController(); lifecycleState.hasInitialized = false; }); document.addEventListener('turbo:load', runLifecycleCallbacks); const slideTimers = new WeakMap(); const cancelSlide = (target) => { const timers = slideTimers.get(target) ?? []; for (const timer of timers) { globalThis.clearTimeout(timer); } slideTimers.delete(target); }; const clearSlideStyles = (target) => { for (const property of ['height', 'padding-top', 'padding-bottom', 'margin-top', 'margin-bottom', 'overflow', 'transition-duration', 'transition-property']) { target.style.removeProperty(property); } }; const slideUp = (target, duration = 500) => { cancelSlide(target); if (duration <= 1) { target.style.display = 'none'; clearSlideStyles(target); return; } target.style.transitionProperty = 'height, margin, padding'; target.style.transitionDuration = `${duration}ms`; target.style.boxSizing = 'border-box'; target.style.height = `${target.offsetHeight}px`; target.style.overflow = 'hidden'; const stepTimer = globalThis.setTimeout(() => { target.style.height = '0'; target.style.paddingTop = '0'; target.style.paddingBottom = '0'; target.style.marginTop = '0'; target.style.marginBottom = '0'; }, 1); const cleanupTimer = globalThis.setTimeout(() => { target.style.display = 'none'; clearSlideStyles(target); slideTimers.delete(target); }, duration); slideTimers.set(target, [stepTimer, cleanupTimer]); }; const slideDown = (target, duration = 500) => { cancelSlide(target); clearSlideStyles(target); target.style.removeProperty('display'); let { display } = globalThis.getComputedStyle(target); if (display === 'none') { display = 'block'; } target.style.display = display; if (duration <= 1) { return; } const height = target.offsetHeight; target.style.overflow = 'hidden'; target.style.height = '0'; target.style.paddingTop = '0'; target.style.paddingBottom = '0'; target.style.marginTop = '0'; target.style.marginBottom = '0'; const stepTimer = globalThis.setTimeout(() => { target.style.boxSizing = 'border-box'; target.style.transitionProperty = 'height, margin, padding'; target.style.transitionDuration = `${duration}ms`; target.style.height = `${height}px`; target.style.removeProperty('padding-top'); target.style.removeProperty('padding-bottom'); target.style.removeProperty('margin-top'); target.style.removeProperty('margin-bottom'); }, 1); const cleanupTimer = globalThis.setTimeout(() => { clearSlideStyles(target); slideTimers.delete(target); }, duration); slideTimers.set(target, [stepTimer, cleanupTimer]); }; const CLASS_NAME_HOLD_TRANSITIONS = 'hold-transition'; const CLASS_NAME_APP_LOADED = 'app-loaded'; class Layout { _element; _holdTransitionTimer; constructor(element) { this._element = element; this._holdTransitionTimer = undefined; } holdTransition(time = 100) { if (this._holdTransitionTimer) { clearTimeout(this._holdTransitionTimer); } document.body.classList.add(CLASS_NAME_HOLD_TRANSITIONS); this._holdTransitionTimer = setTimeout(() => { document.body.classList.remove(CLASS_NAME_HOLD_TRANSITIONS); }, time); } } onDOMContentLoaded(() => { const layout = new Layout(document.body); window.addEventListener('resize', () => layout.holdTransition(200), { signal: getLifecycleSignal() }); setTimeout(() => { document.body.classList.add(CLASS_NAME_APP_LOADED); }, 400); }); const componentRegistry = new WeakMap(); class BaseComponent { static get NAME() { throw new Error('Component subclasses must override the static NAME getter.'); } static get DATA_KEY() { return `lte.${this.NAME}`; } static _getInstance(element) { if (!element) { return null; } return componentRegistry.get(element)?.get(this.DATA_KEY) ?? null; } _element; constructor(element) { this._element = element; const instances = componentRegistry.get(element) ?? new Map(); componentRegistry.set(element, instances); instances.set(this.constructor.DATA_KEY, this); } dispose() { const instances = componentRegistry.get(this._element); instances?.delete(this.constructor.DATA_KEY); if (instances?.size === 0) { componentRegistry.delete(this._element); } } } const dispatchCustomEvent = (element, name, options = {}) => { const event = new CustomEvent(name, { bubbles: true, cancelable: options.cancelable ?? false, detail: options.detail }); element.dispatchEvent(event); return event; }; const NAME$4 = 'card-widget'; const EVENT_KEY$5 = `.lte.${NAME$4}`; const EVENT_COLLAPSE$2 = `collapse${EVENT_KEY$5}`; const EVENT_EXPAND$1 = `expand${EVENT_KEY$5}`; const EVENT_REMOVE = `remove${EVENT_KEY$5}`; const EVENT_COLLAPSED$3 = `collapsed${EVENT_KEY$5}`; const EVENT_EXPANDED$2 = `expanded${EVENT_KEY$5}`; const EVENT_REMOVED = `removed${EVENT_KEY$5}`; const EVENT_MAXIMIZED$1 = `maximized${EVENT_KEY$5}`; const EVENT_MINIMIZED$1 = `minimized${EVENT_KEY$5}`; const CLASS_NAME_CARD = 'card'; const CLASS_NAME_COLLAPSED = 'collapsed-card'; const CLASS_NAME_COLLAPSING = 'collapsing-card'; const CLASS_NAME_EXPANDING = 'expanding-card'; const CLASS_NAME_WAS_COLLAPSED = 'was-collapsed'; const CLASS_NAME_MAXIMIZED = 'maximized-card'; const SELECTOR_DATA_REMOVE = '[data-lte-toggle="card-remove"]'; const SELECTOR_DATA_COLLAPSE = '[data-lte-toggle="card-collapse"]'; const SELECTOR_DATA_MAXIMIZE = '[data-lte-toggle="card-maximize"]'; const SELECTOR_CARD = `.${CLASS_NAME_CARD}`; const SELECTOR_CARD_BODY = '.card-body'; const SELECTOR_CARD_FOOTER = '.card-footer'; const Default$1 = { animationSpeed: 500, collapseTrigger: SELECTOR_DATA_COLLAPSE, removeTrigger: SELECTOR_DATA_REMOVE, maximizeTrigger: SELECTOR_DATA_MAXIMIZE }; class CardWidget extends BaseComponent { static get NAME() { return NAME$4; } static getInstance(element) { return this._getInstance(element); } static getOrCreateInstance(element, config = {}) { return this.getInstance(element) ?? new this(element, config); } _parent; _config; constructor(element, config = {}) { super(element); this._parent = element.closest(SELECTOR_CARD); if (element.classList.contains(CLASS_NAME_CARD)) { this._parent = element; } this._config = { ...Default$1, ...config }; } collapse() { if (!this._parent) { return; } if (dispatchCustomEvent(this._parent, EVENT_COLLAPSE$2, { cancelable: true }).defaultPrevented) { return; } this._parent.classList.add(CLASS_NAME_COLLAPSING); this._parent.classList.remove(CLASS_NAME_EXPANDING); const elm = this._parent.querySelectorAll(`:scope > ${SELECTOR_CARD_BODY}, :scope > ${SELECTOR_CARD_FOOTER}`); elm.forEach(el => { if (el instanceof HTMLElement) { slideUp(el, this._config.animationSpeed); } }); setTimeout(() => { if (this._parent?.classList.contains(CLASS_NAME_COLLAPSING)) { this._parent.classList.add(CLASS_NAME_COLLAPSED); this._parent.classList.remove(CLASS_NAME_COLLAPSING); dispatchCustomEvent(this._parent, EVENT_COLLAPSED$3); } }, this._config.animationSpeed); } expand() { if (!this._parent) { return; } if (dispatchCustomEvent(this._parent, EVENT_EXPAND$1, { cancelable: true }).defaultPrevented) { return; } this._parent.classList.add(CLASS_NAME_EXPANDING); this._parent.classList.remove(CLASS_NAME_COLLAPSING, CLASS_NAME_COLLAPSED); const elm = this._parent.querySelectorAll(`:scope > ${SELECTOR_CARD_BODY}, :scope > ${SELECTOR_CARD_FOOTER}`); elm.forEach(el => { if (el instanceof HTMLElement) { slideDown(el, this._config.animationSpeed); } }); setTimeout(() => { if (this._parent?.classList.contains(CLASS_NAME_EXPANDING)) { this._parent.classList.remove(CLASS_NAME_EXPANDING); dispatchCustomEvent(this._parent, EVENT_EXPANDED$2); } }, this._config.animationSpeed); } remove() { if (!this._parent) { return; } if (dispatchCustomEvent(this._parent, EVENT_REMOVE, { cancelable: true }).defaultPrevented) { return; } const parent = this._parent; slideUp(parent, this._config.animationSpeed); setTimeout(() => { dispatchCustomEvent(parent, EVENT_REMOVED); parent.remove(); this.dispose(); }, this._config.animationSpeed); } toggle() { if (this._parent?.classList.contains(CLASS_NAME_COLLAPSED) || this._parent?.classList.contains(CLASS_NAME_COLLAPSING)) { this.expand(); return; } this.collapse(); } maximize() { if (this._parent) { this._parent.style.height = `${this._parent.offsetHeight}px`; this._parent.style.width = `${this._parent.offsetWidth}px`; this._parent.style.transition = 'all .15s'; setTimeout(() => { const htmlTag = document.querySelector('html'); if (htmlTag) { htmlTag.classList.add(CLASS_NAME_MAXIMIZED); } if (this._parent) { this._parent.classList.add(CLASS_NAME_MAXIMIZED); if (this._parent.classList.contains(CLASS_NAME_COLLAPSED)) { this._parent.classList.add(CLASS_NAME_WAS_COLLAPSED); } dispatchCustomEvent(this._parent, EVENT_MAXIMIZED$1); } }, 150); } } minimize() { if (this._parent) { this._parent.style.height = 'auto'; this._parent.style.width = 'auto'; this._parent.style.transition = 'all .15s'; setTimeout(() => { const htmlTag = document.querySelector('html'); if (htmlTag) { htmlTag.classList.remove(CLASS_NAME_MAXIMIZED); } if (this._parent) { this._parent.classList.remove(CLASS_NAME_MAXIMIZED); if (this._parent?.classList.contains(CLASS_NAME_WAS_COLLAPSED)) { this._parent.classList.remove(CLASS_NAME_WAS_COLLAPSED); } dispatchCustomEvent(this._parent, EVENT_MINIMIZED$1); setTimeout(() => { this._parent?.style.removeProperty('height'); this._parent?.style.removeProperty('width'); this._parent?.style.removeProperty('transition'); }, 150); } }, 10); } } toggleMaximize() { if (this._parent?.classList.contains(CLASS_NAME_MAXIMIZED)) { this.minimize(); return; } this.maximize(); } } document.addEventListener('click', event => { const target = event.target; if (!(target instanceof Element)) { return; } const collapseTrigger = target.closest(SELECTOR_DATA_COLLAPSE); const removeTrigger = target.closest(SELECTOR_DATA_REMOVE); const maximizeTrigger = target.closest(SELECTOR_DATA_MAXIMIZE); const trigger = collapseTrigger ?? removeTrigger ?? maximizeTrigger; if (!trigger) { return; } event.preventDefault(); const card = trigger.closest(SELECTOR_CARD); if (!card) { return; } const widget = CardWidget.getOrCreateInstance(card); if (collapseTrigger) { widget.toggle(); } else if (removeTrigger) { widget.remove(); } else { widget.toggleMaximize(); } }); const NAME$3 = 'treeview'; const EVENT_KEY$4 = `.lte.${NAME$3}`; const EVENT_EXPAND = `expand${EVENT_KEY$4}`; const EVENT_COLLAPSE$1 = `collapse${EVENT_KEY$4}`; const EVENT_EXPANDED$1 = `expanded${EVENT_KEY$4}`; const EVENT_COLLAPSED$2 = `collapsed${EVENT_KEY$4}`; const EVENT_LOAD_DATA_API = `load${EVENT_KEY$4}`; 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$1 = '[data-lte-toggle="treeview"]'; const Default = { animationSpeed: 300, accordion: true }; const setAriaExpanded = (navItem, expanded) => { const link = navItem.querySelector(`:scope > ${SELECTOR_NAV_LINK}`); link?.setAttribute('aria-expanded', String(expanded)); }; class Treeview extends BaseComponent { static get NAME() { return NAME$3; } static getInstance(element) { return this._getInstance(element); } static getOrCreateInstance(element, config = {}) { return this.getInstance(element) ?? new this(element, config); } _config; constructor(element, config = {}) { super(element); this._config = { ...Default, ...config }; } open() { 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 => { if (!this._element.contains(openMenu)) { openMenu.classList.remove(CLASS_NAME_MENU_OPEN); setAriaExpanded(openMenu, false); const childElement = openMenu?.querySelector(SELECTOR_TREEVIEW_MENU); if (childElement) { slideUp(childElement, this._config.animationSpeed); } } }); } this._element.classList.add(CLASS_NAME_MENU_OPEN); setAriaExpanded(this._element, true); const childElement = this._element.querySelector(SELECTOR_TREEVIEW_MENU); if (childElement) { slideDown(childElement, this._config.animationSpeed); } setTimeout(() => { if (this._element.classList.contains(CLASS_NAME_MENU_OPEN)) { dispatchCustomEvent(this._element, EVENT_EXPANDED$1); } }, this._config.animationSpeed); } close() { if (dispatchCustomEvent(this._element, EVENT_COLLAPSE$1, { cancelable: true }).defaultPrevented) { return; } this._element.classList.remove(CLASS_NAME_MENU_OPEN); setAriaExpanded(this._element, false); const childElement = this._element.querySelector(SELECTOR_TREEVIEW_MENU); if (childElement) { slideUp(childElement, this._config.animationSpeed); } setTimeout(() => { if (!this._element.classList.contains(CLASS_NAME_MENU_OPEN)) { dispatchCustomEvent(this._element, EVENT_COLLAPSED$2); } }, this._config.animationSpeed); } toggle() { if (this._element.classList.contains(CLASS_NAME_MENU_OPEN)) { this.close(); } else { this.open(); } } } document.addEventListener('click', event => { const target = event.target; if (!(target instanceof Element)) { return; } const toggleRoot = target.closest(SELECTOR_DATA_TOGGLE$1); if (!toggleRoot) { return; } const targetItem = target.closest(SELECTOR_NAV_ITEM); const targetLink = target.closest(SELECTOR_NAV_LINK); if (!targetItem?.querySelector(SELECTOR_TREEVIEW_MENU)) { return; } if (target.getAttribute('href') === '#' || targetLink?.getAttribute('href') === '#') { event.preventDefault(); } const accordionAttr = toggleRoot.dataset.accordion; const animationSpeedAttr = toggleRoot.dataset.animationSpeed; const config = { accordion: accordionAttr === undefined ? Default.accordion : accordionAttr === 'true', animationSpeed: animationSpeedAttr === undefined ? Default.animationSpeed : Number(animationSpeedAttr) }; Treeview.getOrCreateInstance(targetItem, config).toggle(); }); onDOMContentLoaded(() => { const openMenuItems = document.querySelectorAll(`${SELECTOR_NAV_ITEM}.${CLASS_NAME_MENU_OPEN}`); openMenuItems.forEach(menuItem => { const childElement = menuItem.querySelector(SELECTOR_TREEVIEW_MENU); if (childElement) { slideDown(childElement, 0); const event = new Event(EVENT_LOAD_DATA_API); menuItem.dispatchEvent(event); } }); document.querySelectorAll(SELECTOR_DATA_TOGGLE$1).forEach(root => { root.querySelectorAll(SELECTOR_NAV_ITEM).forEach(item => { if (item.querySelector(`:scope > ${SELECTOR_TREEVIEW_MENU}`)) { setAriaExpanded(item, item.classList.contains(CLASS_NAME_MENU_OPEN)); } }); }); }); const NAME$2 = 'direct-chat'; const EVENT_KEY$3 = `.lte.${NAME$2}`; const EVENT_EXPANDED = `expanded${EVENT_KEY$3}`; const EVENT_COLLAPSED$1 = `collapsed${EVENT_KEY$3}`; const SELECTOR_DATA_TOGGLE = '[data-lte-toggle="chat-pane"]'; const SELECTOR_DIRECT_CHAT = '.direct-chat'; const CLASS_NAME_DIRECT_CHAT_OPEN = 'direct-chat-contacts-open'; class DirectChat extends BaseComponent { static get NAME() { return NAME$2; } static getInstance(element) { return this._getInstance(element); } static getOrCreateInstance(element) { return this.getInstance(element) ?? new this(element); } toggle() { if (this._element.classList.contains(CLASS_NAME_DIRECT_CHAT_OPEN)) { this._element.classList.remove(CLASS_NAME_DIRECT_CHAT_OPEN); dispatchCustomEvent(this._element, EVENT_COLLAPSED$1); } else { this._element.classList.add(CLASS_NAME_DIRECT_CHAT_OPEN); dispatchCustomEvent(this._element, EVENT_EXPANDED); } } } document.addEventListener('click', event => { const target = event.target; if (!(target instanceof Element)) { return; } const trigger = target.closest(SELECTOR_DATA_TOGGLE); if (!trigger) { return; } event.preventDefault(); const chatPane = trigger.closest(SELECTOR_DIRECT_CHAT); if (chatPane) { DirectChat.getOrCreateInstance(chatPane).toggle(); } }); const NAME$1 = 'fullscreen'; const EVENT_KEY$2 = `.lte.${NAME$1}`; const EVENT_MAXIMIZED = `maximized${EVENT_KEY$2}`; const EVENT_MINIMIZED = `minimized${EVENT_KEY$2}`; const SELECTOR_FULLSCREEN_TOGGLE = '[data-lte-toggle="fullscreen"]'; const SELECTOR_MAXIMIZE_ICON = '[data-lte-icon="maximize"]'; const SELECTOR_MINIMIZE_ICON = '[data-lte-icon="minimize"]'; function syncFullScreenState() { const iconMaximize = document.querySelector(SELECTOR_MAXIMIZE_ICON); const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON); const isFullScreen = Boolean(document.fullscreenElement); iconMaximize?.classList.toggle('d-none', isFullScreen); iconMinimize?.classList.toggle('d-none', !isFullScreen); const eventName = isFullScreen ? EVENT_MAXIMIZED : EVENT_MINIMIZED; document.querySelectorAll(SELECTOR_FULLSCREEN_TOGGLE).forEach(button => { dispatchCustomEvent(button, eventName); }); } class FullScreen extends BaseComponent { static get NAME() { return NAME$1; } static getInstance(element) { return this._getInstance(element); } static getOrCreateInstance(element) { return this.getInstance(element) ?? new this(element); } inFullScreen() { void document.documentElement.requestFullscreen().catch(() => { }); } outFullscreen() { void document.exitFullscreen().catch(() => { }); } toggleFullScreen() { if (!document.fullscreenEnabled) { return; } if (document.fullscreenElement) { this.outFullscreen(); } else { this.inFullScreen(); } } } document.addEventListener('click', event => { const target = event.target; if (!(target instanceof Element)) { return; } const button = target.closest(SELECTOR_FULLSCREEN_TOGGLE); if (!button) { return; } event.preventDefault(); FullScreen.getOrCreateInstance(button).toggleFullScreen(); }); onDOMContentLoaded(() => { document.addEventListener('fullscreenchange', syncFullScreenState, { signal: getLifecycleSignal() }); }); const NAME = 'push-menu'; const EVENT_KEY$1 = `.lte.${NAME}`; const EVENT_OPEN = `open${EVENT_KEY$1}`; const EVENT_COLLAPSE = `collapse${EVENT_KEY$1}`; const EVENT_OPENED = `opened${EVENT_KEY$1}`; const EVENT_COLLAPSED = `collapsed${EVENT_KEY$1}`; const CLASS_NAME_SIDEBAR_MINI = 'sidebar-mini'; const CLASS_NAME_SIDEBAR_EXPAND = 'sidebar-expand'; const CLASS_NAME_SIDEBAR_OVERLAY = 'sidebar-overlay'; const CLASS_NAME_SIDEBAR_COLLAPSE = 'sidebar-collapse'; const CLASS_NAME_SIDEBAR_OPEN = 'sidebar-open'; const SELECTOR_APP_SIDEBAR = '.app-sidebar'; const SELECTOR_APP_WRAPPER = '.app-wrapper'; const SELECTOR_SIDEBAR_EXPAND = `[class*="${CLASS_NAME_SIDEBAR_EXPAND}"]`; const SELECTOR_SIDEBAR_TOGGLE = '[data-lte-toggle="sidebar"]'; const STORAGE_KEY_SIDEBAR_STATE = 'lte.sidebar.state'; const Defaults = { sidebarBreakpoint: 991.98, enablePersistence: false }; class PushMenu extends BaseComponent { static get NAME() { return NAME; } static getInstance(element) { return this._getInstance(element); } static getOrCreateInstance(element, config = {}) { return this.getInstance(element) ?? new this(element, config); } _config; constructor(element, config = {}) { super(element); this._config = { ...Defaults, ...config }; } isCollapsed() { return document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE); } isExplicitlyOpen() { return document.body.classList.contains(CLASS_NAME_SIDEBAR_OPEN); } isMiniMode() { return document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI); } isMobileSize() { return globalThis.innerWidth <= this._config.sidebarBreakpoint; } expand() { if (dispatchCustomEvent(this._element, EVENT_OPEN, { cancelable: true }).defaultPrevented) { return; } document.body.classList.remove(CLASS_NAME_SIDEBAR_COLLAPSE); if (this.isMobileSize()) { document.body.classList.add(CLASS_NAME_SIDEBAR_OPEN); } dispatchCustomEvent(this._element, EVENT_OPENED); } collapse() { if (dispatchCustomEvent(this._element, EVENT_COLLAPSE, { cancelable: true }).defaultPrevented) { return; } document.body.classList.remove(CLASS_NAME_SIDEBAR_OPEN); document.body.classList.add(CLASS_NAME_SIDEBAR_COLLAPSE); dispatchCustomEvent(this._element, EVENT_COLLAPSED); } toggle() { const isCollapsed = this.isCollapsed(); if (isCollapsed) { this.expand(); } else { this.collapse(); } if (this._config.enablePersistence) { this.saveSidebarState(isCollapsed ? CLASS_NAME_SIDEBAR_OPEN : CLASS_NAME_SIDEBAR_COLLAPSE); } } setupSidebarBreakPoint() { const sidebarExpand = document.querySelector(SELECTOR_SIDEBAR_EXPAND); if (!sidebarExpand) { return; } const content = globalThis.getComputedStyle(sidebarExpand, '::before') .getPropertyValue('content'); if (!content || content === 'none') { return; } const breakpointValue = Number(content.replace(/[^\d.-]/g, '')); if (Number.isNaN(breakpointValue)) { return; } this._config = { ...this._config, sidebarBreakpoint: breakpointValue }; } updateStateByResponsiveLogic() { if (this.isMobileSize()) { if (!this.isExplicitlyOpen()) { this.collapse(); } } else { if (!(this.isMiniMode() && this.isCollapsed())) { this.expand(); } } } saveSidebarState(state) { if (globalThis.localStorage === undefined) { return; } try { localStorage.setItem(STORAGE_KEY_SIDEBAR_STATE, state); } catch { } } loadSidebarState() { if (globalThis.localStorage === undefined) { return; } try { const storedState = localStorage.getItem(STORAGE_KEY_SIDEBAR_STATE); if (storedState === CLASS_NAME_SIDEBAR_COLLAPSE) { this.collapse(); } else if (storedState === CLASS_NAME_SIDEBAR_OPEN) { this.expand(); } else { this.updateStateByResponsiveLogic(); } } catch { this.updateStateByResponsiveLogic(); } } clearSidebarState() { if (globalThis.localStorage === undefined) { return; } try { localStorage.removeItem(STORAGE_KEY_SIDEBAR_STATE); } catch { } } init() { this.setupSidebarBreakPoint(); if (!this._config.enablePersistence) { this.clearSidebarState(); } if (this._config.enablePersistence && !this.isMobileSize()) { this.loadSidebarState(); } else { this.updateStateByResponsiveLogic(); } } } document.addEventListener('click', event => { const target = event.target; if (!(target instanceof Element)) { return; } const button = target.closest(SELECTOR_SIDEBAR_TOGGLE); if (!button) { return; } event.preventDefault(); const sidebar = document.querySelector(SELECTOR_APP_SIDEBAR); if (sidebar) { PushMenu.getOrCreateInstance(sidebar).toggle(); } }); onDOMContentLoaded(() => { const sidebar = document.querySelector(SELECTOR_APP_SIDEBAR); if (!sidebar) { return; } const sidebarBreakpointAttr = sidebar.dataset.sidebarBreakpoint; const enablePersistenceAttr = sidebar.dataset.enablePersistence; const config = { sidebarBreakpoint: sidebarBreakpointAttr === undefined ? Defaults.sidebarBreakpoint : Number(sidebarBreakpointAttr), enablePersistence: enablePersistenceAttr === undefined ? Defaults.enablePersistence : enablePersistenceAttr === 'true' }; const pushMenu = PushMenu.getOrCreateInstance(sidebar, config); pushMenu.init(); const breakpointQuery = globalThis.matchMedia(`(max-width: ${pushMenu._config.sidebarBreakpoint}px)`); breakpointQuery.addEventListener('change', () => { pushMenu.updateStateByResponsiveLogic(); }, { signal: getLifecycleSignal() }); const appWrapper = document.querySelector(SELECTOR_APP_WRAPPER); let sidebarOverlay = appWrapper?.querySelector(`:scope > .${CLASS_NAME_SIDEBAR_OVERLAY}`); if (!sidebarOverlay) { sidebarOverlay = document.createElement('div'); sidebarOverlay.className = CLASS_NAME_SIDEBAR_OVERLAY; appWrapper?.append(sidebarOverlay); } let overlayTouchMoved = false; sidebarOverlay.addEventListener('touchstart', () => { overlayTouchMoved = false; }, { passive: true }); sidebarOverlay.addEventListener('touchmove', () => { overlayTouchMoved = true; }, { passive: true }); sidebarOverlay.addEventListener('touchend', event => { if (!overlayTouchMoved) { event.preventDefault(); pushMenu.collapse(); } overlayTouchMoved = false; }, { passive: false }); sidebarOverlay.addEventListener('click', event => { event.preventDefault(); pushMenu.collapse(); }); }); const DATA_KEY = 'lte.color-mode'; const EVENT_KEY = `.${DATA_KEY}`; const EVENT_CHANGED = `changed${EVENT_KEY}`; const STORAGE_KEY = 'lte-theme'; const SELECTOR_TOGGLE = '[data-bs-theme-value]'; const SELECTOR_ICON = '[data-lte-theme-icon]'; class ColorMode { getStoredTheme() { try { const stored = localStorage.getItem(STORAGE_KEY); return stored && ['light', 'dark', 'auto'].includes(stored) ? stored : null; } catch { return null; } } getPreferredTheme() { const stored = this.getStoredTheme(); if (stored) { return stored; } return this._prefersDark() ? 'dark' : 'light'; } resolveTheme(theme) { if (theme === 'auto') { return this._prefersDark() ? 'dark' : 'light'; } return theme; } setTheme(theme) { try { localStorage.setItem(STORAGE_KEY, theme); } catch { } this._applyTheme(theme); this._showActiveTheme(theme); document.dispatchEvent(new CustomEvent(EVENT_CHANGED, { detail: { theme, resolved: this.resolveTheme(theme) } })); } _applyTheme(theme) { const resolved = this.resolveTheme(theme); document.documentElement.setAttribute('data-bs-theme', resolved); document.documentElement.style.colorScheme = resolved; } _prefersDark() { return globalThis.matchMedia('(prefers-color-scheme: dark)').matches; } _showActiveTheme(theme) { document.querySelectorAll(SELECTOR_TOGGLE).forEach(toggle => { const isActive = toggle.getAttribute('data-bs-theme-value') === theme; toggle.classList.toggle('active', isActive); toggle.setAttribute('aria-pressed', String(isActive)); toggle.querySelector('.bi-check-lg')?.classList.toggle('d-none', !isActive); }); document.querySelectorAll(SELECTOR_ICON).forEach(icon => { icon.classList.toggle('d-none', icon.dataset.lteThemeIcon !== theme); }); } init() { const theme = this.getPreferredTheme(); this._applyTheme(theme); this._showActiveTheme(theme); } } document.addEventListener('click', event => { const target = event.target; if (!(target instanceof Element)) { return; } const toggle = target.closest(SELECTOR_TOGGLE); const theme = toggle?.getAttribute('data-bs-theme-value'); if (theme) { new ColorMode().setTheme(theme); } }); onDOMContentLoaded(() => { const colorMode = new ColorMode(); colorMode.init(); globalThis.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { const stored = colorMode.getStoredTheme(); if (!stored || stored === 'auto') { colorMode._applyTheme('auto'); colorMode._showActiveTheme(stored ?? 'auto'); } }, { signal: getLifecycleSignal() }); }); class AccessibilityManager { config; liveRegion = null; focusHistory = []; signal = getLifecycleSignal(); constructor(config = {}) { this.config = { announcements: true, skipLinks: true, focusManagement: true, keyboardNavigation: true, reducedMotion: true, ...config }; this.init(); } init() { if (this.config.announcements) { this.createLiveRegion(); } if (this.config.skipLinks) { this.addSkipLinks(); } if (this.config.focusManagement) { this.initFocusManagement(); } if (this.config.keyboardNavigation) { this.initKeyboardNavigation(); } if (this.config.reducedMotion) { this.respectReducedMotion(); } this.initErrorAnnouncements(); this.initTableAccessibility(); this.initFormAccessibility(); } createLiveRegion() { if (this.liveRegion) return; const existingRegion = document.getElementById('live-region'); if (existingRegion) { this.liveRegion = existingRegion; return; } this.liveRegion = document.createElement('div'); this.liveRegion.id = 'live-region'; this.liveRegion.className = 'live-region'; this.liveRegion.setAttribute('aria-live', 'polite'); this.liveRegion.setAttribute('aria-atomic', 'true'); this.liveRegion.setAttribute('role', 'status'); document.body.append(this.liveRegion); } addSkipLinks() { if (document.querySelector('.skip-links')) { this.ensureSkipTargets(); return; } const skipLinksContainer = document.createElement('div'); skipLinksContainer.className = 'skip-links'; const skipToMain = document.createElement('a'); skipToMain.href = '#main'; skipToMain.className = 'skip-link'; skipToMain.textContent = 'Skip to main content'; const skipToNav = document.createElement('a'); skipToNav.href = '#navigation'; skipToNav.className = 'skip-link'; skipToNav.textContent = 'Skip to navigation'; skipLinksContainer.append(skipToMain); skipLinksContainer.append(skipToNav); document.body.insertBefore(skipLinksContainer, document.body.firstChild); this.ensureSkipTargets(); } ensureSkipTargets() { const main = document.querySelector('#main, main, [role="main"]'); if (main && !main.id) { main.id = 'main'; } if (main && !main.hasAttribute('tabindex')) { main.setAttribute('tabindex', '-1'); } const nav = document.querySelector('#navigation, nav, [role="navigation"]'); if (nav && !nav.id) { nav.id = 'navigation'; } if (nav && !nav.hasAttribute('tabindex')) { nav.setAttribute('tabindex', '-1'); } } initFocusManagement() { document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { this.handleEscapeKey(event); } }, { signal: this.signal }); this.initModalFocusManagement(); this.initDropdownFocusManagement(); } handleEscapeKey(event) { const activeModal = document.querySelector('.modal.show'); if (activeModal) { return; } const activeDropdown = document.querySelector('.dropdown-menu.show'); if (activeDropdown) { const toggleButton = document.querySelector('[data-bs-toggle="dropdown"][aria-expanded="true"]'); toggleButton?.click(); event.preventDefault(); } } initKeyboardNavigation() { document.addEventListener('keydown', (event) => { const target = event.target; if (target.matches('input, textarea, select, [contenteditable], [contenteditable] *')) { return; } if (target.closest('.nav, .navbar-nav, .dropdown-menu')) { this.handleMenuNavigation(event); } if ((event.key === 'Enter' || event.key === ' ') && target.hasAttribute('role') && target.getAttribute('role') === 'button' && !target.matches('button, input[type="button"], input[type="submit"]')) { event.preventDefault(); target.click(); } }, { signal: this.signal }); } handleMenuNavigation(event) { if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) { return; } const currentElement = event.target; const menuItems = Array.from(currentElement.closest('.nav, .navbar-nav, .dropdown-menu')?.querySelectorAll('a, button') || []) .filter(item => item.offsetParent !== null); const currentIndex = menuItems.indexOf(currentElement); if (currentIndex === -1) { return; } let nextIndex; switch (event.key) { case 'ArrowDown': case 'ArrowRight': { nextIndex = currentIndex < menuItems.length - 1 ? currentIndex + 1 : 0; break; } case 'ArrowUp': case 'ArrowLeft': { nextIndex = currentIndex > 0 ? currentIndex - 1 : menuItems.length - 1; break; } case 'Home': { nextIndex = 0; break; } case 'End': { nextIndex = menuItems.length - 1; break; } default: { return; } } event.preventDefault(); menuItems[nextIndex]?.focus(); } respectReducedMotion() { const prefersReducedMotion = globalThis.matchMedia('(prefers-reduced-motion: reduce)').matches; if (prefersReducedMotion) { document.body.classList.add('reduce-motion'); document.documentElement.style.scrollBehavior = 'auto'; if (!document.getElementById('adminlte-reduce-motion')) { const style = document.createElement('style'); style.id = 'adminlte-reduce-motion'; style.textContent = ` *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } `; document.head.append(style); } } } initErrorAnnouncements() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { const element = node; if (element.matches('.alert-danger, .invalid-feedback, .error')) { this.announce(element.textContent || 'Error occurred', 'assertive'); } if (element.matches('.alert-success, .success')) { this.announce(element.textContent || 'Success', 'polite'); } } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); this.signal.addEventListener('abort', () => { observer.disconnect(); }, { once: true }); } initTableAccessibility() { document.querySelectorAll('table').forEach((table) => { if (!table.hasAttribute('role')) { table.setAttribute('role', 'table'); } table.querySelectorAll('th').forEach((th) => { if (!th.hasAttribute('scope')) { const isInThead = th.closest('thead'); const isFirstColumn = th.cellIndex === 0; if (isInThead) { th.setAttribute('scope', 'col'); } else if (isFirstColumn) { th.setAttribute('scope', 'row'); } } }); if (!table.querySelector('caption') && table.hasAttribute('title')) { const caption = document.createElement('caption'); caption.textContent = table.getAttribute('title') || ''; table.insertBefore(caption, table.firstChild); } }); } initFormAccessibility() { document.querySelectorAll('input, select, textarea').forEach((input) => { const htmlInput = input; if (!htmlInput.labels?.length && !htmlInput.hasAttribute('aria-label') && !htmlInput.hasAttribute('aria-labelledby')) { const placeholder = htmlInput.getAttribute('placeholder'); if (placeholder) { htmlInput.setAttribute('aria-label', placeholder); } } if (htmlInput.hasAttribute('required')) { const label = htmlInput.labels?.[0]; if (label && !label.querySelector('.required-indicator')) { const indicator = document.createElement('span'); indicator.className = 'required-indicator sr-only'; indicator.textContent = ' (required)'; label.append(indicator); } } if (!htmlInput.classList.contains('disable-adminlte-validations')) { htmlInput.addEventListener('invalid', () => { this.handleFormError(htmlInput); }); } }); } handleFormError(input) { if (!input.id && !input.name) { input.id = accessibilityUtils.generateId('field'); } const errorId = `${input.id || input.name}-error`; let errorElement = document.getElementById(errorId); if (!errorElement) { errorElement = document.createElement('div'); errorElement.id = errorId; errorElement.className = 'invalid-feedback'; errorElement.setAttribute('role', 'alert'); input.parentNode?.append(errorElement); } errorElement.textContent = input.validationMessage; const describedBy = (input.getAttribute('aria-describedby') || '').split(/\s+/).filter(Boolean); if (!describedBy.includes(errorId)) { describedBy.push(errorId); } input.setAttribute('aria-describedby', describedBy.join(' ')); input.classList.add('is-invalid'); this.announce(`Error in ${input.labels?.[0]?.textContent || input.name}: ${input.validationMessage}`, 'assertive'); } initModalFocusManagement() { document.addEventListener('show.bs.modal', () => { this.focusHistory.push(document.activeElement); }, { signal: this.signal }); document.addEventListener('shown.bs.modal', (event) => { const modal = event.target; const autofocusElement = modal.querySelector('[autofocus]'); const firstFocusable = autofocusElement || modal.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); firstFocusable?.focus(); }, { signal: this.signal }); document.addEventListener('hidden.bs.modal', () => { const previousElement = this.focusHistory.pop(); if (previousElement?.isConnected) { previousElement.focus(); } }, { signal: this.signal }); } initDropdownFocusManagement() { document.addEventListener('shown.bs.dropdown', (event) => { const dropdown = event.target; co