adminlte.js 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081
  1. /*!
  2. * AdminLTE v4.0.0-rc7 (https://adminlte.io)
  3. * Copyright 2014-2026 Colorlib <https://colorlib.com>
  4. * Licensed under MIT (https://github.com/ColorlibHQ/AdminLTE/blob/master/LICENSE)
  5. */
  6. (function (global, factory) {
  7. typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  8. typeof define === 'function' && define.amd ? define(['exports'], factory) :
  9. (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.adminlte = {}));
  10. })(this, (function (exports) { 'use strict';
  11. const domContentLoadedCallbacks = [];
  12. const onDOMContentLoaded = (callback) => {
  13. if (document.readyState === 'loading') {
  14. if (!domContentLoadedCallbacks.length) {
  15. document.addEventListener('DOMContentLoaded', () => {
  16. for (const callback of domContentLoadedCallbacks) {
  17. callback();
  18. }
  19. });
  20. }
  21. domContentLoadedCallbacks.push(callback);
  22. }
  23. else {
  24. callback();
  25. }
  26. };
  27. const slideUp = (target, duration = 500) => {
  28. if (duration <= 1) {
  29. target.style.display = 'none';
  30. return;
  31. }
  32. target.style.transitionProperty = 'height, margin, padding';
  33. target.style.transitionDuration = `${duration}ms`;
  34. target.style.boxSizing = 'border-box';
  35. target.style.height = `${target.offsetHeight}px`;
  36. target.style.overflow = 'hidden';
  37. globalThis.setTimeout(() => {
  38. target.style.height = '0';
  39. target.style.paddingTop = '0';
  40. target.style.paddingBottom = '0';
  41. target.style.marginTop = '0';
  42. target.style.marginBottom = '0';
  43. }, 1);
  44. globalThis.setTimeout(() => {
  45. target.style.display = 'none';
  46. target.style.removeProperty('height');
  47. target.style.removeProperty('padding-top');
  48. target.style.removeProperty('padding-bottom');
  49. target.style.removeProperty('margin-top');
  50. target.style.removeProperty('margin-bottom');
  51. target.style.removeProperty('overflow');
  52. target.style.removeProperty('transition-duration');
  53. target.style.removeProperty('transition-property');
  54. }, duration);
  55. };
  56. const slideDown = (target, duration = 500) => {
  57. target.style.removeProperty('display');
  58. let { display } = globalThis.getComputedStyle(target);
  59. if (display === 'none') {
  60. display = 'block';
  61. }
  62. target.style.display = display;
  63. if (duration <= 1) {
  64. return;
  65. }
  66. const height = target.offsetHeight;
  67. target.style.overflow = 'hidden';
  68. target.style.height = '0';
  69. target.style.paddingTop = '0';
  70. target.style.paddingBottom = '0';
  71. target.style.marginTop = '0';
  72. target.style.marginBottom = '0';
  73. globalThis.setTimeout(() => {
  74. target.style.boxSizing = 'border-box';
  75. target.style.transitionProperty = 'height, margin, padding';
  76. target.style.transitionDuration = `${duration}ms`;
  77. target.style.height = `${height}px`;
  78. target.style.removeProperty('padding-top');
  79. target.style.removeProperty('padding-bottom');
  80. target.style.removeProperty('margin-top');
  81. target.style.removeProperty('margin-bottom');
  82. }, 1);
  83. globalThis.setTimeout(() => {
  84. target.style.removeProperty('height');
  85. target.style.removeProperty('overflow');
  86. target.style.removeProperty('transition-duration');
  87. target.style.removeProperty('transition-property');
  88. }, duration);
  89. };
  90. const CLASS_NAME_HOLD_TRANSITIONS = 'hold-transition';
  91. const CLASS_NAME_APP_LOADED = 'app-loaded';
  92. class Layout {
  93. _element;
  94. _holdTransitionTimer;
  95. constructor(element) {
  96. this._element = element;
  97. this._holdTransitionTimer = undefined;
  98. }
  99. holdTransition(time = 100) {
  100. if (this._holdTransitionTimer) {
  101. clearTimeout(this._holdTransitionTimer);
  102. }
  103. document.body.classList.add(CLASS_NAME_HOLD_TRANSITIONS);
  104. this._holdTransitionTimer = setTimeout(() => {
  105. document.body.classList.remove(CLASS_NAME_HOLD_TRANSITIONS);
  106. }, time);
  107. }
  108. }
  109. onDOMContentLoaded(() => {
  110. const layout = new Layout(document.body);
  111. window.addEventListener('resize', () => layout.holdTransition(200));
  112. setTimeout(() => {
  113. document.body.classList.add(CLASS_NAME_APP_LOADED);
  114. }, 400);
  115. });
  116. const DATA_KEY$4 = 'lte.card-widget';
  117. const EVENT_KEY$4 = `.${DATA_KEY$4}`;
  118. const EVENT_COLLAPSED$2 = `collapsed${EVENT_KEY$4}`;
  119. const EVENT_EXPANDED$2 = `expanded${EVENT_KEY$4}`;
  120. const EVENT_REMOVE = `remove${EVENT_KEY$4}`;
  121. const EVENT_MAXIMIZED$1 = `maximized${EVENT_KEY$4}`;
  122. const EVENT_MINIMIZED$1 = `minimized${EVENT_KEY$4}`;
  123. const CLASS_NAME_CARD = 'card';
  124. const CLASS_NAME_COLLAPSED = 'collapsed-card';
  125. const CLASS_NAME_COLLAPSING = 'collapsing-card';
  126. const CLASS_NAME_EXPANDING = 'expanding-card';
  127. const CLASS_NAME_WAS_COLLAPSED = 'was-collapsed';
  128. const CLASS_NAME_MAXIMIZED = 'maximized-card';
  129. const SELECTOR_DATA_REMOVE = '[data-lte-toggle="card-remove"]';
  130. const SELECTOR_DATA_COLLAPSE = '[data-lte-toggle="card-collapse"]';
  131. const SELECTOR_DATA_MAXIMIZE = '[data-lte-toggle="card-maximize"]';
  132. const SELECTOR_CARD = `.${CLASS_NAME_CARD}`;
  133. const SELECTOR_CARD_BODY = '.card-body';
  134. const SELECTOR_CARD_FOOTER = '.card-footer';
  135. const Default$1 = {
  136. animationSpeed: 500,
  137. collapseTrigger: SELECTOR_DATA_COLLAPSE,
  138. removeTrigger: SELECTOR_DATA_REMOVE,
  139. maximizeTrigger: SELECTOR_DATA_MAXIMIZE
  140. };
  141. class CardWidget {
  142. _element;
  143. _parent;
  144. _clone;
  145. _config;
  146. constructor(element, config) {
  147. this._element = element;
  148. this._parent = element.closest(SELECTOR_CARD);
  149. if (element.classList.contains(CLASS_NAME_CARD)) {
  150. this._parent = element;
  151. }
  152. this._config = { ...Default$1, ...config };
  153. }
  154. collapse() {
  155. const event = new Event(EVENT_COLLAPSED$2);
  156. if (this._parent) {
  157. this._parent.classList.add(CLASS_NAME_COLLAPSING);
  158. const elm = this._parent?.querySelectorAll(`:scope > ${SELECTOR_CARD_BODY}, :scope > ${SELECTOR_CARD_FOOTER}`);
  159. elm.forEach(el => {
  160. if (el instanceof HTMLElement) {
  161. slideUp(el, this._config.animationSpeed);
  162. }
  163. });
  164. setTimeout(() => {
  165. if (this._parent) {
  166. this._parent.classList.add(CLASS_NAME_COLLAPSED);
  167. this._parent.classList.remove(CLASS_NAME_COLLAPSING);
  168. }
  169. }, this._config.animationSpeed);
  170. }
  171. this._element?.dispatchEvent(event);
  172. }
  173. expand() {
  174. const event = new Event(EVENT_EXPANDED$2);
  175. if (this._parent) {
  176. this._parent.classList.add(CLASS_NAME_EXPANDING);
  177. const elm = this._parent?.querySelectorAll(`:scope > ${SELECTOR_CARD_BODY}, :scope > ${SELECTOR_CARD_FOOTER}`);
  178. elm.forEach(el => {
  179. if (el instanceof HTMLElement) {
  180. slideDown(el, this._config.animationSpeed);
  181. }
  182. });
  183. setTimeout(() => {
  184. if (this._parent) {
  185. this._parent.classList.remove(CLASS_NAME_COLLAPSED, CLASS_NAME_EXPANDING);
  186. }
  187. }, this._config.animationSpeed);
  188. }
  189. this._element?.dispatchEvent(event);
  190. }
  191. remove() {
  192. const event = new Event(EVENT_REMOVE);
  193. if (this._parent) {
  194. slideUp(this._parent, this._config.animationSpeed);
  195. }
  196. this._element?.dispatchEvent(event);
  197. }
  198. toggle() {
  199. if (this._parent?.classList.contains(CLASS_NAME_COLLAPSED)) {
  200. this.expand();
  201. return;
  202. }
  203. this.collapse();
  204. }
  205. maximize() {
  206. const event = new Event(EVENT_MAXIMIZED$1);
  207. if (this._parent) {
  208. this._parent.style.height = `${this._parent.offsetHeight}px`;
  209. this._parent.style.width = `${this._parent.offsetWidth}px`;
  210. this._parent.style.transition = 'all .15s';
  211. setTimeout(() => {
  212. const htmlTag = document.querySelector('html');
  213. if (htmlTag) {
  214. htmlTag.classList.add(CLASS_NAME_MAXIMIZED);
  215. }
  216. if (this._parent) {
  217. this._parent.classList.add(CLASS_NAME_MAXIMIZED);
  218. if (this._parent.classList.contains(CLASS_NAME_COLLAPSED)) {
  219. this._parent.classList.add(CLASS_NAME_WAS_COLLAPSED);
  220. }
  221. }
  222. }, 150);
  223. }
  224. this._element?.dispatchEvent(event);
  225. }
  226. minimize() {
  227. const event = new Event(EVENT_MINIMIZED$1);
  228. if (this._parent) {
  229. this._parent.style.height = 'auto';
  230. this._parent.style.width = 'auto';
  231. this._parent.style.transition = 'all .15s';
  232. setTimeout(() => {
  233. const htmlTag = document.querySelector('html');
  234. if (htmlTag) {
  235. htmlTag.classList.remove(CLASS_NAME_MAXIMIZED);
  236. }
  237. if (this._parent) {
  238. this._parent.classList.remove(CLASS_NAME_MAXIMIZED);
  239. if (this._parent?.classList.contains(CLASS_NAME_WAS_COLLAPSED)) {
  240. this._parent.classList.remove(CLASS_NAME_WAS_COLLAPSED);
  241. }
  242. }
  243. }, 10);
  244. }
  245. this._element?.dispatchEvent(event);
  246. }
  247. toggleMaximize() {
  248. if (this._parent?.classList.contains(CLASS_NAME_MAXIMIZED)) {
  249. this.minimize();
  250. return;
  251. }
  252. this.maximize();
  253. }
  254. }
  255. onDOMContentLoaded(() => {
  256. const collapseBtn = document.querySelectorAll(SELECTOR_DATA_COLLAPSE);
  257. collapseBtn.forEach(btn => {
  258. btn.addEventListener('click', event => {
  259. event.preventDefault();
  260. const target = event.target;
  261. const data = new CardWidget(target, Default$1);
  262. data.toggle();
  263. });
  264. });
  265. const removeBtn = document.querySelectorAll(SELECTOR_DATA_REMOVE);
  266. removeBtn.forEach(btn => {
  267. btn.addEventListener('click', event => {
  268. event.preventDefault();
  269. const target = event.target;
  270. const data = new CardWidget(target, Default$1);
  271. data.remove();
  272. });
  273. });
  274. const maxBtn = document.querySelectorAll(SELECTOR_DATA_MAXIMIZE);
  275. maxBtn.forEach(btn => {
  276. btn.addEventListener('click', event => {
  277. event.preventDefault();
  278. const target = event.target;
  279. const data = new CardWidget(target, Default$1);
  280. data.toggleMaximize();
  281. });
  282. });
  283. });
  284. const DATA_KEY$3 = 'lte.treeview';
  285. const EVENT_KEY$3 = `.${DATA_KEY$3}`;
  286. const EVENT_EXPANDED$1 = `expanded${EVENT_KEY$3}`;
  287. const EVENT_COLLAPSED$1 = `collapsed${EVENT_KEY$3}`;
  288. const EVENT_LOAD_DATA_API = `load${EVENT_KEY$3}`;
  289. const CLASS_NAME_MENU_OPEN = 'menu-open';
  290. const SELECTOR_NAV_ITEM = '.nav-item';
  291. const SELECTOR_NAV_LINK = '.nav-link';
  292. const SELECTOR_TREEVIEW_MENU = '.nav-treeview';
  293. const SELECTOR_DATA_TOGGLE$1 = '[data-lte-toggle="treeview"]';
  294. const Default = {
  295. animationSpeed: 300,
  296. accordion: true
  297. };
  298. class Treeview {
  299. _element;
  300. _config;
  301. constructor(element, config) {
  302. this._element = element;
  303. this._config = { ...Default, ...config };
  304. }
  305. open() {
  306. const event = new Event(EVENT_EXPANDED$1);
  307. if (this._config.accordion) {
  308. const openMenuList = this._element.parentElement?.querySelectorAll(`${SELECTOR_NAV_ITEM}.${CLASS_NAME_MENU_OPEN}`);
  309. openMenuList?.forEach(openMenu => {
  310. if (openMenu !== this._element.parentElement) {
  311. openMenu.classList.remove(CLASS_NAME_MENU_OPEN);
  312. const childElement = openMenu?.querySelector(SELECTOR_TREEVIEW_MENU);
  313. if (childElement) {
  314. slideUp(childElement, this._config.animationSpeed);
  315. }
  316. }
  317. });
  318. }
  319. this._element.classList.add(CLASS_NAME_MENU_OPEN);
  320. const childElement = this._element?.querySelector(SELECTOR_TREEVIEW_MENU);
  321. if (childElement) {
  322. slideDown(childElement, this._config.animationSpeed);
  323. }
  324. this._element.dispatchEvent(event);
  325. }
  326. close() {
  327. const event = new Event(EVENT_COLLAPSED$1);
  328. this._element.classList.remove(CLASS_NAME_MENU_OPEN);
  329. const childElement = this._element?.querySelector(SELECTOR_TREEVIEW_MENU);
  330. if (childElement) {
  331. slideUp(childElement, this._config.animationSpeed);
  332. }
  333. this._element.dispatchEvent(event);
  334. }
  335. toggle() {
  336. if (this._element.classList.contains(CLASS_NAME_MENU_OPEN)) {
  337. this.close();
  338. }
  339. else {
  340. this.open();
  341. }
  342. }
  343. }
  344. onDOMContentLoaded(() => {
  345. const openMenuItems = document.querySelectorAll(`${SELECTOR_NAV_ITEM}.${CLASS_NAME_MENU_OPEN}`);
  346. openMenuItems.forEach(menuItem => {
  347. const childElement = menuItem.querySelector(SELECTOR_TREEVIEW_MENU);
  348. if (childElement) {
  349. slideDown(childElement, 0);
  350. const event = new Event(EVENT_LOAD_DATA_API);
  351. menuItem.dispatchEvent(event);
  352. }
  353. });
  354. const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE$1);
  355. button.forEach(btn => {
  356. btn.addEventListener('click', event => {
  357. const target = event.target;
  358. const targetItem = target.closest(SELECTOR_NAV_ITEM);
  359. const targetLink = target.closest(SELECTOR_NAV_LINK);
  360. const targetTreeviewMenu = targetItem?.querySelector(SELECTOR_TREEVIEW_MENU);
  361. const lteToggleElement = event.currentTarget;
  362. if (!targetTreeviewMenu) {
  363. return;
  364. }
  365. if (target?.getAttribute('href') === '#' || targetLink?.getAttribute('href') === '#') {
  366. event.preventDefault();
  367. }
  368. if (targetItem) {
  369. const accordionAttr = lteToggleElement.dataset.accordion;
  370. const animationSpeedAttr = lteToggleElement.dataset.animationSpeed;
  371. const config = {
  372. accordion: accordionAttr === undefined ? Default.accordion : accordionAttr === 'true',
  373. animationSpeed: animationSpeedAttr === undefined ? Default.animationSpeed : Number(animationSpeedAttr)
  374. };
  375. const data = new Treeview(targetItem, config);
  376. data.toggle();
  377. }
  378. });
  379. });
  380. });
  381. const DATA_KEY$2 = 'lte.direct-chat';
  382. const EVENT_KEY$2 = `.${DATA_KEY$2}`;
  383. const EVENT_EXPANDED = `expanded${EVENT_KEY$2}`;
  384. const EVENT_COLLAPSED = `collapsed${EVENT_KEY$2}`;
  385. const SELECTOR_DATA_TOGGLE = '[data-lte-toggle="chat-pane"]';
  386. const SELECTOR_DIRECT_CHAT = '.direct-chat';
  387. const CLASS_NAME_DIRECT_CHAT_OPEN = 'direct-chat-contacts-open';
  388. class DirectChat {
  389. _element;
  390. constructor(element) {
  391. this._element = element;
  392. }
  393. toggle() {
  394. if (this._element.classList.contains(CLASS_NAME_DIRECT_CHAT_OPEN)) {
  395. const event = new Event(EVENT_COLLAPSED);
  396. this._element.classList.remove(CLASS_NAME_DIRECT_CHAT_OPEN);
  397. this._element.dispatchEvent(event);
  398. }
  399. else {
  400. const event = new Event(EVENT_EXPANDED);
  401. this._element.classList.add(CLASS_NAME_DIRECT_CHAT_OPEN);
  402. this._element.dispatchEvent(event);
  403. }
  404. }
  405. }
  406. onDOMContentLoaded(() => {
  407. const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE);
  408. button.forEach(btn => {
  409. btn.addEventListener('click', event => {
  410. event.preventDefault();
  411. const target = event.target;
  412. const chatPane = target.closest(SELECTOR_DIRECT_CHAT);
  413. if (chatPane) {
  414. const data = new DirectChat(chatPane);
  415. data.toggle();
  416. }
  417. });
  418. });
  419. });
  420. const DATA_KEY$1 = 'lte.fullscreen';
  421. const EVENT_KEY$1 = `.${DATA_KEY$1}`;
  422. const EVENT_MAXIMIZED = `maximized${EVENT_KEY$1}`;
  423. const EVENT_MINIMIZED = `minimized${EVENT_KEY$1}`;
  424. const SELECTOR_FULLSCREEN_TOGGLE = '[data-lte-toggle="fullscreen"]';
  425. const SELECTOR_MAXIMIZE_ICON = '[data-lte-icon="maximize"]';
  426. const SELECTOR_MINIMIZE_ICON = '[data-lte-icon="minimize"]';
  427. class FullScreen {
  428. _element;
  429. _config;
  430. constructor(element, config) {
  431. this._element = element;
  432. this._config = config;
  433. }
  434. inFullScreen() {
  435. const event = new Event(EVENT_MAXIMIZED);
  436. const iconMaximize = document.querySelector(SELECTOR_MAXIMIZE_ICON);
  437. const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON);
  438. void document.documentElement.requestFullscreen();
  439. if (iconMaximize) {
  440. iconMaximize.style.display = 'none';
  441. }
  442. if (iconMinimize) {
  443. iconMinimize.style.display = 'block';
  444. }
  445. this._element.dispatchEvent(event);
  446. }
  447. outFullscreen() {
  448. const event = new Event(EVENT_MINIMIZED);
  449. const iconMaximize = document.querySelector(SELECTOR_MAXIMIZE_ICON);
  450. const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON);
  451. void document.exitFullscreen();
  452. if (iconMaximize) {
  453. iconMaximize.style.display = 'block';
  454. }
  455. if (iconMinimize) {
  456. iconMinimize.style.display = 'none';
  457. }
  458. this._element.dispatchEvent(event);
  459. }
  460. toggleFullScreen() {
  461. if (document.fullscreenEnabled) {
  462. if (document.fullscreenElement) {
  463. this.outFullscreen();
  464. }
  465. else {
  466. this.inFullScreen();
  467. }
  468. }
  469. }
  470. }
  471. onDOMContentLoaded(() => {
  472. const buttons = document.querySelectorAll(SELECTOR_FULLSCREEN_TOGGLE);
  473. buttons.forEach(btn => {
  474. btn.addEventListener('click', event => {
  475. event.preventDefault();
  476. const target = event.target;
  477. const button = target.closest(SELECTOR_FULLSCREEN_TOGGLE);
  478. if (button) {
  479. const data = new FullScreen(button, undefined);
  480. data.toggleFullScreen();
  481. }
  482. });
  483. });
  484. });
  485. const DATA_KEY = 'lte.push-menu';
  486. const EVENT_KEY = `.${DATA_KEY}`;
  487. const EVENT_OPEN = `open${EVENT_KEY}`;
  488. const EVENT_COLLAPSE = `collapse${EVENT_KEY}`;
  489. const CLASS_NAME_SIDEBAR_MINI = 'sidebar-mini';
  490. const CLASS_NAME_SIDEBAR_EXPAND = 'sidebar-expand';
  491. const CLASS_NAME_SIDEBAR_OVERLAY = 'sidebar-overlay';
  492. const CLASS_NAME_SIDEBAR_COLLAPSE = 'sidebar-collapse';
  493. const CLASS_NAME_SIDEBAR_OPEN = 'sidebar-open';
  494. const SELECTOR_APP_SIDEBAR = '.app-sidebar';
  495. const SELECTOR_APP_WRAPPER = '.app-wrapper';
  496. const SELECTOR_SIDEBAR_EXPAND = `[class*="${CLASS_NAME_SIDEBAR_EXPAND}"]`;
  497. const SELECTOR_SIDEBAR_TOGGLE = '[data-lte-toggle="sidebar"]';
  498. const STORAGE_KEY_SIDEBAR_STATE = 'lte.sidebar.state';
  499. const Defaults = {
  500. sidebarBreakpoint: 992,
  501. enablePersistence: false
  502. };
  503. class PushMenu {
  504. _element;
  505. _config;
  506. constructor(element, config) {
  507. this._element = element;
  508. this._config = { ...Defaults, ...config };
  509. }
  510. isCollapsed() {
  511. return document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE);
  512. }
  513. isExplicitlyOpen() {
  514. return document.body.classList.contains(CLASS_NAME_SIDEBAR_OPEN);
  515. }
  516. isMiniMode() {
  517. return document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI);
  518. }
  519. isMobileSize() {
  520. return globalThis.innerWidth <= this._config.sidebarBreakpoint;
  521. }
  522. expand() {
  523. document.body.classList.remove(CLASS_NAME_SIDEBAR_COLLAPSE);
  524. if (this.isMobileSize()) {
  525. document.body.classList.add(CLASS_NAME_SIDEBAR_OPEN);
  526. }
  527. this._element.dispatchEvent(new Event(EVENT_OPEN));
  528. }
  529. collapse() {
  530. document.body.classList.remove(CLASS_NAME_SIDEBAR_OPEN);
  531. document.body.classList.add(CLASS_NAME_SIDEBAR_COLLAPSE);
  532. this._element.dispatchEvent(new Event(EVENT_COLLAPSE));
  533. }
  534. toggle() {
  535. const isCollapsed = this.isCollapsed();
  536. if (isCollapsed) {
  537. this.expand();
  538. }
  539. else {
  540. this.collapse();
  541. }
  542. if (this._config.enablePersistence) {
  543. this.saveSidebarState(isCollapsed ? CLASS_NAME_SIDEBAR_OPEN : CLASS_NAME_SIDEBAR_COLLAPSE);
  544. }
  545. }
  546. setupSidebarBreakPoint() {
  547. const sidebarExpand = document.querySelector(SELECTOR_SIDEBAR_EXPAND);
  548. if (!sidebarExpand) {
  549. return;
  550. }
  551. const content = globalThis.getComputedStyle(sidebarExpand, '::before')
  552. .getPropertyValue('content');
  553. if (!content || content === 'none') {
  554. return;
  555. }
  556. const breakpointValue = Number(content.replace(/[^\d.-]/g, ''));
  557. if (Number.isNaN(breakpointValue)) {
  558. return;
  559. }
  560. this._config = { ...this._config, sidebarBreakpoint: breakpointValue };
  561. }
  562. updateStateByResponsiveLogic() {
  563. if (this.isMobileSize()) {
  564. if (!this.isExplicitlyOpen()) {
  565. this.collapse();
  566. }
  567. }
  568. else {
  569. if (!(this.isMiniMode() && this.isCollapsed())) {
  570. this.expand();
  571. }
  572. }
  573. }
  574. saveSidebarState(state) {
  575. if (globalThis.localStorage === undefined) {
  576. return;
  577. }
  578. try {
  579. localStorage.setItem(STORAGE_KEY_SIDEBAR_STATE, state);
  580. }
  581. catch {
  582. }
  583. }
  584. loadSidebarState() {
  585. if (globalThis.localStorage === undefined) {
  586. return;
  587. }
  588. try {
  589. const storedState = localStorage.getItem(STORAGE_KEY_SIDEBAR_STATE);
  590. if (storedState === CLASS_NAME_SIDEBAR_COLLAPSE) {
  591. this.collapse();
  592. }
  593. else if (storedState === CLASS_NAME_SIDEBAR_OPEN) {
  594. this.expand();
  595. }
  596. else {
  597. this.updateStateByResponsiveLogic();
  598. }
  599. }
  600. catch {
  601. this.updateStateByResponsiveLogic();
  602. }
  603. }
  604. clearSidebarState() {
  605. if (globalThis.localStorage === undefined) {
  606. return;
  607. }
  608. try {
  609. localStorage.removeItem(STORAGE_KEY_SIDEBAR_STATE);
  610. }
  611. catch {
  612. }
  613. }
  614. init() {
  615. this.setupSidebarBreakPoint();
  616. if (!this._config.enablePersistence) {
  617. this.clearSidebarState();
  618. }
  619. if (this._config.enablePersistence && !this.isMobileSize()) {
  620. this.loadSidebarState();
  621. }
  622. else {
  623. this.updateStateByResponsiveLogic();
  624. }
  625. }
  626. }
  627. onDOMContentLoaded(() => {
  628. const sidebar = document?.querySelector(SELECTOR_APP_SIDEBAR);
  629. if (!sidebar) {
  630. return;
  631. }
  632. const sidebarBreakpointAttr = sidebar.dataset.sidebarBreakpoint;
  633. const enablePersistenceAttr = sidebar.dataset.enablePersistence;
  634. const config = {
  635. sidebarBreakpoint: sidebarBreakpointAttr === undefined ?
  636. Defaults.sidebarBreakpoint :
  637. Number(sidebarBreakpointAttr),
  638. enablePersistence: enablePersistenceAttr === undefined ?
  639. Defaults.enablePersistence :
  640. enablePersistenceAttr === 'true'
  641. };
  642. const pushMenu = new PushMenu(sidebar, config);
  643. pushMenu.init();
  644. window.addEventListener('resize', () => {
  645. pushMenu.setupSidebarBreakPoint();
  646. pushMenu.updateStateByResponsiveLogic();
  647. });
  648. const sidebarOverlay = document.createElement('div');
  649. sidebarOverlay.className = CLASS_NAME_SIDEBAR_OVERLAY;
  650. document.querySelector(SELECTOR_APP_WRAPPER)?.append(sidebarOverlay);
  651. let overlayTouchMoved = false;
  652. sidebarOverlay.addEventListener('touchstart', () => {
  653. overlayTouchMoved = false;
  654. }, { passive: true });
  655. sidebarOverlay.addEventListener('touchmove', () => {
  656. overlayTouchMoved = true;
  657. }, { passive: true });
  658. sidebarOverlay.addEventListener('touchend', event => {
  659. if (!overlayTouchMoved) {
  660. event.preventDefault();
  661. pushMenu.collapse();
  662. }
  663. overlayTouchMoved = false;
  664. }, { passive: false });
  665. sidebarOverlay.addEventListener('click', event => {
  666. event.preventDefault();
  667. pushMenu.collapse();
  668. });
  669. const fullBtn = document.querySelectorAll(SELECTOR_SIDEBAR_TOGGLE);
  670. fullBtn.forEach(btn => {
  671. btn.addEventListener('click', event => {
  672. event.preventDefault();
  673. let button = event.currentTarget;
  674. if (button?.dataset.lteToggle !== 'sidebar') {
  675. button = button?.closest(SELECTOR_SIDEBAR_TOGGLE);
  676. }
  677. if (button) {
  678. event?.preventDefault();
  679. pushMenu.toggle();
  680. }
  681. });
  682. });
  683. });
  684. class AccessibilityManager {
  685. config;
  686. liveRegion = null;
  687. focusHistory = [];
  688. constructor(config = {}) {
  689. this.config = {
  690. announcements: true,
  691. skipLinks: true,
  692. focusManagement: true,
  693. keyboardNavigation: true,
  694. reducedMotion: true,
  695. ...config
  696. };
  697. this.init();
  698. }
  699. init() {
  700. if (this.config.announcements) {
  701. this.createLiveRegion();
  702. }
  703. if (this.config.skipLinks) {
  704. this.addSkipLinks();
  705. }
  706. if (this.config.focusManagement) {
  707. this.initFocusManagement();
  708. }
  709. if (this.config.keyboardNavigation) {
  710. this.initKeyboardNavigation();
  711. }
  712. if (this.config.reducedMotion) {
  713. this.respectReducedMotion();
  714. }
  715. this.initErrorAnnouncements();
  716. this.initTableAccessibility();
  717. this.initFormAccessibility();
  718. }
  719. createLiveRegion() {
  720. if (this.liveRegion)
  721. return;
  722. this.liveRegion = document.createElement('div');
  723. this.liveRegion.id = 'live-region';
  724. this.liveRegion.className = 'live-region';
  725. this.liveRegion.setAttribute('aria-live', 'polite');
  726. this.liveRegion.setAttribute('aria-atomic', 'true');
  727. this.liveRegion.setAttribute('role', 'status');
  728. document.body.append(this.liveRegion);
  729. }
  730. addSkipLinks() {
  731. const skipLinksContainer = document.createElement('div');
  732. skipLinksContainer.className = 'skip-links';
  733. const skipToMain = document.createElement('a');
  734. skipToMain.href = '#main';
  735. skipToMain.className = 'skip-link';
  736. skipToMain.textContent = 'Skip to main content';
  737. const skipToNav = document.createElement('a');
  738. skipToNav.href = '#navigation';
  739. skipToNav.className = 'skip-link';
  740. skipToNav.textContent = 'Skip to navigation';
  741. skipLinksContainer.append(skipToMain);
  742. skipLinksContainer.append(skipToNav);
  743. document.body.insertBefore(skipLinksContainer, document.body.firstChild);
  744. this.ensureSkipTargets();
  745. }
  746. ensureSkipTargets() {
  747. const main = document.querySelector('#main, main, [role="main"]');
  748. if (main && !main.id) {
  749. main.id = 'main';
  750. }
  751. if (main && !main.hasAttribute('tabindex')) {
  752. main.setAttribute('tabindex', '-1');
  753. }
  754. const nav = document.querySelector('#navigation, nav, [role="navigation"]');
  755. if (nav && !nav.id) {
  756. nav.id = 'navigation';
  757. }
  758. if (nav && !nav.hasAttribute('tabindex')) {
  759. nav.setAttribute('tabindex', '-1');
  760. }
  761. }
  762. initFocusManagement() {
  763. document.addEventListener('keydown', (event) => {
  764. if (event.key === 'Tab') {
  765. this.handleTabNavigation(event);
  766. }
  767. if (event.key === 'Escape') {
  768. this.handleEscapeKey(event);
  769. }
  770. });
  771. this.initModalFocusManagement();
  772. this.initDropdownFocusManagement();
  773. }
  774. handleTabNavigation(event) {
  775. const focusableElements = this.getFocusableElements();
  776. const currentIndex = focusableElements.indexOf(document.activeElement);
  777. if (event.shiftKey) {
  778. if (currentIndex <= 0) {
  779. event.preventDefault();
  780. focusableElements.at(-1)?.focus();
  781. }
  782. }
  783. else if (currentIndex >= focusableElements.length - 1) {
  784. event.preventDefault();
  785. focusableElements[0]?.focus();
  786. }
  787. }
  788. getFocusableElements() {
  789. const selector = [
  790. 'a[href]',
  791. 'button:not([disabled])',
  792. 'input:not([disabled])',
  793. 'select:not([disabled])',
  794. 'textarea:not([disabled])',
  795. '[tabindex]:not([tabindex="-1"])',
  796. '[contenteditable="true"]'
  797. ].join(', ');
  798. return Array.from(document.querySelectorAll(selector));
  799. }
  800. handleEscapeKey(event) {
  801. const activeModal = document.querySelector('.modal.show');
  802. if (activeModal) {
  803. return;
  804. }
  805. const activeDropdown = document.querySelector('.dropdown-menu.show');
  806. if (activeDropdown) {
  807. const toggleButton = document.querySelector('[data-bs-toggle="dropdown"][aria-expanded="true"]');
  808. toggleButton?.click();
  809. event.preventDefault();
  810. }
  811. }
  812. initKeyboardNavigation() {
  813. document.addEventListener('keydown', (event) => {
  814. const target = event.target;
  815. if (target.closest('.nav, .navbar-nav, .dropdown-menu')) {
  816. this.handleMenuNavigation(event);
  817. }
  818. if ((event.key === 'Enter' || event.key === ' ') && target.hasAttribute('role') && target.getAttribute('role') === 'button' && !target.matches('button, input[type="button"], input[type="submit"]')) {
  819. event.preventDefault();
  820. target.click();
  821. }
  822. });
  823. }
  824. handleMenuNavigation(event) {
  825. if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) {
  826. return;
  827. }
  828. const currentElement = event.target;
  829. const menuItems = Array.from(currentElement.closest('.nav, .navbar-nav, .dropdown-menu')?.querySelectorAll('a, button') || []);
  830. const currentIndex = menuItems.indexOf(currentElement);
  831. let nextIndex;
  832. switch (event.key) {
  833. case 'ArrowDown':
  834. case 'ArrowRight': {
  835. nextIndex = currentIndex < menuItems.length - 1 ? currentIndex + 1 : 0;
  836. break;
  837. }
  838. case 'ArrowUp':
  839. case 'ArrowLeft': {
  840. nextIndex = currentIndex > 0 ? currentIndex - 1 : menuItems.length - 1;
  841. break;
  842. }
  843. case 'Home': {
  844. nextIndex = 0;
  845. break;
  846. }
  847. case 'End': {
  848. nextIndex = menuItems.length - 1;
  849. break;
  850. }
  851. default: {
  852. return;
  853. }
  854. }
  855. event.preventDefault();
  856. menuItems[nextIndex]?.focus();
  857. }
  858. respectReducedMotion() {
  859. const prefersReducedMotion = globalThis.matchMedia('(prefers-reduced-motion: reduce)').matches;
  860. if (prefersReducedMotion) {
  861. document.body.classList.add('reduce-motion');
  862. document.documentElement.style.scrollBehavior = 'auto';
  863. const style = document.createElement('style');
  864. style.textContent = `
  865. *, *::before, *::after {
  866. animation-duration: 0.01ms !important;
  867. animation-iteration-count: 1 !important;
  868. transition-duration: 0.01ms !important;
  869. }
  870. `;
  871. document.head.append(style);
  872. }
  873. }
  874. initErrorAnnouncements() {
  875. const observer = new MutationObserver((mutations) => {
  876. mutations.forEach((mutation) => {
  877. mutation.addedNodes.forEach((node) => {
  878. if (node.nodeType === Node.ELEMENT_NODE) {
  879. const element = node;
  880. if (element.matches('.alert-danger, .invalid-feedback, .error')) {
  881. this.announce(element.textContent || 'Error occurred', 'assertive');
  882. }
  883. if (element.matches('.alert-success, .success')) {
  884. this.announce(element.textContent || 'Success', 'polite');
  885. }
  886. }
  887. });
  888. });
  889. });
  890. observer.observe(document.body, {
  891. childList: true,
  892. subtree: true
  893. });
  894. }
  895. initTableAccessibility() {
  896. document.querySelectorAll('table').forEach((table) => {
  897. if (!table.hasAttribute('role')) {
  898. table.setAttribute('role', 'table');
  899. }
  900. table.querySelectorAll('th').forEach((th) => {
  901. if (!th.hasAttribute('scope')) {
  902. const isInThead = th.closest('thead');
  903. const isFirstColumn = th.cellIndex === 0;
  904. if (isInThead) {
  905. th.setAttribute('scope', 'col');
  906. }
  907. else if (isFirstColumn) {
  908. th.setAttribute('scope', 'row');
  909. }
  910. }
  911. });
  912. if (!table.querySelector('caption') && table.hasAttribute('title')) {
  913. const caption = document.createElement('caption');
  914. caption.textContent = table.getAttribute('title') || '';
  915. table.insertBefore(caption, table.firstChild);
  916. }
  917. });
  918. }
  919. initFormAccessibility() {
  920. document.querySelectorAll('input, select, textarea').forEach((input) => {
  921. const htmlInput = input;
  922. if (!htmlInput.labels?.length && !htmlInput.hasAttribute('aria-label') && !htmlInput.hasAttribute('aria-labelledby')) {
  923. const placeholder = htmlInput.getAttribute('placeholder');
  924. if (placeholder) {
  925. htmlInput.setAttribute('aria-label', placeholder);
  926. }
  927. }
  928. if (htmlInput.hasAttribute('required')) {
  929. const label = htmlInput.labels?.[0];
  930. if (label && !label.querySelector('.required-indicator')) {
  931. const indicator = document.createElement('span');
  932. indicator.className = 'required-indicator sr-only';
  933. indicator.textContent = ' (required)';
  934. label.append(indicator);
  935. }
  936. }
  937. if (!htmlInput.classList.contains('disable-adminlte-validations')) {
  938. htmlInput.addEventListener('invalid', () => {
  939. this.handleFormError(htmlInput);
  940. });
  941. }
  942. });
  943. }
  944. handleFormError(input) {
  945. const errorId = `${input.id || input.name}-error`;
  946. let errorElement = document.getElementById(errorId);
  947. if (!errorElement) {
  948. errorElement = document.createElement('div');
  949. errorElement.id = errorId;
  950. errorElement.className = 'invalid-feedback';
  951. errorElement.setAttribute('role', 'alert');
  952. input.parentNode?.append(errorElement);
  953. }
  954. errorElement.textContent = input.validationMessage;
  955. input.setAttribute('aria-describedby', errorId);
  956. input.classList.add('is-invalid');
  957. this.announce(`Error in ${input.labels?.[0]?.textContent || input.name}: ${input.validationMessage}`, 'assertive');
  958. }
  959. initModalFocusManagement() {
  960. document.addEventListener('shown.bs.modal', (event) => {
  961. const modal = event.target;
  962. const focusableElements = modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
  963. if (focusableElements.length > 0) {
  964. focusableElements[0].focus();
  965. }
  966. this.focusHistory.push(document.activeElement);
  967. });
  968. document.addEventListener('hidden.bs.modal', () => {
  969. const previousElement = this.focusHistory.pop();
  970. if (previousElement) {
  971. previousElement.focus();
  972. }
  973. });
  974. }
  975. initDropdownFocusManagement() {
  976. document.addEventListener('shown.bs.dropdown', (event) => {
  977. const dropdown = event.target;
  978. const menu = dropdown.querySelector('.dropdown-menu');
  979. const firstItem = menu?.querySelector('a, button');
  980. if (firstItem) {
  981. firstItem.focus();
  982. }
  983. });
  984. }
  985. announce(message, priority = 'polite') {
  986. if (!this.liveRegion) {
  987. this.createLiveRegion();
  988. }
  989. if (this.liveRegion) {
  990. this.liveRegion.setAttribute('aria-live', priority);
  991. this.liveRegion.textContent = message;
  992. setTimeout(() => {
  993. if (this.liveRegion) {
  994. this.liveRegion.textContent = '';
  995. }
  996. }, 1000);
  997. }
  998. }
  999. focusElement(selector) {
  1000. const element = document.querySelector(selector);
  1001. if (element) {
  1002. element.focus();
  1003. element.scrollIntoView({ behavior: 'smooth', block: 'center' });
  1004. }
  1005. }
  1006. trapFocus(container) {
  1007. const focusableElements = container.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
  1008. const focusableArray = Array.from(focusableElements);
  1009. const firstElement = focusableArray[0];
  1010. const lastElement = focusableArray.at(-1);
  1011. container.addEventListener('keydown', (event) => {
  1012. if (event.key === 'Tab') {
  1013. if (event.shiftKey) {
  1014. if (document.activeElement === firstElement) {
  1015. lastElement?.focus();
  1016. event.preventDefault();
  1017. }
  1018. }
  1019. else if (document.activeElement === lastElement) {
  1020. firstElement.focus();
  1021. event.preventDefault();
  1022. }
  1023. }
  1024. });
  1025. }
  1026. addLandmarks() {
  1027. const main = document.querySelector('main');
  1028. if (!main) {
  1029. const appMain = document.querySelector('.app-main');
  1030. if (appMain) {
  1031. appMain.setAttribute('role', 'main');
  1032. appMain.id = 'main';
  1033. }
  1034. }
  1035. document.querySelectorAll('.navbar-nav, .nav').forEach((nav, index) => {
  1036. if (!nav.hasAttribute('role')) {
  1037. nav.setAttribute('role', 'navigation');
  1038. }
  1039. if (!nav.hasAttribute('aria-label')) {
  1040. nav.setAttribute('aria-label', `Navigation ${index + 1}`);
  1041. }
  1042. });
  1043. const searchForm = document.querySelector('form[role="search"], .navbar-search');
  1044. if (searchForm && !searchForm.hasAttribute('role')) {
  1045. searchForm.setAttribute('role', 'search');
  1046. }
  1047. }
  1048. }
  1049. const initAccessibility = (config) => {
  1050. return new AccessibilityManager(config);
  1051. };
  1052. onDOMContentLoaded(() => {
  1053. const accessibilityManager = initAccessibility({
  1054. announcements: true,
  1055. skipLinks: true,
  1056. focusManagement: true,
  1057. keyboardNavigation: true,
  1058. reducedMotion: true
  1059. });
  1060. accessibilityManager.addLandmarks();
  1061. });
  1062. exports.CardWidget = CardWidget;
  1063. exports.DirectChat = DirectChat;
  1064. exports.FullScreen = FullScreen;
  1065. exports.Layout = Layout;
  1066. exports.PushMenu = PushMenu;
  1067. exports.Treeview = Treeview;
  1068. exports.initAccessibility = initAccessibility;
  1069. }));
  1070. //# sourceMappingURL=adminlte.js.map