adminlte.js 43 KB

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