adminlte.js 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191
  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 lteToggleElement = event.currentTarget;
  410. if (target?.getAttribute('href') === '#' || targetLink?.getAttribute('href') === '#') {
  411. event.preventDefault();
  412. }
  413. if (targetItem) {
  414. // Read data attributes
  415. const accordionAttr = lteToggleElement.dataset.accordion;
  416. const animationSpeedAttr = lteToggleElement.dataset.animationSpeed;
  417. // Build config from data attributes, fallback to Default
  418. const config = {
  419. accordion: accordionAttr === undefined ? Default.accordion : accordionAttr === 'true',
  420. animationSpeed: animationSpeedAttr === undefined ? Default.animationSpeed : Number(animationSpeedAttr)
  421. };
  422. const data = new Treeview(targetItem, config);
  423. data.toggle();
  424. }
  425. });
  426. });
  427. });
  428. /**
  429. * --------------------------------------------
  430. * @file AdminLTE direct-chat.ts
  431. * @description Direct chat for AdminLTE.
  432. * @license MIT
  433. * --------------------------------------------
  434. */
  435. /**
  436. * Constants
  437. * ====================================================
  438. */
  439. const DATA_KEY$2 = 'lte.direct-chat';
  440. const EVENT_KEY$2 = `.${DATA_KEY$2}`;
  441. const EVENT_EXPANDED = `expanded${EVENT_KEY$2}`;
  442. const EVENT_COLLAPSED = `collapsed${EVENT_KEY$2}`;
  443. const SELECTOR_DATA_TOGGLE = '[data-lte-toggle="chat-pane"]';
  444. const SELECTOR_DIRECT_CHAT = '.direct-chat';
  445. const CLASS_NAME_DIRECT_CHAT_OPEN = 'direct-chat-contacts-open';
  446. /**
  447. * Class Definition
  448. * ====================================================
  449. */
  450. class DirectChat {
  451. _element;
  452. constructor(element) {
  453. this._element = element;
  454. }
  455. toggle() {
  456. if (this._element.classList.contains(CLASS_NAME_DIRECT_CHAT_OPEN)) {
  457. const event = new Event(EVENT_COLLAPSED);
  458. this._element.classList.remove(CLASS_NAME_DIRECT_CHAT_OPEN);
  459. this._element.dispatchEvent(event);
  460. }
  461. else {
  462. const event = new Event(EVENT_EXPANDED);
  463. this._element.classList.add(CLASS_NAME_DIRECT_CHAT_OPEN);
  464. this._element.dispatchEvent(event);
  465. }
  466. }
  467. }
  468. /**
  469. *
  470. * Data Api implementation
  471. * ====================================================
  472. */
  473. onDOMContentLoaded(() => {
  474. const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE);
  475. button.forEach(btn => {
  476. btn.addEventListener('click', event => {
  477. event.preventDefault();
  478. const target = event.target;
  479. const chatPane = target.closest(SELECTOR_DIRECT_CHAT);
  480. if (chatPane) {
  481. const data = new DirectChat(chatPane);
  482. data.toggle();
  483. }
  484. });
  485. });
  486. });
  487. /**
  488. * --------------------------------------------
  489. * @file AdminLTE fullscreen.ts
  490. * @description Fullscreen plugin for AdminLTE.
  491. * @license MIT
  492. * --------------------------------------------
  493. */
  494. /**
  495. * Constants
  496. * ============================================================================
  497. */
  498. const DATA_KEY$1 = 'lte.fullscreen';
  499. const EVENT_KEY$1 = `.${DATA_KEY$1}`;
  500. const EVENT_MAXIMIZED = `maximized${EVENT_KEY$1}`;
  501. const EVENT_MINIMIZED = `minimized${EVENT_KEY$1}`;
  502. const SELECTOR_FULLSCREEN_TOGGLE = '[data-lte-toggle="fullscreen"]';
  503. const SELECTOR_MAXIMIZE_ICON = '[data-lte-icon="maximize"]';
  504. const SELECTOR_MINIMIZE_ICON = '[data-lte-icon="minimize"]';
  505. /**
  506. * Class Definition.
  507. * ============================================================================
  508. */
  509. class FullScreen {
  510. _element;
  511. _config;
  512. constructor(element, config) {
  513. this._element = element;
  514. this._config = config;
  515. }
  516. inFullScreen() {
  517. const event = new Event(EVENT_MAXIMIZED);
  518. const iconMaximize = document.querySelector(SELECTOR_MAXIMIZE_ICON);
  519. const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON);
  520. void document.documentElement.requestFullscreen();
  521. if (iconMaximize) {
  522. iconMaximize.style.display = 'none';
  523. }
  524. if (iconMinimize) {
  525. iconMinimize.style.display = 'block';
  526. }
  527. this._element.dispatchEvent(event);
  528. }
  529. outFullscreen() {
  530. const event = new Event(EVENT_MINIMIZED);
  531. const iconMaximize = document.querySelector(SELECTOR_MAXIMIZE_ICON);
  532. const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON);
  533. void document.exitFullscreen();
  534. if (iconMaximize) {
  535. iconMaximize.style.display = 'block';
  536. }
  537. if (iconMinimize) {
  538. iconMinimize.style.display = 'none';
  539. }
  540. this._element.dispatchEvent(event);
  541. }
  542. toggleFullScreen() {
  543. if (document.fullscreenEnabled) {
  544. if (document.fullscreenElement) {
  545. this.outFullscreen();
  546. }
  547. else {
  548. this.inFullScreen();
  549. }
  550. }
  551. }
  552. }
  553. /**
  554. * Data Api implementation
  555. * ============================================================================
  556. */
  557. onDOMContentLoaded(() => {
  558. const buttons = document.querySelectorAll(SELECTOR_FULLSCREEN_TOGGLE);
  559. buttons.forEach(btn => {
  560. btn.addEventListener('click', event => {
  561. event.preventDefault();
  562. const target = event.target;
  563. const button = target.closest(SELECTOR_FULLSCREEN_TOGGLE);
  564. if (button) {
  565. const data = new FullScreen(button, undefined);
  566. data.toggleFullScreen();
  567. }
  568. });
  569. });
  570. });
  571. /**
  572. * --------------------------------------------
  573. * @file AdminLTE push-menu.ts
  574. * @description Push menu for AdminLTE.
  575. * @license MIT
  576. * --------------------------------------------
  577. */
  578. /**
  579. * ------------------------------------------------------------------------
  580. * Constants
  581. * ------------------------------------------------------------------------
  582. */
  583. const DATA_KEY = 'lte.push-menu';
  584. const EVENT_KEY = `.${DATA_KEY}`;
  585. const EVENT_OPEN = `open${EVENT_KEY}`;
  586. const EVENT_COLLAPSE = `collapse${EVENT_KEY}`;
  587. const CLASS_NAME_SIDEBAR_MINI = 'sidebar-mini';
  588. const CLASS_NAME_SIDEBAR_COLLAPSE = 'sidebar-collapse';
  589. const CLASS_NAME_SIDEBAR_OPEN = 'sidebar-open';
  590. const CLASS_NAME_SIDEBAR_EXPAND = 'sidebar-expand';
  591. const CLASS_NAME_SIDEBAR_OVERLAY = 'sidebar-overlay';
  592. const CLASS_NAME_MENU_OPEN = 'menu-open';
  593. const SELECTOR_APP_SIDEBAR = '.app-sidebar';
  594. const SELECTOR_SIDEBAR_MENU = '.sidebar-menu';
  595. const SELECTOR_NAV_ITEM = '.nav-item';
  596. const SELECTOR_NAV_TREEVIEW = '.nav-treeview';
  597. const SELECTOR_APP_WRAPPER = '.app-wrapper';
  598. const SELECTOR_SIDEBAR_EXPAND = `[class*="${CLASS_NAME_SIDEBAR_EXPAND}"]`;
  599. const SELECTOR_SIDEBAR_TOGGLE = '[data-lte-toggle="sidebar"]';
  600. const Defaults = {
  601. sidebarBreakpoint: 992
  602. };
  603. /**
  604. * Class Definition
  605. * ====================================================
  606. */
  607. class PushMenu {
  608. _element;
  609. _config;
  610. constructor(element, config) {
  611. this._element = element;
  612. this._config = { ...Defaults, ...config };
  613. }
  614. menusClose() {
  615. const navTreeview = document.querySelectorAll(SELECTOR_NAV_TREEVIEW);
  616. navTreeview.forEach(navTree => {
  617. navTree.style.removeProperty('display');
  618. navTree.style.removeProperty('height');
  619. });
  620. const navSidebar = document.querySelector(SELECTOR_SIDEBAR_MENU);
  621. const navItem = navSidebar?.querySelectorAll(SELECTOR_NAV_ITEM);
  622. if (navItem) {
  623. navItem.forEach(navI => {
  624. navI.classList.remove(CLASS_NAME_MENU_OPEN);
  625. });
  626. }
  627. }
  628. expand() {
  629. const event = new Event(EVENT_OPEN);
  630. document.body.classList.remove(CLASS_NAME_SIDEBAR_COLLAPSE);
  631. document.body.classList.add(CLASS_NAME_SIDEBAR_OPEN);
  632. this._element.dispatchEvent(event);
  633. }
  634. collapse() {
  635. const event = new Event(EVENT_COLLAPSE);
  636. document.body.classList.remove(CLASS_NAME_SIDEBAR_OPEN);
  637. document.body.classList.add(CLASS_NAME_SIDEBAR_COLLAPSE);
  638. this._element.dispatchEvent(event);
  639. }
  640. addSidebarBreakPoint() {
  641. const sidebarExpandList = document.querySelector(SELECTOR_SIDEBAR_EXPAND)?.classList ?? [];
  642. const sidebarExpand = Array.from(sidebarExpandList).find(className => className.startsWith(CLASS_NAME_SIDEBAR_EXPAND)) ?? '';
  643. const sidebar = document.getElementsByClassName(sidebarExpand)[0];
  644. const sidebarContent = globalThis.getComputedStyle(sidebar, '::before').getPropertyValue('content');
  645. this._config = { ...this._config, sidebarBreakpoint: Number(sidebarContent.replace(/[^\d.-]/g, '')) };
  646. // FIXED: Don't auto-collapse on mobile if sidebar is currently open
  647. // This prevents resize events (triggered by scrolling) from closing the sidebar
  648. const isCurrentlyOpen = document.body.classList.contains(CLASS_NAME_SIDEBAR_OPEN);
  649. if (window.innerWidth <= this._config.sidebarBreakpoint) {
  650. // Only collapse if not currently open (prevents scroll-triggered closes)
  651. if (!isCurrentlyOpen) {
  652. this.collapse();
  653. }
  654. }
  655. else {
  656. if (!document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI)) {
  657. this.expand();
  658. }
  659. if (document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI) && document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {
  660. this.collapse();
  661. }
  662. }
  663. }
  664. toggle() {
  665. if (document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {
  666. this.expand();
  667. }
  668. else {
  669. this.collapse();
  670. }
  671. }
  672. init() {
  673. this.addSidebarBreakPoint();
  674. }
  675. }
  676. /**
  677. * ------------------------------------------------------------------------
  678. * Data Api implementation
  679. * ------------------------------------------------------------------------
  680. */
  681. onDOMContentLoaded(() => {
  682. const sidebar = document?.querySelector(SELECTOR_APP_SIDEBAR);
  683. if (sidebar) {
  684. const data = new PushMenu(sidebar, Defaults);
  685. data.init();
  686. window.addEventListener('resize', () => {
  687. data.init();
  688. });
  689. }
  690. const sidebarOverlay = document.createElement('div');
  691. sidebarOverlay.className = CLASS_NAME_SIDEBAR_OVERLAY;
  692. document.querySelector(SELECTOR_APP_WRAPPER)?.append(sidebarOverlay);
  693. let overlayTouchMoved = false;
  694. // Handle touch events on overlay (area outside sidebar)
  695. sidebarOverlay.addEventListener('touchstart', () => {
  696. overlayTouchMoved = false;
  697. }, { passive: true });
  698. sidebarOverlay.addEventListener('touchmove', () => {
  699. overlayTouchMoved = true;
  700. }, { passive: true });
  701. sidebarOverlay.addEventListener('touchend', event => {
  702. if (!overlayTouchMoved) {
  703. event.preventDefault();
  704. const target = event.currentTarget;
  705. const data = new PushMenu(target, Defaults);
  706. data.collapse();
  707. }
  708. overlayTouchMoved = false;
  709. }, { passive: false });
  710. sidebarOverlay.addEventListener('click', event => {
  711. event.preventDefault();
  712. const target = event.currentTarget;
  713. const data = new PushMenu(target, Defaults);
  714. data.collapse();
  715. });
  716. const fullBtn = document.querySelectorAll(SELECTOR_SIDEBAR_TOGGLE);
  717. fullBtn.forEach(btn => {
  718. btn.addEventListener('click', event => {
  719. event.preventDefault();
  720. let button = event.currentTarget;
  721. if (button?.dataset.lteToggle !== 'sidebar') {
  722. button = button?.closest(SELECTOR_SIDEBAR_TOGGLE);
  723. }
  724. if (button) {
  725. event?.preventDefault();
  726. const data = new PushMenu(button, Defaults);
  727. data.toggle();
  728. }
  729. });
  730. });
  731. });
  732. /**
  733. * AdminLTE Accessibility Module
  734. * WCAG 2.1 AA Compliance Features
  735. */
  736. class AccessibilityManager {
  737. config;
  738. liveRegion = null;
  739. focusHistory = [];
  740. constructor(config = {}) {
  741. this.config = {
  742. announcements: true,
  743. skipLinks: true,
  744. focusManagement: true,
  745. keyboardNavigation: true,
  746. reducedMotion: true,
  747. ...config
  748. };
  749. this.init();
  750. }
  751. init() {
  752. if (this.config.announcements) {
  753. this.createLiveRegion();
  754. }
  755. if (this.config.skipLinks) {
  756. this.addSkipLinks();
  757. }
  758. if (this.config.focusManagement) {
  759. this.initFocusManagement();
  760. }
  761. if (this.config.keyboardNavigation) {
  762. this.initKeyboardNavigation();
  763. }
  764. if (this.config.reducedMotion) {
  765. this.respectReducedMotion();
  766. }
  767. this.initErrorAnnouncements();
  768. this.initTableAccessibility();
  769. this.initFormAccessibility();
  770. }
  771. // WCAG 4.1.3: Status Messages
  772. createLiveRegion() {
  773. if (this.liveRegion)
  774. return;
  775. this.liveRegion = document.createElement('div');
  776. this.liveRegion.id = 'live-region';
  777. this.liveRegion.className = 'live-region';
  778. this.liveRegion.setAttribute('aria-live', 'polite');
  779. this.liveRegion.setAttribute('aria-atomic', 'true');
  780. this.liveRegion.setAttribute('role', 'status');
  781. document.body.append(this.liveRegion);
  782. }
  783. // WCAG 2.4.1: Bypass Blocks
  784. addSkipLinks() {
  785. const skipLinksContainer = document.createElement('div');
  786. skipLinksContainer.className = 'skip-links';
  787. const skipToMain = document.createElement('a');
  788. skipToMain.href = '#main';
  789. skipToMain.className = 'skip-link';
  790. skipToMain.textContent = 'Skip to main content';
  791. const skipToNav = document.createElement('a');
  792. skipToNav.href = '#navigation';
  793. skipToNav.className = 'skip-link';
  794. skipToNav.textContent = 'Skip to navigation';
  795. skipLinksContainer.append(skipToMain);
  796. skipLinksContainer.append(skipToNav);
  797. document.body.insertBefore(skipLinksContainer, document.body.firstChild);
  798. // Ensure targets exist and are focusable
  799. this.ensureSkipTargets();
  800. }
  801. ensureSkipTargets() {
  802. const main = document.querySelector('#main, main, [role="main"]');
  803. if (main && !main.id) {
  804. main.id = 'main';
  805. }
  806. if (main && !main.hasAttribute('tabindex')) {
  807. main.setAttribute('tabindex', '-1');
  808. }
  809. const nav = document.querySelector('#navigation, nav, [role="navigation"]');
  810. if (nav && !nav.id) {
  811. nav.id = 'navigation';
  812. }
  813. if (nav && !nav.hasAttribute('tabindex')) {
  814. nav.setAttribute('tabindex', '-1');
  815. }
  816. }
  817. // WCAG 2.4.3: Focus Order & 2.4.7: Focus Visible
  818. initFocusManagement() {
  819. document.addEventListener('keydown', (event) => {
  820. if (event.key === 'Tab') {
  821. this.handleTabNavigation(event);
  822. }
  823. if (event.key === 'Escape') {
  824. this.handleEscapeKey(event);
  825. }
  826. });
  827. // Focus management for modals and dropdowns
  828. this.initModalFocusManagement();
  829. this.initDropdownFocusManagement();
  830. }
  831. handleTabNavigation(event) {
  832. const focusableElements = this.getFocusableElements();
  833. const currentIndex = focusableElements.indexOf(document.activeElement);
  834. if (event.shiftKey) {
  835. // Shift+Tab (backward)
  836. if (currentIndex <= 0) {
  837. event.preventDefault();
  838. focusableElements.at(-1)?.focus();
  839. }
  840. }
  841. else if (currentIndex >= focusableElements.length - 1) {
  842. // Tab (forward)
  843. event.preventDefault();
  844. focusableElements[0]?.focus();
  845. }
  846. }
  847. getFocusableElements() {
  848. const selector = [
  849. 'a[href]',
  850. 'button:not([disabled])',
  851. 'input:not([disabled])',
  852. 'select:not([disabled])',
  853. 'textarea:not([disabled])',
  854. '[tabindex]:not([tabindex="-1"])',
  855. '[contenteditable="true"]'
  856. ].join(', ');
  857. return Array.from(document.querySelectorAll(selector));
  858. }
  859. handleEscapeKey(event) {
  860. // Close modals, dropdowns, etc.
  861. const activeModal = document.querySelector('.modal.show');
  862. const activeDropdown = document.querySelector('.dropdown-menu.show');
  863. if (activeModal) {
  864. const closeButton = activeModal.querySelector('[data-bs-dismiss="modal"]');
  865. closeButton?.click();
  866. event.preventDefault();
  867. }
  868. else if (activeDropdown) {
  869. const toggleButton = document.querySelector('[data-bs-toggle="dropdown"][aria-expanded="true"]');
  870. toggleButton?.click();
  871. event.preventDefault();
  872. }
  873. }
  874. // WCAG 2.1.1: Keyboard Access
  875. initKeyboardNavigation() {
  876. // Add keyboard support for custom components
  877. document.addEventListener('keydown', (event) => {
  878. const target = event.target;
  879. // Handle arrow key navigation for menus
  880. if (target.closest('.nav, .navbar-nav, .dropdown-menu')) {
  881. this.handleMenuNavigation(event);
  882. }
  883. // Handle Enter and Space for custom buttons
  884. if ((event.key === 'Enter' || event.key === ' ') && target.hasAttribute('role') && target.getAttribute('role') === 'button' && !target.matches('button, input[type="button"], input[type="submit"]')) {
  885. event.preventDefault();
  886. target.click();
  887. }
  888. });
  889. }
  890. handleMenuNavigation(event) {
  891. if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) {
  892. return;
  893. }
  894. const currentElement = event.target;
  895. const menuItems = Array.from(currentElement.closest('.nav, .navbar-nav, .dropdown-menu')?.querySelectorAll('a, button') || []);
  896. const currentIndex = menuItems.indexOf(currentElement);
  897. let nextIndex;
  898. switch (event.key) {
  899. case 'ArrowDown':
  900. case 'ArrowRight': {
  901. nextIndex = currentIndex < menuItems.length - 1 ? currentIndex + 1 : 0;
  902. break;
  903. }
  904. case 'ArrowUp':
  905. case 'ArrowLeft': {
  906. nextIndex = currentIndex > 0 ? currentIndex - 1 : menuItems.length - 1;
  907. break;
  908. }
  909. case 'Home': {
  910. nextIndex = 0;
  911. break;
  912. }
  913. case 'End': {
  914. nextIndex = menuItems.length - 1;
  915. break;
  916. }
  917. default: {
  918. return;
  919. }
  920. }
  921. event.preventDefault();
  922. menuItems[nextIndex]?.focus();
  923. }
  924. // WCAG 2.3.3: Animation from Interactions
  925. respectReducedMotion() {
  926. const prefersReducedMotion = globalThis.matchMedia('(prefers-reduced-motion: reduce)').matches;
  927. if (prefersReducedMotion) {
  928. document.body.classList.add('reduce-motion');
  929. // Disable smooth scrolling
  930. document.documentElement.style.scrollBehavior = 'auto';
  931. // Reduce animation duration
  932. const style = document.createElement('style');
  933. style.textContent = `
  934. *, *::before, *::after {
  935. animation-duration: 0.01ms !important;
  936. animation-iteration-count: 1 !important;
  937. transition-duration: 0.01ms !important;
  938. }
  939. `;
  940. document.head.append(style);
  941. }
  942. }
  943. // WCAG 3.3.1: Error Identification
  944. initErrorAnnouncements() {
  945. const observer = new MutationObserver((mutations) => {
  946. mutations.forEach((mutation) => {
  947. mutation.addedNodes.forEach((node) => {
  948. if (node.nodeType === Node.ELEMENT_NODE) {
  949. const element = node;
  950. // Check for error messages
  951. if (element.matches('.alert-danger, .invalid-feedback, .error')) {
  952. this.announce(element.textContent || 'Error occurred', 'assertive');
  953. }
  954. // Check for success messages
  955. if (element.matches('.alert-success, .success')) {
  956. this.announce(element.textContent || 'Success', 'polite');
  957. }
  958. }
  959. });
  960. });
  961. });
  962. observer.observe(document.body, {
  963. childList: true,
  964. subtree: true
  965. });
  966. }
  967. // WCAG 1.3.1: Info and Relationships
  968. initTableAccessibility() {
  969. document.querySelectorAll('table').forEach((table) => {
  970. // Add table role if missing
  971. if (!table.hasAttribute('role')) {
  972. table.setAttribute('role', 'table');
  973. }
  974. // Ensure headers have proper scope
  975. table.querySelectorAll('th').forEach((th) => {
  976. if (!th.hasAttribute('scope')) {
  977. const isInThead = th.closest('thead');
  978. const isFirstColumn = th.cellIndex === 0;
  979. if (isInThead) {
  980. th.setAttribute('scope', 'col');
  981. }
  982. else if (isFirstColumn) {
  983. th.setAttribute('scope', 'row');
  984. }
  985. }
  986. });
  987. // Add caption if missing but title exists
  988. if (!table.querySelector('caption') && table.hasAttribute('title')) {
  989. const caption = document.createElement('caption');
  990. caption.textContent = table.getAttribute('title') || '';
  991. table.insertBefore(caption, table.firstChild);
  992. }
  993. });
  994. }
  995. // WCAG 3.3.2: Labels or Instructions
  996. initFormAccessibility() {
  997. document.querySelectorAll('input, select, textarea').forEach((input) => {
  998. const htmlInput = input;
  999. // Ensure all inputs have labels
  1000. if (!htmlInput.labels?.length && !htmlInput.hasAttribute('aria-label') && !htmlInput.hasAttribute('aria-labelledby')) {
  1001. const placeholder = htmlInput.getAttribute('placeholder');
  1002. if (placeholder) {
  1003. htmlInput.setAttribute('aria-label', placeholder);
  1004. }
  1005. }
  1006. // Add required indicators
  1007. if (htmlInput.hasAttribute('required')) {
  1008. const label = htmlInput.labels?.[0];
  1009. if (label && !label.querySelector('.required-indicator')) {
  1010. const indicator = document.createElement('span');
  1011. indicator.className = 'required-indicator sr-only';
  1012. indicator.textContent = ' (required)';
  1013. label.append(indicator);
  1014. }
  1015. }
  1016. // Handle invalid states
  1017. htmlInput.addEventListener('invalid', () => {
  1018. this.handleFormError(htmlInput);
  1019. });
  1020. });
  1021. }
  1022. handleFormError(input) {
  1023. const errorId = `${input.id || input.name}-error`;
  1024. let errorElement = document.getElementById(errorId);
  1025. if (!errorElement) {
  1026. errorElement = document.createElement('div');
  1027. errorElement.id = errorId;
  1028. errorElement.className = 'invalid-feedback';
  1029. errorElement.setAttribute('role', 'alert');
  1030. input.parentNode?.insertBefore(errorElement, input.nextSibling);
  1031. }
  1032. errorElement.textContent = input.validationMessage;
  1033. input.setAttribute('aria-describedby', errorId);
  1034. input.classList.add('is-invalid');
  1035. this.announce(`Error in ${input.labels?.[0]?.textContent || input.name}: ${input.validationMessage}`, 'assertive');
  1036. }
  1037. // Modal focus management
  1038. initModalFocusManagement() {
  1039. document.addEventListener('shown.bs.modal', (event) => {
  1040. const modal = event.target;
  1041. const focusableElements = modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
  1042. if (focusableElements.length > 0) {
  1043. focusableElements[0].focus();
  1044. }
  1045. // Store previous focus
  1046. this.focusHistory.push(document.activeElement);
  1047. });
  1048. document.addEventListener('hidden.bs.modal', () => {
  1049. // Restore previous focus
  1050. const previousElement = this.focusHistory.pop();
  1051. if (previousElement) {
  1052. previousElement.focus();
  1053. }
  1054. });
  1055. }
  1056. // Dropdown focus management
  1057. initDropdownFocusManagement() {
  1058. document.addEventListener('shown.bs.dropdown', (event) => {
  1059. const dropdown = event.target;
  1060. const menu = dropdown.querySelector('.dropdown-menu');
  1061. const firstItem = menu?.querySelector('a, button');
  1062. if (firstItem) {
  1063. firstItem.focus();
  1064. }
  1065. });
  1066. }
  1067. // Public API methods
  1068. announce(message, priority = 'polite') {
  1069. if (!this.liveRegion) {
  1070. this.createLiveRegion();
  1071. }
  1072. if (this.liveRegion) {
  1073. this.liveRegion.setAttribute('aria-live', priority);
  1074. this.liveRegion.textContent = message;
  1075. // Clear after announcement
  1076. setTimeout(() => {
  1077. if (this.liveRegion) {
  1078. this.liveRegion.textContent = '';
  1079. }
  1080. }, 1000);
  1081. }
  1082. }
  1083. focusElement(selector) {
  1084. const element = document.querySelector(selector);
  1085. if (element) {
  1086. element.focus();
  1087. // Ensure element is visible
  1088. element.scrollIntoView({ behavior: 'smooth', block: 'center' });
  1089. }
  1090. }
  1091. trapFocus(container) {
  1092. const focusableElements = container.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
  1093. const focusableArray = Array.from(focusableElements);
  1094. const firstElement = focusableArray[0];
  1095. const lastElement = focusableArray.at(-1);
  1096. container.addEventListener('keydown', (event) => {
  1097. if (event.key === 'Tab') {
  1098. if (event.shiftKey) {
  1099. if (document.activeElement === firstElement) {
  1100. lastElement?.focus();
  1101. event.preventDefault();
  1102. }
  1103. }
  1104. else if (document.activeElement === lastElement) {
  1105. firstElement.focus();
  1106. event.preventDefault();
  1107. }
  1108. }
  1109. });
  1110. }
  1111. addLandmarks() {
  1112. // Add main landmark if missing
  1113. const main = document.querySelector('main');
  1114. if (!main) {
  1115. const appMain = document.querySelector('.app-main');
  1116. if (appMain) {
  1117. appMain.setAttribute('role', 'main');
  1118. appMain.id = 'main';
  1119. }
  1120. }
  1121. // Add navigation landmarks
  1122. document.querySelectorAll('.navbar-nav, .nav').forEach((nav, index) => {
  1123. if (!nav.hasAttribute('role')) {
  1124. nav.setAttribute('role', 'navigation');
  1125. }
  1126. if (!nav.hasAttribute('aria-label')) {
  1127. nav.setAttribute('aria-label', `Navigation ${index + 1}`);
  1128. }
  1129. });
  1130. // Add search landmark
  1131. const searchForm = document.querySelector('form[role="search"], .navbar-search');
  1132. if (searchForm && !searchForm.hasAttribute('role')) {
  1133. searchForm.setAttribute('role', 'search');
  1134. }
  1135. }
  1136. }
  1137. // Initialize accessibility when DOM is ready
  1138. const initAccessibility = (config) => {
  1139. return new AccessibilityManager(config);
  1140. };
  1141. /**
  1142. * AdminLTE v4.0.0-rc5
  1143. * Author: Colorlib
  1144. * Website: AdminLTE.io <https://adminlte.io>
  1145. * License: Open source - MIT <https://opensource.org/licenses/MIT>
  1146. */
  1147. onDOMContentLoaded(() => {
  1148. /**
  1149. * Initialize AdminLTE Core Components
  1150. * -------------------------------
  1151. */
  1152. const layout = new Layout(document.body);
  1153. layout.holdTransition();
  1154. /**
  1155. * Initialize Accessibility Features - WCAG 2.1 AA Compliance
  1156. * --------------------------------------------------------
  1157. */
  1158. const accessibilityManager = initAccessibility({
  1159. announcements: true,
  1160. skipLinks: true,
  1161. focusManagement: true,
  1162. keyboardNavigation: true,
  1163. reducedMotion: true
  1164. });
  1165. // Add semantic landmarks
  1166. accessibilityManager.addLandmarks();
  1167. // Mark app as loaded after initialization
  1168. setTimeout(() => {
  1169. document.body.classList.add('app-loaded');
  1170. }, 400);
  1171. });
  1172. exports.CardWidget = CardWidget;
  1173. exports.DirectChat = DirectChat;
  1174. exports.FullScreen = FullScreen;
  1175. exports.Layout = Layout;
  1176. exports.PushMenu = PushMenu;
  1177. exports.Treeview = Treeview;
  1178. exports.initAccessibility = initAccessibility;
  1179. }));
  1180. //# sourceMappingURL=adminlte.js.map