adminlte.js 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203
  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 Defaults = {
  606. sidebarBreakpoint: 992
  607. };
  608. /**
  609. * Class Definition
  610. * ====================================================
  611. */
  612. class PushMenu {
  613. _element;
  614. _config;
  615. constructor(element, config) {
  616. this._element = element;
  617. this._config = { ...Defaults, ...config };
  618. }
  619. menusClose() {
  620. const navTreeview = document.querySelectorAll(SELECTOR_NAV_TREEVIEW);
  621. navTreeview.forEach(navTree => {
  622. navTree.style.removeProperty('display');
  623. navTree.style.removeProperty('height');
  624. });
  625. const navSidebar = document.querySelector(SELECTOR_SIDEBAR_MENU);
  626. const navItem = navSidebar?.querySelectorAll(SELECTOR_NAV_ITEM);
  627. if (navItem) {
  628. navItem.forEach(navI => {
  629. navI.classList.remove(CLASS_NAME_MENU_OPEN);
  630. });
  631. }
  632. }
  633. expand() {
  634. const event = new Event(EVENT_OPEN);
  635. document.body.classList.remove(CLASS_NAME_SIDEBAR_COLLAPSE);
  636. document.body.classList.add(CLASS_NAME_SIDEBAR_OPEN);
  637. this._element.dispatchEvent(event);
  638. }
  639. collapse() {
  640. const event = new Event(EVENT_COLLAPSE);
  641. document.body.classList.remove(CLASS_NAME_SIDEBAR_OPEN);
  642. document.body.classList.add(CLASS_NAME_SIDEBAR_COLLAPSE);
  643. this._element.dispatchEvent(event);
  644. }
  645. addSidebarBreakPoint() {
  646. const sidebarExpandList = document.querySelector(SELECTOR_SIDEBAR_EXPAND)?.classList ?? [];
  647. const sidebarExpand = Array.from(sidebarExpandList).find(className => className.startsWith(CLASS_NAME_SIDEBAR_EXPAND)) ?? '';
  648. const sidebar = document.getElementsByClassName(sidebarExpand)[0];
  649. const sidebarContent = globalThis.getComputedStyle(sidebar, '::before').getPropertyValue('content');
  650. this._config = { ...this._config, sidebarBreakpoint: Number(sidebarContent.replace(/[^\d.-]/g, '')) };
  651. // FIXED: Don't auto-collapse on mobile if sidebar is currently open
  652. // This prevents resize events (triggered by scrolling) from closing the sidebar
  653. const isCurrentlyOpen = document.body.classList.contains(CLASS_NAME_SIDEBAR_OPEN);
  654. if (window.innerWidth <= this._config.sidebarBreakpoint) {
  655. // Only collapse if not currently open (prevents scroll-triggered closes)
  656. if (!isCurrentlyOpen) {
  657. this.collapse();
  658. }
  659. }
  660. else {
  661. if (!document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI)) {
  662. this.expand();
  663. }
  664. if (document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI) && document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {
  665. this.collapse();
  666. }
  667. }
  668. }
  669. toggle() {
  670. if (document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {
  671. this.expand();
  672. }
  673. else {
  674. this.collapse();
  675. }
  676. }
  677. init() {
  678. this.addSidebarBreakPoint();
  679. }
  680. }
  681. /**
  682. * ------------------------------------------------------------------------
  683. * Data Api implementation
  684. * ------------------------------------------------------------------------
  685. */
  686. onDOMContentLoaded(() => {
  687. const sidebar = document?.querySelector(SELECTOR_APP_SIDEBAR);
  688. if (sidebar) {
  689. const data = new PushMenu(sidebar, Defaults);
  690. data.init();
  691. window.addEventListener('resize', () => {
  692. data.init();
  693. });
  694. }
  695. const sidebarOverlay = document.createElement('div');
  696. sidebarOverlay.className = CLASS_NAME_SIDEBAR_OVERLAY;
  697. document.querySelector(SELECTOR_APP_WRAPPER)?.append(sidebarOverlay);
  698. let overlayTouchMoved = false;
  699. // Handle touch events on overlay (area outside sidebar)
  700. sidebarOverlay.addEventListener('touchstart', () => {
  701. overlayTouchMoved = false;
  702. }, { passive: true });
  703. sidebarOverlay.addEventListener('touchmove', () => {
  704. overlayTouchMoved = true;
  705. }, { passive: true });
  706. sidebarOverlay.addEventListener('touchend', event => {
  707. if (!overlayTouchMoved) {
  708. event.preventDefault();
  709. const target = event.currentTarget;
  710. const data = new PushMenu(target, Defaults);
  711. data.collapse();
  712. }
  713. overlayTouchMoved = false;
  714. }, { passive: false });
  715. sidebarOverlay.addEventListener('click', event => {
  716. event.preventDefault();
  717. const target = event.currentTarget;
  718. const data = new PushMenu(target, Defaults);
  719. data.collapse();
  720. });
  721. const fullBtn = document.querySelectorAll(SELECTOR_SIDEBAR_TOGGLE);
  722. fullBtn.forEach(btn => {
  723. btn.addEventListener('click', event => {
  724. event.preventDefault();
  725. let button = event.currentTarget;
  726. if (button?.dataset.lteToggle !== 'sidebar') {
  727. button = button?.closest(SELECTOR_SIDEBAR_TOGGLE);
  728. }
  729. if (button) {
  730. event?.preventDefault();
  731. const data = new PushMenu(button, Defaults);
  732. data.toggle();
  733. }
  734. });
  735. });
  736. });
  737. /**
  738. * AdminLTE Accessibility Module
  739. * WCAG 2.1 AA Compliance Features
  740. */
  741. class AccessibilityManager {
  742. config;
  743. liveRegion = null;
  744. focusHistory = [];
  745. constructor(config = {}) {
  746. this.config = {
  747. announcements: true,
  748. skipLinks: true,
  749. focusManagement: true,
  750. keyboardNavigation: true,
  751. reducedMotion: true,
  752. ...config
  753. };
  754. this.init();
  755. }
  756. init() {
  757. if (this.config.announcements) {
  758. this.createLiveRegion();
  759. }
  760. if (this.config.skipLinks) {
  761. this.addSkipLinks();
  762. }
  763. if (this.config.focusManagement) {
  764. this.initFocusManagement();
  765. }
  766. if (this.config.keyboardNavigation) {
  767. this.initKeyboardNavigation();
  768. }
  769. if (this.config.reducedMotion) {
  770. this.respectReducedMotion();
  771. }
  772. this.initErrorAnnouncements();
  773. this.initTableAccessibility();
  774. this.initFormAccessibility();
  775. }
  776. // WCAG 4.1.3: Status Messages
  777. createLiveRegion() {
  778. if (this.liveRegion)
  779. return;
  780. this.liveRegion = document.createElement('div');
  781. this.liveRegion.id = 'live-region';
  782. this.liveRegion.className = 'live-region';
  783. this.liveRegion.setAttribute('aria-live', 'polite');
  784. this.liveRegion.setAttribute('aria-atomic', 'true');
  785. this.liveRegion.setAttribute('role', 'status');
  786. document.body.append(this.liveRegion);
  787. }
  788. // WCAG 2.4.1: Bypass Blocks
  789. addSkipLinks() {
  790. const skipLinksContainer = document.createElement('div');
  791. skipLinksContainer.className = 'skip-links';
  792. const skipToMain = document.createElement('a');
  793. skipToMain.href = '#main';
  794. skipToMain.className = 'skip-link';
  795. skipToMain.textContent = 'Skip to main content';
  796. const skipToNav = document.createElement('a');
  797. skipToNav.href = '#navigation';
  798. skipToNav.className = 'skip-link';
  799. skipToNav.textContent = 'Skip to navigation';
  800. skipLinksContainer.append(skipToMain);
  801. skipLinksContainer.append(skipToNav);
  802. document.body.insertBefore(skipLinksContainer, document.body.firstChild);
  803. // Ensure targets exist and are focusable
  804. this.ensureSkipTargets();
  805. }
  806. ensureSkipTargets() {
  807. const main = document.querySelector('#main, main, [role="main"]');
  808. if (main && !main.id) {
  809. main.id = 'main';
  810. }
  811. if (main && !main.hasAttribute('tabindex')) {
  812. main.setAttribute('tabindex', '-1');
  813. }
  814. const nav = document.querySelector('#navigation, nav, [role="navigation"]');
  815. if (nav && !nav.id) {
  816. nav.id = 'navigation';
  817. }
  818. if (nav && !nav.hasAttribute('tabindex')) {
  819. nav.setAttribute('tabindex', '-1');
  820. }
  821. }
  822. // WCAG 2.4.3: Focus Order & 2.4.7: Focus Visible
  823. initFocusManagement() {
  824. document.addEventListener('keydown', (event) => {
  825. if (event.key === 'Tab') {
  826. this.handleTabNavigation(event);
  827. }
  828. if (event.key === 'Escape') {
  829. this.handleEscapeKey(event);
  830. }
  831. });
  832. // Focus management for modals and dropdowns
  833. this.initModalFocusManagement();
  834. this.initDropdownFocusManagement();
  835. }
  836. handleTabNavigation(event) {
  837. const focusableElements = this.getFocusableElements();
  838. const currentIndex = focusableElements.indexOf(document.activeElement);
  839. if (event.shiftKey) {
  840. // Shift+Tab (backward)
  841. if (currentIndex <= 0) {
  842. event.preventDefault();
  843. focusableElements.at(-1)?.focus();
  844. }
  845. }
  846. else if (currentIndex >= focusableElements.length - 1) {
  847. // Tab (forward)
  848. event.preventDefault();
  849. focusableElements[0]?.focus();
  850. }
  851. }
  852. getFocusableElements() {
  853. const selector = [
  854. 'a[href]',
  855. 'button:not([disabled])',
  856. 'input:not([disabled])',
  857. 'select:not([disabled])',
  858. 'textarea:not([disabled])',
  859. '[tabindex]:not([tabindex="-1"])',
  860. '[contenteditable="true"]'
  861. ].join(', ');
  862. return Array.from(document.querySelectorAll(selector));
  863. }
  864. handleEscapeKey(event) {
  865. // Close modals, dropdowns, etc.
  866. const activeModal = document.querySelector('.modal.show');
  867. const activeDropdown = document.querySelector('.dropdown-menu.show');
  868. if (activeModal) {
  869. const closeButton = activeModal.querySelector('[data-bs-dismiss="modal"]');
  870. closeButton?.click();
  871. event.preventDefault();
  872. }
  873. else if (activeDropdown) {
  874. const toggleButton = document.querySelector('[data-bs-toggle="dropdown"][aria-expanded="true"]');
  875. toggleButton?.click();
  876. event.preventDefault();
  877. }
  878. }
  879. // WCAG 2.1.1: Keyboard Access
  880. initKeyboardNavigation() {
  881. // Add keyboard support for custom components
  882. document.addEventListener('keydown', (event) => {
  883. const target = event.target;
  884. // Handle arrow key navigation for menus
  885. if (target.closest('.nav, .navbar-nav, .dropdown-menu')) {
  886. this.handleMenuNavigation(event);
  887. }
  888. // Handle Enter and Space for custom buttons
  889. if ((event.key === 'Enter' || event.key === ' ') && target.hasAttribute('role') && target.getAttribute('role') === 'button' && !target.matches('button, input[type="button"], input[type="submit"]')) {
  890. event.preventDefault();
  891. target.click();
  892. }
  893. });
  894. }
  895. handleMenuNavigation(event) {
  896. if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) {
  897. return;
  898. }
  899. const currentElement = event.target;
  900. const menuItems = Array.from(currentElement.closest('.nav, .navbar-nav, .dropdown-menu')?.querySelectorAll('a, button') || []);
  901. const currentIndex = menuItems.indexOf(currentElement);
  902. let nextIndex;
  903. switch (event.key) {
  904. case 'ArrowDown':
  905. case 'ArrowRight': {
  906. nextIndex = currentIndex < menuItems.length - 1 ? currentIndex + 1 : 0;
  907. break;
  908. }
  909. case 'ArrowUp':
  910. case 'ArrowLeft': {
  911. nextIndex = currentIndex > 0 ? currentIndex - 1 : menuItems.length - 1;
  912. break;
  913. }
  914. case 'Home': {
  915. nextIndex = 0;
  916. break;
  917. }
  918. case 'End': {
  919. nextIndex = menuItems.length - 1;
  920. break;
  921. }
  922. default: {
  923. return;
  924. }
  925. }
  926. event.preventDefault();
  927. menuItems[nextIndex]?.focus();
  928. }
  929. // WCAG 2.3.3: Animation from Interactions
  930. respectReducedMotion() {
  931. const prefersReducedMotion = globalThis.matchMedia('(prefers-reduced-motion: reduce)').matches;
  932. if (prefersReducedMotion) {
  933. document.body.classList.add('reduce-motion');
  934. // Disable smooth scrolling
  935. document.documentElement.style.scrollBehavior = 'auto';
  936. // Reduce animation duration
  937. const style = document.createElement('style');
  938. style.textContent = `
  939. *, *::before, *::after {
  940. animation-duration: 0.01ms !important;
  941. animation-iteration-count: 1 !important;
  942. transition-duration: 0.01ms !important;
  943. }
  944. `;
  945. document.head.append(style);
  946. }
  947. }
  948. // WCAG 3.3.1: Error Identification
  949. initErrorAnnouncements() {
  950. const observer = new MutationObserver((mutations) => {
  951. mutations.forEach((mutation) => {
  952. mutation.addedNodes.forEach((node) => {
  953. if (node.nodeType === Node.ELEMENT_NODE) {
  954. const element = node;
  955. // Check for error messages
  956. if (element.matches('.alert-danger, .invalid-feedback, .error')) {
  957. this.announce(element.textContent || 'Error occurred', 'assertive');
  958. }
  959. // Check for success messages
  960. if (element.matches('.alert-success, .success')) {
  961. this.announce(element.textContent || 'Success', 'polite');
  962. }
  963. }
  964. });
  965. });
  966. });
  967. observer.observe(document.body, {
  968. childList: true,
  969. subtree: true
  970. });
  971. }
  972. // WCAG 1.3.1: Info and Relationships
  973. initTableAccessibility() {
  974. document.querySelectorAll('table').forEach((table) => {
  975. // Add table role if missing
  976. if (!table.hasAttribute('role')) {
  977. table.setAttribute('role', 'table');
  978. }
  979. // Ensure headers have proper scope
  980. table.querySelectorAll('th').forEach((th) => {
  981. if (!th.hasAttribute('scope')) {
  982. const isInThead = th.closest('thead');
  983. const isFirstColumn = th.cellIndex === 0;
  984. if (isInThead) {
  985. th.setAttribute('scope', 'col');
  986. }
  987. else if (isFirstColumn) {
  988. th.setAttribute('scope', 'row');
  989. }
  990. }
  991. });
  992. // Add caption if missing but title exists
  993. if (!table.querySelector('caption') && table.hasAttribute('title')) {
  994. const caption = document.createElement('caption');
  995. caption.textContent = table.getAttribute('title') || '';
  996. table.insertBefore(caption, table.firstChild);
  997. }
  998. });
  999. }
  1000. // WCAG 3.3.2: Labels or Instructions
  1001. initFormAccessibility() {
  1002. document.querySelectorAll('input, select, textarea').forEach((input) => {
  1003. const htmlInput = input;
  1004. // Ensure all inputs have labels
  1005. if (!htmlInput.labels?.length && !htmlInput.hasAttribute('aria-label') && !htmlInput.hasAttribute('aria-labelledby')) {
  1006. const placeholder = htmlInput.getAttribute('placeholder');
  1007. if (placeholder) {
  1008. htmlInput.setAttribute('aria-label', placeholder);
  1009. }
  1010. }
  1011. // Add required indicators
  1012. if (htmlInput.hasAttribute('required')) {
  1013. const label = htmlInput.labels?.[0];
  1014. if (label && !label.querySelector('.required-indicator')) {
  1015. const indicator = document.createElement('span');
  1016. indicator.className = 'required-indicator sr-only';
  1017. indicator.textContent = ' (required)';
  1018. label.append(indicator);
  1019. }
  1020. }
  1021. // Handle invalid state unless the element explicitly opts out via the
  1022. // 'disable-adminlte-validations' class.
  1023. if (!htmlInput.classList.contains('disable-adminlte-validations')) {
  1024. htmlInput.addEventListener('invalid', () => {
  1025. this.handleFormError(htmlInput);
  1026. });
  1027. }
  1028. });
  1029. }
  1030. handleFormError(input) {
  1031. const errorId = `${input.id || input.name}-error`;
  1032. let errorElement = document.getElementById(errorId);
  1033. if (!errorElement) {
  1034. errorElement = document.createElement('div');
  1035. errorElement.id = errorId;
  1036. errorElement.className = 'invalid-feedback';
  1037. errorElement.setAttribute('role', 'alert');
  1038. // Always append the error element as the last child of the parent.
  1039. // This prevents breaking layouts where inputs use Bootstrap's
  1040. // `.input-group-text` decorators, ensuring the error stays below
  1041. // the entire input group.
  1042. input.parentNode?.append(errorElement);
  1043. }
  1044. errorElement.textContent = input.validationMessage;
  1045. input.setAttribute('aria-describedby', errorId);
  1046. input.classList.add('is-invalid');
  1047. this.announce(`Error in ${input.labels?.[0]?.textContent || input.name}: ${input.validationMessage}`, 'assertive');
  1048. }
  1049. // Modal focus management
  1050. initModalFocusManagement() {
  1051. document.addEventListener('shown.bs.modal', (event) => {
  1052. const modal = event.target;
  1053. const focusableElements = modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
  1054. if (focusableElements.length > 0) {
  1055. focusableElements[0].focus();
  1056. }
  1057. // Store previous focus
  1058. this.focusHistory.push(document.activeElement);
  1059. });
  1060. document.addEventListener('hidden.bs.modal', () => {
  1061. // Restore previous focus
  1062. const previousElement = this.focusHistory.pop();
  1063. if (previousElement) {
  1064. previousElement.focus();
  1065. }
  1066. });
  1067. }
  1068. // Dropdown focus management
  1069. initDropdownFocusManagement() {
  1070. document.addEventListener('shown.bs.dropdown', (event) => {
  1071. const dropdown = event.target;
  1072. const menu = dropdown.querySelector('.dropdown-menu');
  1073. const firstItem = menu?.querySelector('a, button');
  1074. if (firstItem) {
  1075. firstItem.focus();
  1076. }
  1077. });
  1078. }
  1079. // Public API methods
  1080. announce(message, priority = 'polite') {
  1081. if (!this.liveRegion) {
  1082. this.createLiveRegion();
  1083. }
  1084. if (this.liveRegion) {
  1085. this.liveRegion.setAttribute('aria-live', priority);
  1086. this.liveRegion.textContent = message;
  1087. // Clear after announcement
  1088. setTimeout(() => {
  1089. if (this.liveRegion) {
  1090. this.liveRegion.textContent = '';
  1091. }
  1092. }, 1000);
  1093. }
  1094. }
  1095. focusElement(selector) {
  1096. const element = document.querySelector(selector);
  1097. if (element) {
  1098. element.focus();
  1099. // Ensure element is visible
  1100. element.scrollIntoView({ behavior: 'smooth', block: 'center' });
  1101. }
  1102. }
  1103. trapFocus(container) {
  1104. const focusableElements = container.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
  1105. const focusableArray = Array.from(focusableElements);
  1106. const firstElement = focusableArray[0];
  1107. const lastElement = focusableArray.at(-1);
  1108. container.addEventListener('keydown', (event) => {
  1109. if (event.key === 'Tab') {
  1110. if (event.shiftKey) {
  1111. if (document.activeElement === firstElement) {
  1112. lastElement?.focus();
  1113. event.preventDefault();
  1114. }
  1115. }
  1116. else if (document.activeElement === lastElement) {
  1117. firstElement.focus();
  1118. event.preventDefault();
  1119. }
  1120. }
  1121. });
  1122. }
  1123. addLandmarks() {
  1124. // Add main landmark if missing
  1125. const main = document.querySelector('main');
  1126. if (!main) {
  1127. const appMain = document.querySelector('.app-main');
  1128. if (appMain) {
  1129. appMain.setAttribute('role', 'main');
  1130. appMain.id = 'main';
  1131. }
  1132. }
  1133. // Add navigation landmarks
  1134. document.querySelectorAll('.navbar-nav, .nav').forEach((nav, index) => {
  1135. if (!nav.hasAttribute('role')) {
  1136. nav.setAttribute('role', 'navigation');
  1137. }
  1138. if (!nav.hasAttribute('aria-label')) {
  1139. nav.setAttribute('aria-label', `Navigation ${index + 1}`);
  1140. }
  1141. });
  1142. // Add search landmark
  1143. const searchForm = document.querySelector('form[role="search"], .navbar-search');
  1144. if (searchForm && !searchForm.hasAttribute('role')) {
  1145. searchForm.setAttribute('role', 'search');
  1146. }
  1147. }
  1148. }
  1149. // Initialize accessibility when DOM is ready
  1150. const initAccessibility = (config) => {
  1151. return new AccessibilityManager(config);
  1152. };
  1153. /**
  1154. * AdminLTE v4.0.0-rc5
  1155. * Author: Colorlib
  1156. * Website: AdminLTE.io <https://adminlte.io>
  1157. * License: Open source - MIT <https://opensource.org/licenses/MIT>
  1158. */
  1159. onDOMContentLoaded(() => {
  1160. /**
  1161. * Initialize AdminLTE Core Components
  1162. * -------------------------------
  1163. */
  1164. const layout = new Layout(document.body);
  1165. layout.holdTransition();
  1166. /**
  1167. * Initialize Accessibility Features - WCAG 2.1 AA Compliance
  1168. * --------------------------------------------------------
  1169. */
  1170. const accessibilityManager = initAccessibility({
  1171. announcements: true,
  1172. skipLinks: true,
  1173. focusManagement: true,
  1174. keyboardNavigation: true,
  1175. reducedMotion: true
  1176. });
  1177. // Add semantic landmarks
  1178. accessibilityManager.addLandmarks();
  1179. // Mark app as loaded after initialization
  1180. setTimeout(() => {
  1181. document.body.classList.add('app-loaded');
  1182. }, 400);
  1183. });
  1184. exports.CardWidget = CardWidget;
  1185. exports.DirectChat = DirectChat;
  1186. exports.FullScreen = FullScreen;
  1187. exports.Layout = Layout;
  1188. exports.PushMenu = PushMenu;
  1189. exports.Treeview = Treeview;
  1190. exports.initAccessibility = initAccessibility;
  1191. }));
  1192. //# sourceMappingURL=adminlte.js.map