adminlte.js 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258
  1. /*!
  2. * AdminLTE v4.0.0-rc5 (https://adminlte.io)
  3. * Copyright 2014-2025 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. // add listener on the first call when the document is in loading state
  15. if (!domContentLoadedCallbacks.length) {
  16. document.addEventListener('DOMContentLoaded', () => {
  17. for (const callback of domContentLoadedCallbacks) {
  18. callback();
  19. }
  20. });
  21. }
  22. domContentLoadedCallbacks.push(callback);
  23. }
  24. else {
  25. callback();
  26. }
  27. };
  28. /* SLIDE UP */
  29. const slideUp = (target, duration = 500) => {
  30. target.style.transitionProperty = 'height, margin, padding';
  31. target.style.transitionDuration = `${duration}ms`;
  32. target.style.boxSizing = 'border-box';
  33. target.style.height = `${target.offsetHeight}px`;
  34. target.style.overflow = 'hidden';
  35. globalThis.setTimeout(() => {
  36. target.style.height = '0';
  37. target.style.paddingTop = '0';
  38. target.style.paddingBottom = '0';
  39. target.style.marginTop = '0';
  40. target.style.marginBottom = '0';
  41. }, 1);
  42. globalThis.setTimeout(() => {
  43. target.style.display = 'none';
  44. target.style.removeProperty('height');
  45. target.style.removeProperty('padding-top');
  46. target.style.removeProperty('padding-bottom');
  47. target.style.removeProperty('margin-top');
  48. target.style.removeProperty('margin-bottom');
  49. target.style.removeProperty('overflow');
  50. target.style.removeProperty('transition-duration');
  51. target.style.removeProperty('transition-property');
  52. }, duration);
  53. };
  54. /* SLIDE DOWN */
  55. const slideDown = (target, duration = 500) => {
  56. target.style.removeProperty('display');
  57. let { display } = globalThis.getComputedStyle(target);
  58. if (display === 'none') {
  59. display = 'block';
  60. }
  61. target.style.display = display;
  62. const height = target.offsetHeight;
  63. target.style.overflow = 'hidden';
  64. target.style.height = '0';
  65. target.style.paddingTop = '0';
  66. target.style.paddingBottom = '0';
  67. target.style.marginTop = '0';
  68. target.style.marginBottom = '0';
  69. globalThis.setTimeout(() => {
  70. target.style.boxSizing = 'border-box';
  71. target.style.transitionProperty = 'height, margin, padding';
  72. target.style.transitionDuration = `${duration}ms`;
  73. target.style.height = `${height}px`;
  74. target.style.removeProperty('padding-top');
  75. target.style.removeProperty('padding-bottom');
  76. target.style.removeProperty('margin-top');
  77. target.style.removeProperty('margin-bottom');
  78. }, 1);
  79. globalThis.setTimeout(() => {
  80. target.style.removeProperty('height');
  81. target.style.removeProperty('overflow');
  82. target.style.removeProperty('transition-duration');
  83. target.style.removeProperty('transition-property');
  84. }, duration);
  85. };
  86. /**
  87. * --------------------------------------------
  88. * @file AdminLTE layout.ts
  89. * @description Layout for AdminLTE.
  90. * @license MIT
  91. * --------------------------------------------
  92. */
  93. /**
  94. * ------------------------------------------------------------------------
  95. * Constants
  96. * ------------------------------------------------------------------------
  97. */
  98. const CLASS_NAME_HOLD_TRANSITIONS = 'hold-transition';
  99. const CLASS_NAME_APP_LOADED = 'app-loaded';
  100. /**
  101. * Class Definition
  102. * ====================================================
  103. */
  104. class Layout {
  105. _element;
  106. constructor(element) {
  107. this._element = element;
  108. }
  109. holdTransition() {
  110. let resizeTimer;
  111. window.addEventListener('resize', () => {
  112. document.body.classList.add(CLASS_NAME_HOLD_TRANSITIONS);
  113. clearTimeout(resizeTimer);
  114. resizeTimer = setTimeout(() => {
  115. document.body.classList.remove(CLASS_NAME_HOLD_TRANSITIONS);
  116. }, 400);
  117. });
  118. }
  119. }
  120. onDOMContentLoaded(() => {
  121. const data = new Layout(document.body);
  122. data.holdTransition();
  123. setTimeout(() => {
  124. document.body.classList.add(CLASS_NAME_APP_LOADED);
  125. }, 400);
  126. });
  127. /**
  128. * --------------------------------------------
  129. * @file AdminLTE card-widget.ts
  130. * @description Card widget for AdminLTE.
  131. * @license MIT
  132. * --------------------------------------------
  133. */
  134. /**
  135. * Constants
  136. * ====================================================
  137. */
  138. const DATA_KEY$4 = 'lte.card-widget';
  139. const EVENT_KEY$4 = `.${DATA_KEY$4}`;
  140. const EVENT_COLLAPSED$2 = `collapsed${EVENT_KEY$4}`;
  141. const EVENT_EXPANDED$2 = `expanded${EVENT_KEY$4}`;
  142. const EVENT_REMOVE = `remove${EVENT_KEY$4}`;
  143. const EVENT_MAXIMIZED$1 = `maximized${EVENT_KEY$4}`;
  144. const EVENT_MINIMIZED$1 = `minimized${EVENT_KEY$4}`;
  145. const CLASS_NAME_CARD = 'card';
  146. const CLASS_NAME_COLLAPSED = 'collapsed-card';
  147. const CLASS_NAME_COLLAPSING = 'collapsing-card';
  148. const CLASS_NAME_EXPANDING = 'expanding-card';
  149. const CLASS_NAME_WAS_COLLAPSED = 'was-collapsed';
  150. const CLASS_NAME_MAXIMIZED = 'maximized-card';
  151. const SELECTOR_DATA_REMOVE = '[data-lte-toggle="card-remove"]';
  152. const SELECTOR_DATA_COLLAPSE = '[data-lte-toggle="card-collapse"]';
  153. const SELECTOR_DATA_MAXIMIZE = '[data-lte-toggle="card-maximize"]';
  154. const SELECTOR_CARD = `.${CLASS_NAME_CARD}`;
  155. const SELECTOR_CARD_BODY = '.card-body';
  156. const SELECTOR_CARD_FOOTER = '.card-footer';
  157. const Default$1 = {
  158. animationSpeed: 500,
  159. collapseTrigger: SELECTOR_DATA_COLLAPSE,
  160. removeTrigger: SELECTOR_DATA_REMOVE,
  161. maximizeTrigger: SELECTOR_DATA_MAXIMIZE
  162. };
  163. class CardWidget {
  164. _element;
  165. _parent;
  166. _clone;
  167. _config;
  168. constructor(element, config) {
  169. this._element = element;
  170. this._parent = element.closest(SELECTOR_CARD);
  171. if (element.classList.contains(CLASS_NAME_CARD)) {
  172. this._parent = element;
  173. }
  174. this._config = { ...Default$1, ...config };
  175. }
  176. collapse() {
  177. const event = new Event(EVENT_COLLAPSED$2);
  178. if (this._parent) {
  179. this._parent.classList.add(CLASS_NAME_COLLAPSING);
  180. const elm = this._parent?.querySelectorAll(`${SELECTOR_CARD_BODY}, ${SELECTOR_CARD_FOOTER}`);
  181. elm.forEach(el => {
  182. if (el instanceof HTMLElement) {
  183. slideUp(el, this._config.animationSpeed);
  184. }
  185. });
  186. setTimeout(() => {
  187. if (this._parent) {
  188. this._parent.classList.add(CLASS_NAME_COLLAPSED);
  189. this._parent.classList.remove(CLASS_NAME_COLLAPSING);
  190. }
  191. }, this._config.animationSpeed);
  192. }
  193. this._element?.dispatchEvent(event);
  194. }
  195. expand() {
  196. const event = new Event(EVENT_EXPANDED$2);
  197. if (this._parent) {
  198. this._parent.classList.add(CLASS_NAME_EXPANDING);
  199. const elm = this._parent?.querySelectorAll(`${SELECTOR_CARD_BODY}, ${SELECTOR_CARD_FOOTER}`);
  200. elm.forEach(el => {
  201. if (el instanceof HTMLElement) {
  202. slideDown(el, this._config.animationSpeed);
  203. }
  204. });
  205. setTimeout(() => {
  206. if (this._parent) {
  207. this._parent.classList.remove(CLASS_NAME_COLLAPSED, CLASS_NAME_EXPANDING);
  208. }
  209. }, this._config.animationSpeed);
  210. }
  211. this._element?.dispatchEvent(event);
  212. }
  213. remove() {
  214. const event = new Event(EVENT_REMOVE);
  215. if (this._parent) {
  216. slideUp(this._parent, this._config.animationSpeed);
  217. }
  218. this._element?.dispatchEvent(event);
  219. }
  220. toggle() {
  221. if (this._parent?.classList.contains(CLASS_NAME_COLLAPSED)) {
  222. this.expand();
  223. return;
  224. }
  225. this.collapse();
  226. }
  227. maximize() {
  228. const event = new Event(EVENT_MAXIMIZED$1);
  229. if (this._parent) {
  230. this._parent.style.height = `${this._parent.offsetHeight}px`;
  231. this._parent.style.width = `${this._parent.offsetWidth}px`;
  232. this._parent.style.transition = 'all .15s';
  233. setTimeout(() => {
  234. const htmlTag = document.querySelector('html');
  235. if (htmlTag) {
  236. htmlTag.classList.add(CLASS_NAME_MAXIMIZED);
  237. }
  238. if (this._parent) {
  239. this._parent.classList.add(CLASS_NAME_MAXIMIZED);
  240. if (this._parent.classList.contains(CLASS_NAME_COLLAPSED)) {
  241. this._parent.classList.add(CLASS_NAME_WAS_COLLAPSED);
  242. }
  243. }
  244. }, 150);
  245. }
  246. this._element?.dispatchEvent(event);
  247. }
  248. minimize() {
  249. const event = new Event(EVENT_MINIMIZED$1);
  250. if (this._parent) {
  251. this._parent.style.height = 'auto';
  252. this._parent.style.width = 'auto';
  253. this._parent.style.transition = 'all .15s';
  254. setTimeout(() => {
  255. const htmlTag = document.querySelector('html');
  256. if (htmlTag) {
  257. htmlTag.classList.remove(CLASS_NAME_MAXIMIZED);
  258. }
  259. if (this._parent) {
  260. this._parent.classList.remove(CLASS_NAME_MAXIMIZED);
  261. if (this._parent?.classList.contains(CLASS_NAME_WAS_COLLAPSED)) {
  262. this._parent.classList.remove(CLASS_NAME_WAS_COLLAPSED);
  263. }
  264. }
  265. }, 10);
  266. }
  267. this._element?.dispatchEvent(event);
  268. }
  269. toggleMaximize() {
  270. if (this._parent?.classList.contains(CLASS_NAME_MAXIMIZED)) {
  271. this.minimize();
  272. return;
  273. }
  274. this.maximize();
  275. }
  276. }
  277. /**
  278. *
  279. * Data Api implementation
  280. * ====================================================
  281. */
  282. onDOMContentLoaded(() => {
  283. const collapseBtn = document.querySelectorAll(SELECTOR_DATA_COLLAPSE);
  284. collapseBtn.forEach(btn => {
  285. btn.addEventListener('click', event => {
  286. event.preventDefault();
  287. const target = event.target;
  288. const data = new CardWidget(target, Default$1);
  289. data.toggle();
  290. });
  291. });
  292. const removeBtn = document.querySelectorAll(SELECTOR_DATA_REMOVE);
  293. removeBtn.forEach(btn => {
  294. btn.addEventListener('click', event => {
  295. event.preventDefault();
  296. const target = event.target;
  297. const data = new CardWidget(target, Default$1);
  298. data.remove();
  299. });
  300. });
  301. const maxBtn = document.querySelectorAll(SELECTOR_DATA_MAXIMIZE);
  302. maxBtn.forEach(btn => {
  303. btn.addEventListener('click', event => {
  304. event.preventDefault();
  305. const target = event.target;
  306. const data = new CardWidget(target, Default$1);
  307. data.toggleMaximize();
  308. });
  309. });
  310. });
  311. /**
  312. * --------------------------------------------
  313. * @file AdminLTE treeview.ts
  314. * @description Treeview plugin for AdminLTE.
  315. * @license MIT
  316. * --------------------------------------------
  317. */
  318. /**
  319. * ------------------------------------------------------------------------
  320. * Constants
  321. * ------------------------------------------------------------------------
  322. */
  323. // const NAME = 'Treeview'
  324. const DATA_KEY$3 = 'lte.treeview';
  325. const EVENT_KEY$3 = `.${DATA_KEY$3}`;
  326. const EVENT_EXPANDED$1 = `expanded${EVENT_KEY$3}`;
  327. const EVENT_COLLAPSED$1 = `collapsed${EVENT_KEY$3}`;
  328. const EVENT_LOAD_DATA_API = `load${EVENT_KEY$3}`;
  329. const CLASS_NAME_MENU_OPEN$1 = 'menu-open';
  330. const SELECTOR_NAV_ITEM$1 = '.nav-item';
  331. const SELECTOR_NAV_LINK = '.nav-link';
  332. const SELECTOR_TREEVIEW_MENU = '.nav-treeview';
  333. const SELECTOR_DATA_TOGGLE$1 = '[data-lte-toggle="treeview"]';
  334. const Default = {
  335. animationSpeed: 300,
  336. accordion: true
  337. };
  338. /**
  339. * Class Definition
  340. * ====================================================
  341. */
  342. class Treeview {
  343. _element;
  344. _config;
  345. constructor(element, config) {
  346. this._element = element;
  347. this._config = { ...Default, ...config };
  348. }
  349. open() {
  350. const event = new Event(EVENT_EXPANDED$1);
  351. if (this._config.accordion) {
  352. const openMenuList = this._element.parentElement?.querySelectorAll(`${SELECTOR_NAV_ITEM$1}.${CLASS_NAME_MENU_OPEN$1}`);
  353. openMenuList?.forEach(openMenu => {
  354. if (openMenu !== this._element.parentElement) {
  355. openMenu.classList.remove(CLASS_NAME_MENU_OPEN$1);
  356. const childElement = openMenu?.querySelector(SELECTOR_TREEVIEW_MENU);
  357. if (childElement) {
  358. slideUp(childElement, this._config.animationSpeed);
  359. }
  360. }
  361. });
  362. }
  363. this._element.classList.add(CLASS_NAME_MENU_OPEN$1);
  364. const childElement = this._element?.querySelector(SELECTOR_TREEVIEW_MENU);
  365. if (childElement) {
  366. slideDown(childElement, this._config.animationSpeed);
  367. }
  368. this._element.dispatchEvent(event);
  369. }
  370. close() {
  371. const event = new Event(EVENT_COLLAPSED$1);
  372. this._element.classList.remove(CLASS_NAME_MENU_OPEN$1);
  373. const childElement = this._element?.querySelector(SELECTOR_TREEVIEW_MENU);
  374. if (childElement) {
  375. slideUp(childElement, this._config.animationSpeed);
  376. }
  377. this._element.dispatchEvent(event);
  378. }
  379. toggle() {
  380. if (this._element.classList.contains(CLASS_NAME_MENU_OPEN$1)) {
  381. this.close();
  382. }
  383. else {
  384. this.open();
  385. }
  386. }
  387. }
  388. /**
  389. * ------------------------------------------------------------------------
  390. * Data Api implementation
  391. * ------------------------------------------------------------------------
  392. */
  393. onDOMContentLoaded(() => {
  394. const openMenuItems = document.querySelectorAll(`${SELECTOR_NAV_ITEM$1}.${CLASS_NAME_MENU_OPEN$1}`);
  395. openMenuItems.forEach(menuItem => {
  396. const childElement = menuItem.querySelector(SELECTOR_TREEVIEW_MENU);
  397. if (childElement) {
  398. slideDown(childElement, 0);
  399. const event = new Event(EVENT_LOAD_DATA_API);
  400. menuItem.dispatchEvent(event);
  401. }
  402. });
  403. const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE$1);
  404. button.forEach(btn => {
  405. btn.addEventListener('click', event => {
  406. const target = event.target;
  407. const targetItem = target.closest(SELECTOR_NAV_ITEM$1);
  408. const targetLink = target.closest(SELECTOR_NAV_LINK);
  409. const targetTreeviewMenu = targetItem?.querySelector(SELECTOR_TREEVIEW_MENU);
  410. const lteToggleElement = event.currentTarget;
  411. // Avoid creating Treeview instances on non menu elements
  412. if (!targetTreeviewMenu) {
  413. return;
  414. }
  415. if (target?.getAttribute('href') === '#' || targetLink?.getAttribute('href') === '#') {
  416. event.preventDefault();
  417. }
  418. if (targetItem) {
  419. // Read data attributes
  420. const accordionAttr = lteToggleElement.dataset.accordion;
  421. const animationSpeedAttr = lteToggleElement.dataset.animationSpeed;
  422. // Build config from data attributes, fallback to Default
  423. const config = {
  424. accordion: accordionAttr === undefined ? Default.accordion : accordionAttr === 'true',
  425. animationSpeed: animationSpeedAttr === undefined ? Default.animationSpeed : Number(animationSpeedAttr)
  426. };
  427. const data = new Treeview(targetItem, config);
  428. data.toggle();
  429. }
  430. });
  431. });
  432. });
  433. /**
  434. * --------------------------------------------
  435. * @file AdminLTE direct-chat.ts
  436. * @description Direct chat for AdminLTE.
  437. * @license MIT
  438. * --------------------------------------------
  439. */
  440. /**
  441. * Constants
  442. * ====================================================
  443. */
  444. const DATA_KEY$2 = 'lte.direct-chat';
  445. const EVENT_KEY$2 = `.${DATA_KEY$2}`;
  446. const EVENT_EXPANDED = `expanded${EVENT_KEY$2}`;
  447. const EVENT_COLLAPSED = `collapsed${EVENT_KEY$2}`;
  448. const SELECTOR_DATA_TOGGLE = '[data-lte-toggle="chat-pane"]';
  449. const SELECTOR_DIRECT_CHAT = '.direct-chat';
  450. const CLASS_NAME_DIRECT_CHAT_OPEN = 'direct-chat-contacts-open';
  451. /**
  452. * Class Definition
  453. * ====================================================
  454. */
  455. class DirectChat {
  456. _element;
  457. constructor(element) {
  458. this._element = element;
  459. }
  460. toggle() {
  461. if (this._element.classList.contains(CLASS_NAME_DIRECT_CHAT_OPEN)) {
  462. const event = new Event(EVENT_COLLAPSED);
  463. this._element.classList.remove(CLASS_NAME_DIRECT_CHAT_OPEN);
  464. this._element.dispatchEvent(event);
  465. }
  466. else {
  467. const event = new Event(EVENT_EXPANDED);
  468. this._element.classList.add(CLASS_NAME_DIRECT_CHAT_OPEN);
  469. this._element.dispatchEvent(event);
  470. }
  471. }
  472. }
  473. /**
  474. *
  475. * Data Api implementation
  476. * ====================================================
  477. */
  478. onDOMContentLoaded(() => {
  479. const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE);
  480. button.forEach(btn => {
  481. btn.addEventListener('click', event => {
  482. event.preventDefault();
  483. const target = event.target;
  484. const chatPane = target.closest(SELECTOR_DIRECT_CHAT);
  485. if (chatPane) {
  486. const data = new DirectChat(chatPane);
  487. data.toggle();
  488. }
  489. });
  490. });
  491. });
  492. /**
  493. * --------------------------------------------
  494. * @file AdminLTE fullscreen.ts
  495. * @description Fullscreen plugin for AdminLTE.
  496. * @license MIT
  497. * --------------------------------------------
  498. */
  499. /**
  500. * Constants
  501. * ============================================================================
  502. */
  503. const DATA_KEY$1 = 'lte.fullscreen';
  504. const EVENT_KEY$1 = `.${DATA_KEY$1}`;
  505. const EVENT_MAXIMIZED = `maximized${EVENT_KEY$1}`;
  506. const EVENT_MINIMIZED = `minimized${EVENT_KEY$1}`;
  507. const SELECTOR_FULLSCREEN_TOGGLE = '[data-lte-toggle="fullscreen"]';
  508. const SELECTOR_MAXIMIZE_ICON = '[data-lte-icon="maximize"]';
  509. const SELECTOR_MINIMIZE_ICON = '[data-lte-icon="minimize"]';
  510. /**
  511. * Class Definition.
  512. * ============================================================================
  513. */
  514. class FullScreen {
  515. _element;
  516. _config;
  517. constructor(element, config) {
  518. this._element = element;
  519. this._config = config;
  520. }
  521. inFullScreen() {
  522. const event = new Event(EVENT_MAXIMIZED);
  523. const iconMaximize = document.querySelector(SELECTOR_MAXIMIZE_ICON);
  524. const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON);
  525. void document.documentElement.requestFullscreen();
  526. if (iconMaximize) {
  527. iconMaximize.style.display = 'none';
  528. }
  529. if (iconMinimize) {
  530. iconMinimize.style.display = 'block';
  531. }
  532. this._element.dispatchEvent(event);
  533. }
  534. outFullscreen() {
  535. const event = new Event(EVENT_MINIMIZED);
  536. const iconMaximize = document.querySelector(SELECTOR_MAXIMIZE_ICON);
  537. const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON);
  538. void document.exitFullscreen();
  539. if (iconMaximize) {
  540. iconMaximize.style.display = 'block';
  541. }
  542. if (iconMinimize) {
  543. iconMinimize.style.display = 'none';
  544. }
  545. this._element.dispatchEvent(event);
  546. }
  547. toggleFullScreen() {
  548. if (document.fullscreenEnabled) {
  549. if (document.fullscreenElement) {
  550. this.outFullscreen();
  551. }
  552. else {
  553. this.inFullScreen();
  554. }
  555. }
  556. }
  557. }
  558. /**
  559. * Data Api implementation
  560. * ============================================================================
  561. */
  562. onDOMContentLoaded(() => {
  563. const buttons = document.querySelectorAll(SELECTOR_FULLSCREEN_TOGGLE);
  564. buttons.forEach(btn => {
  565. btn.addEventListener('click', event => {
  566. event.preventDefault();
  567. const target = event.target;
  568. const button = target.closest(SELECTOR_FULLSCREEN_TOGGLE);
  569. if (button) {
  570. const data = new FullScreen(button, undefined);
  571. data.toggleFullScreen();
  572. }
  573. });
  574. });
  575. });
  576. /**
  577. * --------------------------------------------
  578. * @file AdminLTE push-menu.ts
  579. * @description Push menu for AdminLTE.
  580. * @license MIT
  581. * --------------------------------------------
  582. */
  583. /**
  584. * ------------------------------------------------------------------------
  585. * Constants
  586. * ------------------------------------------------------------------------
  587. */
  588. const DATA_KEY = 'lte.push-menu';
  589. const EVENT_KEY = `.${DATA_KEY}`;
  590. const EVENT_OPEN = `open${EVENT_KEY}`;
  591. const EVENT_COLLAPSE = `collapse${EVENT_KEY}`;
  592. const CLASS_NAME_SIDEBAR_MINI = 'sidebar-mini';
  593. const CLASS_NAME_SIDEBAR_COLLAPSE = 'sidebar-collapse';
  594. const CLASS_NAME_SIDEBAR_OPEN = 'sidebar-open';
  595. const CLASS_NAME_SIDEBAR_EXPAND = 'sidebar-expand';
  596. const CLASS_NAME_SIDEBAR_OVERLAY = 'sidebar-overlay';
  597. const CLASS_NAME_MENU_OPEN = 'menu-open';
  598. const SELECTOR_APP_SIDEBAR = '.app-sidebar';
  599. const SELECTOR_SIDEBAR_MENU = '.sidebar-menu';
  600. const SELECTOR_NAV_ITEM = '.nav-item';
  601. const SELECTOR_NAV_TREEVIEW = '.nav-treeview';
  602. const SELECTOR_APP_WRAPPER = '.app-wrapper';
  603. const SELECTOR_SIDEBAR_EXPAND = `[class*="${CLASS_NAME_SIDEBAR_EXPAND}"]`;
  604. const SELECTOR_SIDEBAR_TOGGLE = '[data-lte-toggle="sidebar"]';
  605. const STORAGE_KEY_SIDEBAR_STATE = 'lte.sidebar.state';
  606. const Defaults = {
  607. sidebarBreakpoint: 992,
  608. enablePersistence: true
  609. };
  610. /**
  611. * Class Definition
  612. * ====================================================
  613. */
  614. class PushMenu {
  615. _element;
  616. _config;
  617. constructor(element, config) {
  618. this._element = element;
  619. this._config = { ...Defaults, ...config };
  620. }
  621. menusClose() {
  622. const navTreeview = document.querySelectorAll(SELECTOR_NAV_TREEVIEW);
  623. navTreeview.forEach(navTree => {
  624. navTree.style.removeProperty('display');
  625. navTree.style.removeProperty('height');
  626. });
  627. const navSidebar = document.querySelector(SELECTOR_SIDEBAR_MENU);
  628. const navItem = navSidebar?.querySelectorAll(SELECTOR_NAV_ITEM);
  629. if (navItem) {
  630. navItem.forEach(navI => {
  631. navI.classList.remove(CLASS_NAME_MENU_OPEN);
  632. });
  633. }
  634. }
  635. expand() {
  636. const event = new Event(EVENT_OPEN);
  637. document.body.classList.remove(CLASS_NAME_SIDEBAR_COLLAPSE);
  638. document.body.classList.add(CLASS_NAME_SIDEBAR_OPEN);
  639. this._element.dispatchEvent(event);
  640. }
  641. collapse() {
  642. const event = new Event(EVENT_COLLAPSE);
  643. document.body.classList.remove(CLASS_NAME_SIDEBAR_OPEN);
  644. document.body.classList.add(CLASS_NAME_SIDEBAR_COLLAPSE);
  645. this._element.dispatchEvent(event);
  646. }
  647. addSidebarBreakPoint() {
  648. const sidebarExpandList = document.querySelector(SELECTOR_SIDEBAR_EXPAND)?.classList ?? [];
  649. const sidebarExpand = Array.from(sidebarExpandList).find(className => className.startsWith(CLASS_NAME_SIDEBAR_EXPAND)) ?? '';
  650. const sidebar = document.getElementsByClassName(sidebarExpand)[0];
  651. const sidebarContent = globalThis.getComputedStyle(sidebar, '::before').getPropertyValue('content');
  652. this._config = { ...this._config, sidebarBreakpoint: Number(sidebarContent.replace(/[^\d.-]/g, '')) };
  653. // FIXED: Don't auto-collapse on mobile if sidebar is currently open
  654. // This prevents resize events (triggered by scrolling) from closing the sidebar
  655. const isCurrentlyOpen = document.body.classList.contains(CLASS_NAME_SIDEBAR_OPEN);
  656. if (window.innerWidth <= this._config.sidebarBreakpoint) {
  657. // Only collapse if not currently open (prevents scroll-triggered closes)
  658. if (!isCurrentlyOpen) {
  659. this.collapse();
  660. }
  661. }
  662. else {
  663. if (!document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI)) {
  664. this.expand();
  665. }
  666. if (document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI) && document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {
  667. this.collapse();
  668. }
  669. }
  670. }
  671. toggle() {
  672. if (document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {
  673. this.expand();
  674. }
  675. else {
  676. this.collapse();
  677. }
  678. this.saveSidebarState();
  679. }
  680. /**
  681. * Save sidebar state to localStorage
  682. */
  683. saveSidebarState() {
  684. if (!this._config.enablePersistence) {
  685. return;
  686. }
  687. // Check for SSR environment
  688. if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
  689. return;
  690. }
  691. try {
  692. const state = document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)
  693. ? CLASS_NAME_SIDEBAR_COLLAPSE
  694. : CLASS_NAME_SIDEBAR_OPEN;
  695. localStorage.setItem(STORAGE_KEY_SIDEBAR_STATE, state);
  696. }
  697. catch {
  698. // localStorage may be unavailable (private browsing, quota exceeded, etc.)
  699. }
  700. }
  701. /**
  702. * Load sidebar state from localStorage
  703. * Only applies on desktop; mobile always starts collapsed
  704. */
  705. loadSidebarState() {
  706. if (!this._config.enablePersistence) {
  707. return;
  708. }
  709. // Check for SSR environment
  710. if (typeof window === 'undefined' || typeof localStorage === 'undefined') {
  711. return;
  712. }
  713. // Don't restore state on mobile - let responsive behavior handle it
  714. if (window.innerWidth <= this._config.sidebarBreakpoint) {
  715. return;
  716. }
  717. try {
  718. const storedState = localStorage.getItem(STORAGE_KEY_SIDEBAR_STATE);
  719. if (storedState === CLASS_NAME_SIDEBAR_COLLAPSE) {
  720. this.collapse();
  721. }
  722. else if (storedState === CLASS_NAME_SIDEBAR_OPEN) {
  723. this.expand();
  724. }
  725. // If null (never saved), let default behavior apply
  726. }
  727. catch {
  728. // localStorage may be unavailable
  729. }
  730. }
  731. init() {
  732. this.addSidebarBreakPoint();
  733. this.loadSidebarState();
  734. }
  735. }
  736. /**
  737. * ------------------------------------------------------------------------
  738. * Data Api implementation
  739. * ------------------------------------------------------------------------
  740. */
  741. onDOMContentLoaded(() => {
  742. const sidebar = document?.querySelector(SELECTOR_APP_SIDEBAR);
  743. if (sidebar) {
  744. const data = new PushMenu(sidebar, Defaults);
  745. data.init();
  746. window.addEventListener('resize', () => {
  747. data.init();
  748. });
  749. }
  750. const sidebarOverlay = document.createElement('div');
  751. sidebarOverlay.className = CLASS_NAME_SIDEBAR_OVERLAY;
  752. document.querySelector(SELECTOR_APP_WRAPPER)?.append(sidebarOverlay);
  753. let overlayTouchMoved = false;
  754. // Handle touch events on overlay (area outside sidebar)
  755. sidebarOverlay.addEventListener('touchstart', () => {
  756. overlayTouchMoved = false;
  757. }, { passive: true });
  758. sidebarOverlay.addEventListener('touchmove', () => {
  759. overlayTouchMoved = true;
  760. }, { passive: true });
  761. sidebarOverlay.addEventListener('touchend', event => {
  762. if (!overlayTouchMoved) {
  763. event.preventDefault();
  764. const target = event.currentTarget;
  765. const data = new PushMenu(target, Defaults);
  766. data.collapse();
  767. }
  768. overlayTouchMoved = false;
  769. }, { passive: false });
  770. sidebarOverlay.addEventListener('click', event => {
  771. event.preventDefault();
  772. const target = event.currentTarget;
  773. const data = new PushMenu(target, Defaults);
  774. data.collapse();
  775. });
  776. const fullBtn = document.querySelectorAll(SELECTOR_SIDEBAR_TOGGLE);
  777. fullBtn.forEach(btn => {
  778. btn.addEventListener('click', event => {
  779. event.preventDefault();
  780. let button = event.currentTarget;
  781. if (button?.dataset.lteToggle !== 'sidebar') {
  782. button = button?.closest(SELECTOR_SIDEBAR_TOGGLE);
  783. }
  784. if (button) {
  785. event?.preventDefault();
  786. const data = new PushMenu(button, Defaults);
  787. data.toggle();
  788. }
  789. });
  790. });
  791. });
  792. /**
  793. * AdminLTE Accessibility Module
  794. * WCAG 2.1 AA Compliance Features
  795. */
  796. class AccessibilityManager {
  797. config;
  798. liveRegion = null;
  799. focusHistory = [];
  800. constructor(config = {}) {
  801. this.config = {
  802. announcements: true,
  803. skipLinks: true,
  804. focusManagement: true,
  805. keyboardNavigation: true,
  806. reducedMotion: true,
  807. ...config
  808. };
  809. this.init();
  810. }
  811. init() {
  812. if (this.config.announcements) {
  813. this.createLiveRegion();
  814. }
  815. if (this.config.skipLinks) {
  816. this.addSkipLinks();
  817. }
  818. if (this.config.focusManagement) {
  819. this.initFocusManagement();
  820. }
  821. if (this.config.keyboardNavigation) {
  822. this.initKeyboardNavigation();
  823. }
  824. if (this.config.reducedMotion) {
  825. this.respectReducedMotion();
  826. }
  827. this.initErrorAnnouncements();
  828. this.initTableAccessibility();
  829. this.initFormAccessibility();
  830. }
  831. // WCAG 4.1.3: Status Messages
  832. createLiveRegion() {
  833. if (this.liveRegion)
  834. return;
  835. this.liveRegion = document.createElement('div');
  836. this.liveRegion.id = 'live-region';
  837. this.liveRegion.className = 'live-region';
  838. this.liveRegion.setAttribute('aria-live', 'polite');
  839. this.liveRegion.setAttribute('aria-atomic', 'true');
  840. this.liveRegion.setAttribute('role', 'status');
  841. document.body.append(this.liveRegion);
  842. }
  843. // WCAG 2.4.1: Bypass Blocks
  844. addSkipLinks() {
  845. const skipLinksContainer = document.createElement('div');
  846. skipLinksContainer.className = 'skip-links';
  847. const skipToMain = document.createElement('a');
  848. skipToMain.href = '#main';
  849. skipToMain.className = 'skip-link';
  850. skipToMain.textContent = 'Skip to main content';
  851. const skipToNav = document.createElement('a');
  852. skipToNav.href = '#navigation';
  853. skipToNav.className = 'skip-link';
  854. skipToNav.textContent = 'Skip to navigation';
  855. skipLinksContainer.append(skipToMain);
  856. skipLinksContainer.append(skipToNav);
  857. document.body.insertBefore(skipLinksContainer, document.body.firstChild);
  858. // Ensure targets exist and are focusable
  859. this.ensureSkipTargets();
  860. }
  861. ensureSkipTargets() {
  862. const main = document.querySelector('#main, main, [role="main"]');
  863. if (main && !main.id) {
  864. main.id = 'main';
  865. }
  866. if (main && !main.hasAttribute('tabindex')) {
  867. main.setAttribute('tabindex', '-1');
  868. }
  869. const nav = document.querySelector('#navigation, nav, [role="navigation"]');
  870. if (nav && !nav.id) {
  871. nav.id = 'navigation';
  872. }
  873. if (nav && !nav.hasAttribute('tabindex')) {
  874. nav.setAttribute('tabindex', '-1');
  875. }
  876. }
  877. // WCAG 2.4.3: Focus Order & 2.4.7: Focus Visible
  878. initFocusManagement() {
  879. document.addEventListener('keydown', (event) => {
  880. if (event.key === 'Tab') {
  881. this.handleTabNavigation(event);
  882. }
  883. if (event.key === 'Escape') {
  884. this.handleEscapeKey(event);
  885. }
  886. });
  887. // Focus management for modals and dropdowns
  888. this.initModalFocusManagement();
  889. this.initDropdownFocusManagement();
  890. }
  891. handleTabNavigation(event) {
  892. const focusableElements = this.getFocusableElements();
  893. const currentIndex = focusableElements.indexOf(document.activeElement);
  894. if (event.shiftKey) {
  895. // Shift+Tab (backward)
  896. if (currentIndex <= 0) {
  897. event.preventDefault();
  898. focusableElements.at(-1)?.focus();
  899. }
  900. }
  901. else if (currentIndex >= focusableElements.length - 1) {
  902. // Tab (forward)
  903. event.preventDefault();
  904. focusableElements[0]?.focus();
  905. }
  906. }
  907. getFocusableElements() {
  908. const selector = [
  909. 'a[href]',
  910. 'button:not([disabled])',
  911. 'input:not([disabled])',
  912. 'select:not([disabled])',
  913. 'textarea:not([disabled])',
  914. '[tabindex]:not([tabindex="-1"])',
  915. '[contenteditable="true"]'
  916. ].join(', ');
  917. return Array.from(document.querySelectorAll(selector));
  918. }
  919. handleEscapeKey(event) {
  920. // Close modals, dropdowns, etc.
  921. const activeModal = document.querySelector('.modal.show');
  922. const activeDropdown = document.querySelector('.dropdown-menu.show');
  923. if (activeModal) {
  924. const closeButton = activeModal.querySelector('[data-bs-dismiss="modal"]');
  925. closeButton?.click();
  926. event.preventDefault();
  927. }
  928. else if (activeDropdown) {
  929. const toggleButton = document.querySelector('[data-bs-toggle="dropdown"][aria-expanded="true"]');
  930. toggleButton?.click();
  931. event.preventDefault();
  932. }
  933. }
  934. // WCAG 2.1.1: Keyboard Access
  935. initKeyboardNavigation() {
  936. // Add keyboard support for custom components
  937. document.addEventListener('keydown', (event) => {
  938. const target = event.target;
  939. // Handle arrow key navigation for menus
  940. if (target.closest('.nav, .navbar-nav, .dropdown-menu')) {
  941. this.handleMenuNavigation(event);
  942. }
  943. // Handle Enter and Space for custom buttons
  944. if ((event.key === 'Enter' || event.key === ' ') && target.hasAttribute('role') && target.getAttribute('role') === 'button' && !target.matches('button, input[type="button"], input[type="submit"]')) {
  945. event.preventDefault();
  946. target.click();
  947. }
  948. });
  949. }
  950. handleMenuNavigation(event) {
  951. if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) {
  952. return;
  953. }
  954. const currentElement = event.target;
  955. const menuItems = Array.from(currentElement.closest('.nav, .navbar-nav, .dropdown-menu')?.querySelectorAll('a, button') || []);
  956. const currentIndex = menuItems.indexOf(currentElement);
  957. let nextIndex;
  958. switch (event.key) {
  959. case 'ArrowDown':
  960. case 'ArrowRight': {
  961. nextIndex = currentIndex < menuItems.length - 1 ? currentIndex + 1 : 0;
  962. break;
  963. }
  964. case 'ArrowUp':
  965. case 'ArrowLeft': {
  966. nextIndex = currentIndex > 0 ? currentIndex - 1 : menuItems.length - 1;
  967. break;
  968. }
  969. case 'Home': {
  970. nextIndex = 0;
  971. break;
  972. }
  973. case 'End': {
  974. nextIndex = menuItems.length - 1;
  975. break;
  976. }
  977. default: {
  978. return;
  979. }
  980. }
  981. event.preventDefault();
  982. menuItems[nextIndex]?.focus();
  983. }
  984. // WCAG 2.3.3: Animation from Interactions
  985. respectReducedMotion() {
  986. const prefersReducedMotion = globalThis.matchMedia('(prefers-reduced-motion: reduce)').matches;
  987. if (prefersReducedMotion) {
  988. document.body.classList.add('reduce-motion');
  989. // Disable smooth scrolling
  990. document.documentElement.style.scrollBehavior = 'auto';
  991. // Reduce animation duration
  992. const style = document.createElement('style');
  993. style.textContent = `
  994. *, *::before, *::after {
  995. animation-duration: 0.01ms !important;
  996. animation-iteration-count: 1 !important;
  997. transition-duration: 0.01ms !important;
  998. }
  999. `;
  1000. document.head.append(style);
  1001. }
  1002. }
  1003. // WCAG 3.3.1: Error Identification
  1004. initErrorAnnouncements() {
  1005. const observer = new MutationObserver((mutations) => {
  1006. mutations.forEach((mutation) => {
  1007. mutation.addedNodes.forEach((node) => {
  1008. if (node.nodeType === Node.ELEMENT_NODE) {
  1009. const element = node;
  1010. // Check for error messages
  1011. if (element.matches('.alert-danger, .invalid-feedback, .error')) {
  1012. this.announce(element.textContent || 'Error occurred', 'assertive');
  1013. }
  1014. // Check for success messages
  1015. if (element.matches('.alert-success, .success')) {
  1016. this.announce(element.textContent || 'Success', 'polite');
  1017. }
  1018. }
  1019. });
  1020. });
  1021. });
  1022. observer.observe(document.body, {
  1023. childList: true,
  1024. subtree: true
  1025. });
  1026. }
  1027. // WCAG 1.3.1: Info and Relationships
  1028. initTableAccessibility() {
  1029. document.querySelectorAll('table').forEach((table) => {
  1030. // Add table role if missing
  1031. if (!table.hasAttribute('role')) {
  1032. table.setAttribute('role', 'table');
  1033. }
  1034. // Ensure headers have proper scope
  1035. table.querySelectorAll('th').forEach((th) => {
  1036. if (!th.hasAttribute('scope')) {
  1037. const isInThead = th.closest('thead');
  1038. const isFirstColumn = th.cellIndex === 0;
  1039. if (isInThead) {
  1040. th.setAttribute('scope', 'col');
  1041. }
  1042. else if (isFirstColumn) {
  1043. th.setAttribute('scope', 'row');
  1044. }
  1045. }
  1046. });
  1047. // Add caption if missing but title exists
  1048. if (!table.querySelector('caption') && table.hasAttribute('title')) {
  1049. const caption = document.createElement('caption');
  1050. caption.textContent = table.getAttribute('title') || '';
  1051. table.insertBefore(caption, table.firstChild);
  1052. }
  1053. });
  1054. }
  1055. // WCAG 3.3.2: Labels or Instructions
  1056. initFormAccessibility() {
  1057. document.querySelectorAll('input, select, textarea').forEach((input) => {
  1058. const htmlInput = input;
  1059. // Ensure all inputs have labels
  1060. if (!htmlInput.labels?.length && !htmlInput.hasAttribute('aria-label') && !htmlInput.hasAttribute('aria-labelledby')) {
  1061. const placeholder = htmlInput.getAttribute('placeholder');
  1062. if (placeholder) {
  1063. htmlInput.setAttribute('aria-label', placeholder);
  1064. }
  1065. }
  1066. // Add required indicators
  1067. if (htmlInput.hasAttribute('required')) {
  1068. const label = htmlInput.labels?.[0];
  1069. if (label && !label.querySelector('.required-indicator')) {
  1070. const indicator = document.createElement('span');
  1071. indicator.className = 'required-indicator sr-only';
  1072. indicator.textContent = ' (required)';
  1073. label.append(indicator);
  1074. }
  1075. }
  1076. // Handle invalid state unless the element explicitly opts out via the
  1077. // 'disable-adminlte-validations' class.
  1078. if (!htmlInput.classList.contains('disable-adminlte-validations')) {
  1079. htmlInput.addEventListener('invalid', () => {
  1080. this.handleFormError(htmlInput);
  1081. });
  1082. }
  1083. });
  1084. }
  1085. handleFormError(input) {
  1086. const errorId = `${input.id || input.name}-error`;
  1087. let errorElement = document.getElementById(errorId);
  1088. if (!errorElement) {
  1089. errorElement = document.createElement('div');
  1090. errorElement.id = errorId;
  1091. errorElement.className = 'invalid-feedback';
  1092. errorElement.setAttribute('role', 'alert');
  1093. // Always append the error element as the last child of the parent.
  1094. // This prevents breaking layouts where inputs use Bootstrap's
  1095. // `.input-group-text` decorators, ensuring the error stays below
  1096. // the entire input group.
  1097. input.parentNode?.append(errorElement);
  1098. }
  1099. errorElement.textContent = input.validationMessage;
  1100. input.setAttribute('aria-describedby', errorId);
  1101. input.classList.add('is-invalid');
  1102. this.announce(`Error in ${input.labels?.[0]?.textContent || input.name}: ${input.validationMessage}`, 'assertive');
  1103. }
  1104. // Modal focus management
  1105. initModalFocusManagement() {
  1106. document.addEventListener('shown.bs.modal', (event) => {
  1107. const modal = event.target;
  1108. const focusableElements = modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
  1109. if (focusableElements.length > 0) {
  1110. focusableElements[0].focus();
  1111. }
  1112. // Store previous focus
  1113. this.focusHistory.push(document.activeElement);
  1114. });
  1115. document.addEventListener('hidden.bs.modal', () => {
  1116. // Restore previous focus
  1117. const previousElement = this.focusHistory.pop();
  1118. if (previousElement) {
  1119. previousElement.focus();
  1120. }
  1121. });
  1122. }
  1123. // Dropdown focus management
  1124. initDropdownFocusManagement() {
  1125. document.addEventListener('shown.bs.dropdown', (event) => {
  1126. const dropdown = event.target;
  1127. const menu = dropdown.querySelector('.dropdown-menu');
  1128. const firstItem = menu?.querySelector('a, button');
  1129. if (firstItem) {
  1130. firstItem.focus();
  1131. }
  1132. });
  1133. }
  1134. // Public API methods
  1135. announce(message, priority = 'polite') {
  1136. if (!this.liveRegion) {
  1137. this.createLiveRegion();
  1138. }
  1139. if (this.liveRegion) {
  1140. this.liveRegion.setAttribute('aria-live', priority);
  1141. this.liveRegion.textContent = message;
  1142. // Clear after announcement
  1143. setTimeout(() => {
  1144. if (this.liveRegion) {
  1145. this.liveRegion.textContent = '';
  1146. }
  1147. }, 1000);
  1148. }
  1149. }
  1150. focusElement(selector) {
  1151. const element = document.querySelector(selector);
  1152. if (element) {
  1153. element.focus();
  1154. // Ensure element is visible
  1155. element.scrollIntoView({ behavior: 'smooth', block: 'center' });
  1156. }
  1157. }
  1158. trapFocus(container) {
  1159. const focusableElements = container.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
  1160. const focusableArray = Array.from(focusableElements);
  1161. const firstElement = focusableArray[0];
  1162. const lastElement = focusableArray.at(-1);
  1163. container.addEventListener('keydown', (event) => {
  1164. if (event.key === 'Tab') {
  1165. if (event.shiftKey) {
  1166. if (document.activeElement === firstElement) {
  1167. lastElement?.focus();
  1168. event.preventDefault();
  1169. }
  1170. }
  1171. else if (document.activeElement === lastElement) {
  1172. firstElement.focus();
  1173. event.preventDefault();
  1174. }
  1175. }
  1176. });
  1177. }
  1178. addLandmarks() {
  1179. // Add main landmark if missing
  1180. const main = document.querySelector('main');
  1181. if (!main) {
  1182. const appMain = document.querySelector('.app-main');
  1183. if (appMain) {
  1184. appMain.setAttribute('role', 'main');
  1185. appMain.id = 'main';
  1186. }
  1187. }
  1188. // Add navigation landmarks
  1189. document.querySelectorAll('.navbar-nav, .nav').forEach((nav, index) => {
  1190. if (!nav.hasAttribute('role')) {
  1191. nav.setAttribute('role', 'navigation');
  1192. }
  1193. if (!nav.hasAttribute('aria-label')) {
  1194. nav.setAttribute('aria-label', `Navigation ${index + 1}`);
  1195. }
  1196. });
  1197. // Add search landmark
  1198. const searchForm = document.querySelector('form[role="search"], .navbar-search');
  1199. if (searchForm && !searchForm.hasAttribute('role')) {
  1200. searchForm.setAttribute('role', 'search');
  1201. }
  1202. }
  1203. }
  1204. // Initialize accessibility when DOM is ready
  1205. const initAccessibility = (config) => {
  1206. return new AccessibilityManager(config);
  1207. };
  1208. /**
  1209. * AdminLTE v4.0.0-rc5
  1210. * Author: Colorlib
  1211. * Website: AdminLTE.io <https://adminlte.io>
  1212. * License: Open source - MIT <https://opensource.org/licenses/MIT>
  1213. */
  1214. onDOMContentLoaded(() => {
  1215. /**
  1216. * Initialize AdminLTE Core Components
  1217. * -------------------------------
  1218. */
  1219. const layout = new Layout(document.body);
  1220. layout.holdTransition();
  1221. /**
  1222. * Initialize Accessibility Features - WCAG 2.1 AA Compliance
  1223. * --------------------------------------------------------
  1224. */
  1225. const accessibilityManager = initAccessibility({
  1226. announcements: true,
  1227. skipLinks: true,
  1228. focusManagement: true,
  1229. keyboardNavigation: true,
  1230. reducedMotion: true
  1231. });
  1232. // Add semantic landmarks
  1233. accessibilityManager.addLandmarks();
  1234. // Mark app as loaded after initialization
  1235. setTimeout(() => {
  1236. document.body.classList.add('app-loaded');
  1237. }, 400);
  1238. });
  1239. exports.CardWidget = CardWidget;
  1240. exports.DirectChat = DirectChat;
  1241. exports.FullScreen = FullScreen;
  1242. exports.Layout = Layout;
  1243. exports.PushMenu = PushMenu;
  1244. exports.Treeview = Treeview;
  1245. exports.initAccessibility = initAccessibility;
  1246. }));
  1247. //# sourceMappingURL=adminlte.js.map