admin-lte 4.0.0-rc6 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +44 -18
  3. package/dist/UI/general.html +311 -92
  4. package/dist/UI/icons.html +311 -92
  5. package/dist/UI/timeline.html +311 -92
  6. package/dist/css/adminlte.css +2814 -1138
  7. package/dist/css/adminlte.css.map +1 -1
  8. package/dist/css/adminlte.min.css +2 -2
  9. package/dist/css/adminlte.min.css.map +1 -1
  10. package/dist/css/adminlte.rtl.css +2814 -1138
  11. package/dist/css/adminlte.rtl.css.map +1 -1
  12. package/dist/css/adminlte.rtl.min.css +2 -2
  13. package/dist/css/adminlte.rtl.min.css.map +1 -1
  14. package/dist/docs/browser-support.html +291 -385
  15. package/dist/docs/color-mode.html +283 -381
  16. package/dist/docs/components/main-header.html +269 -363
  17. package/dist/docs/components/main-sidebar.html +284 -378
  18. package/dist/docs/customization.html +1056 -0
  19. package/dist/docs/deployment.html +1121 -0
  20. package/dist/docs/faq.html +887 -397
  21. package/dist/docs/getting-started.html +967 -0
  22. package/dist/docs/how-to-contribute.html +365 -450
  23. package/dist/docs/index.html +16 -0
  24. package/dist/docs/integrations.html +1207 -0
  25. package/dist/docs/introduction.html +483 -434
  26. package/dist/docs/javascript/accessibility.html +925 -0
  27. package/dist/docs/javascript/card-widget.html +995 -0
  28. package/dist/docs/javascript/direct-chat.html +872 -0
  29. package/dist/docs/javascript/fullscreen.html +881 -0
  30. package/dist/docs/javascript/layout.html +884 -0
  31. package/dist/docs/javascript/plugins-overview.html +1120 -0
  32. package/dist/docs/javascript/pushmenu.html +522 -377
  33. package/dist/docs/javascript/treeview.html +498 -390
  34. package/dist/docs/layout-blueprint.html +1063 -0
  35. package/dist/docs/layout.html +316 -402
  36. package/dist/docs/license.html +282 -375
  37. package/dist/docs/migration.html +1125 -0
  38. package/dist/docs/recipes.html +1161 -0
  39. package/dist/docs/rtl.html +933 -0
  40. package/dist/examples/lockscreen.html +66 -2
  41. package/dist/examples/login-v2.html +65 -1
  42. package/dist/examples/login.html +65 -1
  43. package/dist/examples/register-v2.html +65 -1
  44. package/dist/examples/register.html +65 -1
  45. package/dist/forms/elements.html +1381 -0
  46. package/dist/forms/{general.html → layout.html} +382 -452
  47. package/dist/forms/validation.html +1251 -0
  48. package/dist/forms/wizard.html +1386 -0
  49. package/dist/generate/theme.html +135 -5
  50. package/dist/index.html +311 -92
  51. package/dist/index2.html +314 -92
  52. package/dist/index3.html +311 -92
  53. package/dist/js/adminlte.js +135 -314
  54. package/dist/js/adminlte.js.map +1 -1
  55. package/dist/js/adminlte.min.js +3 -3
  56. package/dist/js/adminlte.min.js.map +1 -1
  57. package/dist/layout/collapsed-sidebar-without-hover.html +1120 -0
  58. package/dist/layout/collapsed-sidebar.html +311 -92
  59. package/dist/layout/fixed-complete.html +311 -92
  60. package/dist/layout/fixed-footer.html +311 -92
  61. package/dist/layout/fixed-header.html +311 -92
  62. package/dist/layout/fixed-sidebar.html +311 -92
  63. package/dist/layout/layout-custom-area.html +311 -92
  64. package/dist/layout/layout-rtl.html +311 -92
  65. package/dist/layout/logo-switch.html +135 -5
  66. package/dist/layout/sidebar-mini.html +311 -92
  67. package/dist/layout/unfixed-sidebar.html +311 -92
  68. package/dist/mailbox/compose.html +1127 -0
  69. package/dist/mailbox/inbox.html +1477 -0
  70. package/dist/mailbox/read.html +1178 -0
  71. package/dist/pages/404.html +213 -0
  72. package/dist/pages/500.html +201 -0
  73. package/dist/pages/calendar.html +1242 -0
  74. package/dist/pages/chat.html +1403 -0
  75. package/dist/pages/faq.html +1359 -0
  76. package/dist/pages/file-manager.html +1811 -0
  77. package/dist/pages/invoice.html +1175 -0
  78. package/dist/pages/kanban.html +1415 -0
  79. package/dist/pages/maintenance.html +204 -0
  80. package/dist/pages/pricing.html +1255 -0
  81. package/dist/pages/profile.html +1358 -0
  82. package/dist/pages/projects.html +1620 -0
  83. package/dist/pages/settings.html +1363 -0
  84. package/dist/tables/data.html +1295 -0
  85. package/dist/tables/simple.html +311 -92
  86. package/dist/widgets/cards.html +311 -92
  87. package/dist/widgets/info-box.html +311 -92
  88. package/dist/widgets/small-box.html +311 -92
  89. package/netlify.toml +16 -0
  90. package/package.json +32 -26
  91. package/src/scss/_accessibility.scss +44 -0
  92. package/src/scss/_app-footer.scss +9 -0
  93. package/src/scss/_app-header.scss +27 -0
  94. package/src/scss/_app-sidebar.scss +73 -74
  95. package/src/scss/_bootstrap-variables.scss +12 -5
  96. package/src/scss/_docs.scss +816 -0
  97. package/src/scss/_table.scss +4 -4
  98. package/src/scss/adminlte.scss +4 -1
  99. package/src/scss/pages/_login_and_register.scss +1 -1
  100. package/src/ts/accessibility.ts +9 -7
  101. package/src/ts/adminlte.ts +2 -14
  102. package/src/ts/fullscreen.ts +8 -4
  103. package/src/ts/layout.ts +34 -16
  104. package/src/ts/push-menu.ts +257 -104
  105. package/src/ts/util/index.ts +10 -0
  106. package/.claude/settings.local.json +0 -33
  107. package/CLAUDE.md +0 -79
@@ -1,6 +1,6 @@
1
1
  /*!
2
- * AdminLTE v4.0.0-rc6 (https://adminlte.io)
3
- * Copyright 2014-2025 Colorlib <https://colorlib.com>
2
+ * AdminLTE v4.0.0 (https://adminlte.io)
3
+ * Copyright 2014-2026 Colorlib <https://colorlib.com>
4
4
  * Licensed under MIT (https://github.com/ColorlibHQ/AdminLTE/blob/master/LICENSE)
5
5
  */
6
6
  (function (global, factory) {
@@ -12,7 +12,6 @@
12
12
  const domContentLoadedCallbacks = [];
13
13
  const onDOMContentLoaded = (callback) => {
14
14
  if (document.readyState === 'loading') {
15
- // add listener on the first call when the document is in loading state
16
15
  if (!domContentLoadedCallbacks.length) {
17
16
  document.addEventListener('DOMContentLoaded', () => {
18
17
  for (const callback of domContentLoadedCallbacks) {
@@ -26,8 +25,11 @@
26
25
  callback();
27
26
  }
28
27
  };
29
- /* SLIDE UP */
30
28
  const slideUp = (target, duration = 500) => {
29
+ if (duration <= 1) {
30
+ target.style.display = 'none';
31
+ return;
32
+ }
31
33
  target.style.transitionProperty = 'height, margin, padding';
32
34
  target.style.transitionDuration = `${duration}ms`;
33
35
  target.style.boxSizing = 'border-box';
@@ -52,7 +54,6 @@
52
54
  target.style.removeProperty('transition-property');
53
55
  }, duration);
54
56
  };
55
- /* SLIDE DOWN */
56
57
  const slideDown = (target, duration = 500) => {
57
58
  target.style.removeProperty('display');
58
59
  let { display } = globalThis.getComputedStyle(target);
@@ -60,6 +61,9 @@
60
61
  display = 'block';
61
62
  }
62
63
  target.style.display = display;
64
+ if (duration <= 1) {
65
+ return;
66
+ }
63
67
  const height = target.offsetHeight;
64
68
  target.style.overflow = 'hidden';
65
69
  target.style.height = '0';
@@ -85,59 +89,33 @@
85
89
  }, duration);
86
90
  };
87
91
 
88
- /**
89
- * --------------------------------------------
90
- * @file AdminLTE layout.ts
91
- * @description Layout for AdminLTE.
92
- * @license MIT
93
- * --------------------------------------------
94
- */
95
- /**
96
- * ------------------------------------------------------------------------
97
- * Constants
98
- * ------------------------------------------------------------------------
99
- */
100
92
  const CLASS_NAME_HOLD_TRANSITIONS = 'hold-transition';
101
93
  const CLASS_NAME_APP_LOADED = 'app-loaded';
102
- /**
103
- * Class Definition
104
- * ====================================================
105
- */
106
94
  class Layout {
107
95
  _element;
96
+ _holdTransitionTimer;
108
97
  constructor(element) {
109
98
  this._element = element;
99
+ this._holdTransitionTimer = undefined;
110
100
  }
111
- holdTransition() {
112
- let resizeTimer;
113
- window.addEventListener('resize', () => {
114
- document.body.classList.add(CLASS_NAME_HOLD_TRANSITIONS);
115
- clearTimeout(resizeTimer);
116
- resizeTimer = setTimeout(() => {
117
- document.body.classList.remove(CLASS_NAME_HOLD_TRANSITIONS);
118
- }, 400);
119
- });
101
+ holdTransition(time = 100) {
102
+ if (this._holdTransitionTimer) {
103
+ clearTimeout(this._holdTransitionTimer);
104
+ }
105
+ document.body.classList.add(CLASS_NAME_HOLD_TRANSITIONS);
106
+ this._holdTransitionTimer = setTimeout(() => {
107
+ document.body.classList.remove(CLASS_NAME_HOLD_TRANSITIONS);
108
+ }, time);
120
109
  }
121
110
  }
122
111
  onDOMContentLoaded(() => {
123
- const data = new Layout(document.body);
124
- data.holdTransition();
112
+ const layout = new Layout(document.body);
113
+ window.addEventListener('resize', () => layout.holdTransition(200));
125
114
  setTimeout(() => {
126
115
  document.body.classList.add(CLASS_NAME_APP_LOADED);
127
116
  }, 400);
128
117
  });
129
118
 
130
- /**
131
- * --------------------------------------------
132
- * @file AdminLTE card-widget.ts
133
- * @description Card widget for AdminLTE.
134
- * @license MIT
135
- * --------------------------------------------
136
- */
137
- /**
138
- * Constants
139
- * ====================================================
140
- */
141
119
  const DATA_KEY$4 = 'lte.card-widget';
142
120
  const EVENT_KEY$4 = `.${DATA_KEY$4}`;
143
121
  const EVENT_COLLAPSED$2 = `collapsed${EVENT_KEY$4}`;
@@ -180,7 +158,6 @@
180
158
  const event = new Event(EVENT_COLLAPSED$2);
181
159
  if (this._parent) {
182
160
  this._parent.classList.add(CLASS_NAME_COLLAPSING);
183
- // Only target direct children to avoid affecting nested cards
184
161
  const elm = this._parent?.querySelectorAll(`:scope > ${SELECTOR_CARD_BODY}, :scope > ${SELECTOR_CARD_FOOTER}`);
185
162
  elm.forEach(el => {
186
163
  if (el instanceof HTMLElement) {
@@ -200,7 +177,6 @@
200
177
  const event = new Event(EVENT_EXPANDED$2);
201
178
  if (this._parent) {
202
179
  this._parent.classList.add(CLASS_NAME_EXPANDING);
203
- // Only target direct children to avoid affecting nested cards
204
180
  const elm = this._parent?.querySelectorAll(`:scope > ${SELECTOR_CARD_BODY}, :scope > ${SELECTOR_CARD_FOOTER}`);
205
181
  elm.forEach(el => {
206
182
  if (el instanceof HTMLElement) {
@@ -279,11 +255,6 @@
279
255
  this.maximize();
280
256
  }
281
257
  }
282
- /**
283
- *
284
- * Data Api implementation
285
- * ====================================================
286
- */
287
258
  onDOMContentLoaded(() => {
288
259
  const collapseBtn = document.querySelectorAll(SELECTOR_DATA_COLLAPSE);
289
260
  collapseBtn.forEach(btn => {
@@ -314,26 +285,13 @@
314
285
  });
315
286
  });
316
287
 
317
- /**
318
- * --------------------------------------------
319
- * @file AdminLTE treeview.ts
320
- * @description Treeview plugin for AdminLTE.
321
- * @license MIT
322
- * --------------------------------------------
323
- */
324
- /**
325
- * ------------------------------------------------------------------------
326
- * Constants
327
- * ------------------------------------------------------------------------
328
- */
329
- // const NAME = 'Treeview'
330
288
  const DATA_KEY$3 = 'lte.treeview';
331
289
  const EVENT_KEY$3 = `.${DATA_KEY$3}`;
332
290
  const EVENT_EXPANDED$1 = `expanded${EVENT_KEY$3}`;
333
291
  const EVENT_COLLAPSED$1 = `collapsed${EVENT_KEY$3}`;
334
292
  const EVENT_LOAD_DATA_API = `load${EVENT_KEY$3}`;
335
- const CLASS_NAME_MENU_OPEN$1 = 'menu-open';
336
- const SELECTOR_NAV_ITEM$1 = '.nav-item';
293
+ const CLASS_NAME_MENU_OPEN = 'menu-open';
294
+ const SELECTOR_NAV_ITEM = '.nav-item';
337
295
  const SELECTOR_NAV_LINK = '.nav-link';
338
296
  const SELECTOR_TREEVIEW_MENU = '.nav-treeview';
339
297
  const SELECTOR_DATA_TOGGLE$1 = '[data-lte-toggle="treeview"]';
@@ -341,10 +299,6 @@
341
299
  animationSpeed: 300,
342
300
  accordion: true
343
301
  };
344
- /**
345
- * Class Definition
346
- * ====================================================
347
- */
348
302
  class Treeview {
349
303
  _element;
350
304
  _config;
@@ -355,10 +309,10 @@
355
309
  open() {
356
310
  const event = new Event(EVENT_EXPANDED$1);
357
311
  if (this._config.accordion) {
358
- const openMenuList = this._element.parentElement?.querySelectorAll(`${SELECTOR_NAV_ITEM$1}.${CLASS_NAME_MENU_OPEN$1}`);
312
+ const openMenuList = this._element.parentElement?.querySelectorAll(`${SELECTOR_NAV_ITEM}.${CLASS_NAME_MENU_OPEN}`);
359
313
  openMenuList?.forEach(openMenu => {
360
314
  if (openMenu !== this._element.parentElement) {
361
- openMenu.classList.remove(CLASS_NAME_MENU_OPEN$1);
315
+ openMenu.classList.remove(CLASS_NAME_MENU_OPEN);
362
316
  const childElement = openMenu?.querySelector(SELECTOR_TREEVIEW_MENU);
363
317
  if (childElement) {
364
318
  slideUp(childElement, this._config.animationSpeed);
@@ -366,7 +320,7 @@
366
320
  }
367
321
  });
368
322
  }
369
- this._element.classList.add(CLASS_NAME_MENU_OPEN$1);
323
+ this._element.classList.add(CLASS_NAME_MENU_OPEN);
370
324
  const childElement = this._element?.querySelector(SELECTOR_TREEVIEW_MENU);
371
325
  if (childElement) {
372
326
  slideDown(childElement, this._config.animationSpeed);
@@ -375,7 +329,7 @@
375
329
  }
376
330
  close() {
377
331
  const event = new Event(EVENT_COLLAPSED$1);
378
- this._element.classList.remove(CLASS_NAME_MENU_OPEN$1);
332
+ this._element.classList.remove(CLASS_NAME_MENU_OPEN);
379
333
  const childElement = this._element?.querySelector(SELECTOR_TREEVIEW_MENU);
380
334
  if (childElement) {
381
335
  slideUp(childElement, this._config.animationSpeed);
@@ -383,7 +337,7 @@
383
337
  this._element.dispatchEvent(event);
384
338
  }
385
339
  toggle() {
386
- if (this._element.classList.contains(CLASS_NAME_MENU_OPEN$1)) {
340
+ if (this._element.classList.contains(CLASS_NAME_MENU_OPEN)) {
387
341
  this.close();
388
342
  }
389
343
  else {
@@ -391,13 +345,8 @@
391
345
  }
392
346
  }
393
347
  }
394
- /**
395
- * ------------------------------------------------------------------------
396
- * Data Api implementation
397
- * ------------------------------------------------------------------------
398
- */
399
348
  onDOMContentLoaded(() => {
400
- const openMenuItems = document.querySelectorAll(`${SELECTOR_NAV_ITEM$1}.${CLASS_NAME_MENU_OPEN$1}`);
349
+ const openMenuItems = document.querySelectorAll(`${SELECTOR_NAV_ITEM}.${CLASS_NAME_MENU_OPEN}`);
401
350
  openMenuItems.forEach(menuItem => {
402
351
  const childElement = menuItem.querySelector(SELECTOR_TREEVIEW_MENU);
403
352
  if (childElement) {
@@ -410,11 +359,10 @@
410
359
  button.forEach(btn => {
411
360
  btn.addEventListener('click', event => {
412
361
  const target = event.target;
413
- const targetItem = target.closest(SELECTOR_NAV_ITEM$1);
362
+ const targetItem = target.closest(SELECTOR_NAV_ITEM);
414
363
  const targetLink = target.closest(SELECTOR_NAV_LINK);
415
364
  const targetTreeviewMenu = targetItem?.querySelector(SELECTOR_TREEVIEW_MENU);
416
365
  const lteToggleElement = event.currentTarget;
417
- // Avoid creating Treeview instances on non menu elements
418
366
  if (!targetTreeviewMenu) {
419
367
  return;
420
368
  }
@@ -422,10 +370,8 @@
422
370
  event.preventDefault();
423
371
  }
424
372
  if (targetItem) {
425
- // Read data attributes
426
373
  const accordionAttr = lteToggleElement.dataset.accordion;
427
374
  const animationSpeedAttr = lteToggleElement.dataset.animationSpeed;
428
- // Build config from data attributes, fallback to Default
429
375
  const config = {
430
376
  accordion: accordionAttr === undefined ? Default.accordion : accordionAttr === 'true',
431
377
  animationSpeed: animationSpeedAttr === undefined ? Default.animationSpeed : Number(animationSpeedAttr)
@@ -437,17 +383,6 @@
437
383
  });
438
384
  });
439
385
 
440
- /**
441
- * --------------------------------------------
442
- * @file AdminLTE direct-chat.ts
443
- * @description Direct chat for AdminLTE.
444
- * @license MIT
445
- * --------------------------------------------
446
- */
447
- /**
448
- * Constants
449
- * ====================================================
450
- */
451
386
  const DATA_KEY$2 = 'lte.direct-chat';
452
387
  const EVENT_KEY$2 = `.${DATA_KEY$2}`;
453
388
  const EVENT_EXPANDED = `expanded${EVENT_KEY$2}`;
@@ -455,10 +390,6 @@
455
390
  const SELECTOR_DATA_TOGGLE = '[data-lte-toggle="chat-pane"]';
456
391
  const SELECTOR_DIRECT_CHAT = '.direct-chat';
457
392
  const CLASS_NAME_DIRECT_CHAT_OPEN = 'direct-chat-contacts-open';
458
- /**
459
- * Class Definition
460
- * ====================================================
461
- */
462
393
  class DirectChat {
463
394
  _element;
464
395
  constructor(element) {
@@ -477,11 +408,6 @@
477
408
  }
478
409
  }
479
410
  }
480
- /**
481
- *
482
- * Data Api implementation
483
- * ====================================================
484
- */
485
411
  onDOMContentLoaded(() => {
486
412
  const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE);
487
413
  button.forEach(btn => {
@@ -497,17 +423,6 @@
497
423
  });
498
424
  });
499
425
 
500
- /**
501
- * --------------------------------------------
502
- * @file AdminLTE fullscreen.ts
503
- * @description Fullscreen plugin for AdminLTE.
504
- * @license MIT
505
- * --------------------------------------------
506
- */
507
- /**
508
- * Constants
509
- * ============================================================================
510
- */
511
426
  const DATA_KEY$1 = 'lte.fullscreen';
512
427
  const EVENT_KEY$1 = `.${DATA_KEY$1}`;
513
428
  const EVENT_MAXIMIZED = `maximized${EVENT_KEY$1}`;
@@ -515,10 +430,6 @@
515
430
  const SELECTOR_FULLSCREEN_TOGGLE = '[data-lte-toggle="fullscreen"]';
516
431
  const SELECTOR_MAXIMIZE_ICON = '[data-lte-icon="maximize"]';
517
432
  const SELECTOR_MINIMIZE_ICON = '[data-lte-icon="minimize"]';
518
- /**
519
- * Class Definition.
520
- * ============================================================================
521
- */
522
433
  class FullScreen {
523
434
  _element;
524
435
  _config;
@@ -532,10 +443,10 @@
532
443
  const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON);
533
444
  void document.documentElement.requestFullscreen();
534
445
  if (iconMaximize) {
535
- iconMaximize.style.display = 'none';
446
+ iconMaximize.classList.add('d-none');
536
447
  }
537
448
  if (iconMinimize) {
538
- iconMinimize.style.display = 'block';
449
+ iconMinimize.classList.remove('d-none');
539
450
  }
540
451
  this._element.dispatchEvent(event);
541
452
  }
@@ -545,10 +456,10 @@
545
456
  const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON);
546
457
  void document.exitFullscreen();
547
458
  if (iconMaximize) {
548
- iconMaximize.style.display = 'block';
459
+ iconMaximize.classList.remove('d-none');
549
460
  }
550
461
  if (iconMinimize) {
551
- iconMinimize.style.display = 'none';
462
+ iconMinimize.classList.add('d-none');
552
463
  }
553
464
  this._element.dispatchEvent(event);
554
465
  }
@@ -563,10 +474,6 @@
563
474
  }
564
475
  }
565
476
  }
566
- /**
567
- * Data Api implementation
568
- * ============================================================================
569
- */
570
477
  onDOMContentLoaded(() => {
571
478
  const buttons = document.querySelectorAll(SELECTOR_FULLSCREEN_TOGGLE);
572
479
  buttons.forEach(btn => {
@@ -582,44 +489,24 @@
582
489
  });
583
490
  });
584
491
 
585
- /**
586
- * --------------------------------------------
587
- * @file AdminLTE push-menu.ts
588
- * @description Push menu for AdminLTE.
589
- * @license MIT
590
- * --------------------------------------------
591
- */
592
- /**
593
- * ------------------------------------------------------------------------
594
- * Constants
595
- * ------------------------------------------------------------------------
596
- */
597
492
  const DATA_KEY = 'lte.push-menu';
598
493
  const EVENT_KEY = `.${DATA_KEY}`;
599
494
  const EVENT_OPEN = `open${EVENT_KEY}`;
600
495
  const EVENT_COLLAPSE = `collapse${EVENT_KEY}`;
601
496
  const CLASS_NAME_SIDEBAR_MINI = 'sidebar-mini';
602
- const CLASS_NAME_SIDEBAR_COLLAPSE = 'sidebar-collapse';
603
- const CLASS_NAME_SIDEBAR_OPEN = 'sidebar-open';
604
497
  const CLASS_NAME_SIDEBAR_EXPAND = 'sidebar-expand';
605
498
  const CLASS_NAME_SIDEBAR_OVERLAY = 'sidebar-overlay';
606
- const CLASS_NAME_MENU_OPEN = 'menu-open';
499
+ const CLASS_NAME_SIDEBAR_COLLAPSE = 'sidebar-collapse';
500
+ const CLASS_NAME_SIDEBAR_OPEN = 'sidebar-open';
607
501
  const SELECTOR_APP_SIDEBAR = '.app-sidebar';
608
- const SELECTOR_SIDEBAR_MENU = '.sidebar-menu';
609
- const SELECTOR_NAV_ITEM = '.nav-item';
610
- const SELECTOR_NAV_TREEVIEW = '.nav-treeview';
611
502
  const SELECTOR_APP_WRAPPER = '.app-wrapper';
612
503
  const SELECTOR_SIDEBAR_EXPAND = `[class*="${CLASS_NAME_SIDEBAR_EXPAND}"]`;
613
504
  const SELECTOR_SIDEBAR_TOGGLE = '[data-lte-toggle="sidebar"]';
614
505
  const STORAGE_KEY_SIDEBAR_STATE = 'lte.sidebar.state';
615
506
  const Defaults = {
616
507
  sidebarBreakpoint: 992,
617
- enablePersistence: true
508
+ enablePersistence: false
618
509
  };
619
- /**
620
- * Class Definition
621
- * ====================================================
622
- */
623
510
  class PushMenu {
624
511
  _element;
625
512
  _config;
@@ -627,100 +514,82 @@
627
514
  this._element = element;
628
515
  this._config = { ...Defaults, ...config };
629
516
  }
630
- menusClose() {
631
- const navTreeview = document.querySelectorAll(SELECTOR_NAV_TREEVIEW);
632
- navTreeview.forEach(navTree => {
633
- navTree.style.removeProperty('display');
634
- navTree.style.removeProperty('height');
635
- });
636
- const navSidebar = document.querySelector(SELECTOR_SIDEBAR_MENU);
637
- const navItem = navSidebar?.querySelectorAll(SELECTOR_NAV_ITEM);
638
- if (navItem) {
639
- navItem.forEach(navI => {
640
- navI.classList.remove(CLASS_NAME_MENU_OPEN);
641
- });
642
- }
517
+ isCollapsed() {
518
+ return document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE);
519
+ }
520
+ isExplicitlyOpen() {
521
+ return document.body.classList.contains(CLASS_NAME_SIDEBAR_OPEN);
522
+ }
523
+ isMiniMode() {
524
+ return document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI);
525
+ }
526
+ isMobileSize() {
527
+ return globalThis.innerWidth <= this._config.sidebarBreakpoint;
643
528
  }
644
529
  expand() {
645
- const event = new Event(EVENT_OPEN);
646
530
  document.body.classList.remove(CLASS_NAME_SIDEBAR_COLLAPSE);
647
- document.body.classList.add(CLASS_NAME_SIDEBAR_OPEN);
648
- this._element.dispatchEvent(event);
531
+ if (this.isMobileSize()) {
532
+ document.body.classList.add(CLASS_NAME_SIDEBAR_OPEN);
533
+ }
534
+ this._element.dispatchEvent(new Event(EVENT_OPEN));
649
535
  }
650
536
  collapse() {
651
- const event = new Event(EVENT_COLLAPSE);
652
537
  document.body.classList.remove(CLASS_NAME_SIDEBAR_OPEN);
653
538
  document.body.classList.add(CLASS_NAME_SIDEBAR_COLLAPSE);
654
- this._element.dispatchEvent(event);
655
- }
656
- addSidebarBreakPoint() {
657
- const sidebarExpandList = document.querySelector(SELECTOR_SIDEBAR_EXPAND)?.classList ?? [];
658
- const sidebarExpand = Array.from(sidebarExpandList).find(className => className.startsWith(CLASS_NAME_SIDEBAR_EXPAND)) ?? '';
659
- const sidebar = document.getElementsByClassName(sidebarExpand)[0];
660
- const sidebarContent = globalThis.getComputedStyle(sidebar, '::before').getPropertyValue('content');
661
- this._config = { ...this._config, sidebarBreakpoint: Number(sidebarContent.replace(/[^\d.-]/g, '')) };
662
- // FIXED: Don't auto-collapse on mobile if sidebar is currently open
663
- // This prevents resize events (triggered by scrolling) from closing the sidebar
664
- const isCurrentlyOpen = document.body.classList.contains(CLASS_NAME_SIDEBAR_OPEN);
665
- if (window.innerWidth <= this._config.sidebarBreakpoint) {
666
- // Only collapse if not currently open (prevents scroll-triggered closes)
667
- if (!isCurrentlyOpen) {
668
- this.collapse();
669
- }
670
- }
671
- else {
672
- if (!document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI)) {
673
- this.expand();
674
- }
675
- if (document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI) && document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {
676
- this.collapse();
677
- }
678
- }
539
+ this._element.dispatchEvent(new Event(EVENT_COLLAPSE));
679
540
  }
680
541
  toggle() {
681
- if (document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE)) {
542
+ const isCollapsed = this.isCollapsed();
543
+ if (isCollapsed) {
682
544
  this.expand();
683
545
  }
684
546
  else {
685
547
  this.collapse();
686
548
  }
687
- this.saveSidebarState();
549
+ if (this._config.enablePersistence) {
550
+ this.saveSidebarState(isCollapsed ? CLASS_NAME_SIDEBAR_OPEN : CLASS_NAME_SIDEBAR_COLLAPSE);
551
+ }
688
552
  }
689
- /**
690
- * Save sidebar state to localStorage
691
- */
692
- saveSidebarState() {
693
- if (!this._config.enablePersistence) {
553
+ setupSidebarBreakPoint() {
554
+ const sidebarExpand = document.querySelector(SELECTOR_SIDEBAR_EXPAND);
555
+ if (!sidebarExpand) {
694
556
  return;
695
557
  }
696
- // Check for SSR environment
697
- if (globalThis.window === undefined || globalThis.localStorage === undefined) {
558
+ const content = globalThis.getComputedStyle(sidebarExpand, '::before')
559
+ .getPropertyValue('content');
560
+ if (!content || content === 'none') {
561
+ return;
562
+ }
563
+ const breakpointValue = Number(content.replace(/[^\d.-]/g, ''));
564
+ if (Number.isNaN(breakpointValue)) {
565
+ return;
566
+ }
567
+ this._config = { ...this._config, sidebarBreakpoint: breakpointValue };
568
+ }
569
+ updateStateByResponsiveLogic() {
570
+ if (this.isMobileSize()) {
571
+ if (!this.isExplicitlyOpen()) {
572
+ this.collapse();
573
+ }
574
+ }
575
+ else {
576
+ if (!(this.isMiniMode() && this.isCollapsed())) {
577
+ this.expand();
578
+ }
579
+ }
580
+ }
581
+ saveSidebarState(state) {
582
+ if (globalThis.localStorage === undefined) {
698
583
  return;
699
584
  }
700
585
  try {
701
- const state = document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE) ?
702
- CLASS_NAME_SIDEBAR_COLLAPSE :
703
- CLASS_NAME_SIDEBAR_OPEN;
704
586
  localStorage.setItem(STORAGE_KEY_SIDEBAR_STATE, state);
705
587
  }
706
588
  catch {
707
- // localStorage may be unavailable (private browsing, quota exceeded, etc.)
708
589
  }
709
590
  }
710
- /**
711
- * Load sidebar state from localStorage
712
- * Only applies on desktop; mobile always starts collapsed
713
- */
714
591
  loadSidebarState() {
715
- if (!this._config.enablePersistence) {
716
- return;
717
- }
718
- // Check for SSR environment
719
- if (globalThis.window === undefined || globalThis.localStorage === undefined) {
720
- return;
721
- }
722
- // Don't restore state on mobile - let responsive behavior handle it
723
- if (globalThis.innerWidth <= this._config.sidebarBreakpoint) {
592
+ if (globalThis.localStorage === undefined) {
724
593
  return;
725
594
  }
726
595
  try {
@@ -731,36 +600,62 @@
731
600
  else if (storedState === CLASS_NAME_SIDEBAR_OPEN) {
732
601
  this.expand();
733
602
  }
734
- // If null (never saved), let default behavior apply
603
+ else {
604
+ this.updateStateByResponsiveLogic();
605
+ }
606
+ }
607
+ catch {
608
+ this.updateStateByResponsiveLogic();
609
+ }
610
+ }
611
+ clearSidebarState() {
612
+ if (globalThis.localStorage === undefined) {
613
+ return;
614
+ }
615
+ try {
616
+ localStorage.removeItem(STORAGE_KEY_SIDEBAR_STATE);
735
617
  }
736
618
  catch {
737
- // localStorage may be unavailable
738
619
  }
739
620
  }
740
621
  init() {
741
- this.addSidebarBreakPoint();
742
- this.loadSidebarState();
622
+ this.setupSidebarBreakPoint();
623
+ if (!this._config.enablePersistence) {
624
+ this.clearSidebarState();
625
+ }
626
+ if (this._config.enablePersistence && !this.isMobileSize()) {
627
+ this.loadSidebarState();
628
+ }
629
+ else {
630
+ this.updateStateByResponsiveLogic();
631
+ }
743
632
  }
744
633
  }
745
- /**
746
- * ------------------------------------------------------------------------
747
- * Data Api implementation
748
- * ------------------------------------------------------------------------
749
- */
750
634
  onDOMContentLoaded(() => {
751
635
  const sidebar = document?.querySelector(SELECTOR_APP_SIDEBAR);
752
- if (sidebar) {
753
- const data = new PushMenu(sidebar, Defaults);
754
- data.init();
755
- window.addEventListener('resize', () => {
756
- data.init();
757
- });
636
+ if (!sidebar) {
637
+ return;
758
638
  }
639
+ const sidebarBreakpointAttr = sidebar.dataset.sidebarBreakpoint;
640
+ const enablePersistenceAttr = sidebar.dataset.enablePersistence;
641
+ const config = {
642
+ sidebarBreakpoint: sidebarBreakpointAttr === undefined ?
643
+ Defaults.sidebarBreakpoint :
644
+ Number(sidebarBreakpointAttr),
645
+ enablePersistence: enablePersistenceAttr === undefined ?
646
+ Defaults.enablePersistence :
647
+ enablePersistenceAttr === 'true'
648
+ };
649
+ const pushMenu = new PushMenu(sidebar, config);
650
+ pushMenu.init();
651
+ window.addEventListener('resize', () => {
652
+ pushMenu.setupSidebarBreakPoint();
653
+ pushMenu.updateStateByResponsiveLogic();
654
+ });
759
655
  const sidebarOverlay = document.createElement('div');
760
656
  sidebarOverlay.className = CLASS_NAME_SIDEBAR_OVERLAY;
761
657
  document.querySelector(SELECTOR_APP_WRAPPER)?.append(sidebarOverlay);
762
658
  let overlayTouchMoved = false;
763
- // Handle touch events on overlay (area outside sidebar)
764
659
  sidebarOverlay.addEventListener('touchstart', () => {
765
660
  overlayTouchMoved = false;
766
661
  }, { passive: true });
@@ -770,17 +665,13 @@
770
665
  sidebarOverlay.addEventListener('touchend', event => {
771
666
  if (!overlayTouchMoved) {
772
667
  event.preventDefault();
773
- const target = event.currentTarget;
774
- const data = new PushMenu(target, Defaults);
775
- data.collapse();
668
+ pushMenu.collapse();
776
669
  }
777
670
  overlayTouchMoved = false;
778
671
  }, { passive: false });
779
672
  sidebarOverlay.addEventListener('click', event => {
780
673
  event.preventDefault();
781
- const target = event.currentTarget;
782
- const data = new PushMenu(target, Defaults);
783
- data.collapse();
674
+ pushMenu.collapse();
784
675
  });
785
676
  const fullBtn = document.querySelectorAll(SELECTOR_SIDEBAR_TOGGLE);
786
677
  fullBtn.forEach(btn => {
@@ -792,17 +683,12 @@
792
683
  }
793
684
  if (button) {
794
685
  event?.preventDefault();
795
- const data = new PushMenu(button, Defaults);
796
- data.toggle();
686
+ pushMenu.toggle();
797
687
  }
798
688
  });
799
689
  });
800
690
  });
801
691
 
802
- /**
803
- * AdminLTE Accessibility Module
804
- * WCAG 2.1 AA Compliance Features
805
- */
806
692
  class AccessibilityManager {
807
693
  config;
808
694
  liveRegion = null;
@@ -838,7 +724,6 @@
838
724
  this.initTableAccessibility();
839
725
  this.initFormAccessibility();
840
726
  }
841
- // WCAG 4.1.3: Status Messages
842
727
  createLiveRegion() {
843
728
  if (this.liveRegion)
844
729
  return;
@@ -850,7 +735,6 @@
850
735
  this.liveRegion.setAttribute('role', 'status');
851
736
  document.body.append(this.liveRegion);
852
737
  }
853
- // WCAG 2.4.1: Bypass Blocks
854
738
  addSkipLinks() {
855
739
  const skipLinksContainer = document.createElement('div');
856
740
  skipLinksContainer.className = 'skip-links';
@@ -865,7 +749,6 @@
865
749
  skipLinksContainer.append(skipToMain);
866
750
  skipLinksContainer.append(skipToNav);
867
751
  document.body.insertBefore(skipLinksContainer, document.body.firstChild);
868
- // Ensure targets exist and are focusable
869
752
  this.ensureSkipTargets();
870
753
  }
871
754
  ensureSkipTargets() {
@@ -884,7 +767,6 @@
884
767
  nav.setAttribute('tabindex', '-1');
885
768
  }
886
769
  }
887
- // WCAG 2.4.3: Focus Order & 2.4.7: Focus Visible
888
770
  initFocusManagement() {
889
771
  document.addEventListener('keydown', (event) => {
890
772
  if (event.key === 'Tab') {
@@ -894,7 +776,6 @@
894
776
  this.handleEscapeKey(event);
895
777
  }
896
778
  });
897
- // Focus management for modals and dropdowns
898
779
  this.initModalFocusManagement();
899
780
  this.initDropdownFocusManagement();
900
781
  }
@@ -902,14 +783,12 @@
902
783
  const focusableElements = this.getFocusableElements();
903
784
  const currentIndex = focusableElements.indexOf(document.activeElement);
904
785
  if (event.shiftKey) {
905
- // Shift+Tab (backward)
906
786
  if (currentIndex <= 0) {
907
787
  event.preventDefault();
908
788
  focusableElements.at(-1)?.focus();
909
789
  }
910
790
  }
911
791
  else if (currentIndex >= focusableElements.length - 1) {
912
- // Tab (forward)
913
792
  event.preventDefault();
914
793
  focusableElements[0]?.focus();
915
794
  }
@@ -927,30 +806,23 @@
927
806
  return Array.from(document.querySelectorAll(selector));
928
807
  }
929
808
  handleEscapeKey(event) {
930
- // Close modals, dropdowns, etc.
931
809
  const activeModal = document.querySelector('.modal.show');
932
- const activeDropdown = document.querySelector('.dropdown-menu.show');
933
810
  if (activeModal) {
934
- const closeButton = activeModal.querySelector('[data-bs-dismiss="modal"]');
935
- closeButton?.click();
936
- event.preventDefault();
811
+ return;
937
812
  }
938
- else if (activeDropdown) {
813
+ const activeDropdown = document.querySelector('.dropdown-menu.show');
814
+ if (activeDropdown) {
939
815
  const toggleButton = document.querySelector('[data-bs-toggle="dropdown"][aria-expanded="true"]');
940
816
  toggleButton?.click();
941
817
  event.preventDefault();
942
818
  }
943
819
  }
944
- // WCAG 2.1.1: Keyboard Access
945
820
  initKeyboardNavigation() {
946
- // Add keyboard support for custom components
947
821
  document.addEventListener('keydown', (event) => {
948
822
  const target = event.target;
949
- // Handle arrow key navigation for menus
950
823
  if (target.closest('.nav, .navbar-nav, .dropdown-menu')) {
951
824
  this.handleMenuNavigation(event);
952
825
  }
953
- // Handle Enter and Space for custom buttons
954
826
  if ((event.key === 'Enter' || event.key === ' ') && target.hasAttribute('role') && target.getAttribute('role') === 'button' && !target.matches('button, input[type="button"], input[type="submit"]')) {
955
827
  event.preventDefault();
956
828
  target.click();
@@ -991,14 +863,11 @@
991
863
  event.preventDefault();
992
864
  menuItems[nextIndex]?.focus();
993
865
  }
994
- // WCAG 2.3.3: Animation from Interactions
995
866
  respectReducedMotion() {
996
867
  const prefersReducedMotion = globalThis.matchMedia('(prefers-reduced-motion: reduce)').matches;
997
868
  if (prefersReducedMotion) {
998
869
  document.body.classList.add('reduce-motion');
999
- // Disable smooth scrolling
1000
870
  document.documentElement.style.scrollBehavior = 'auto';
1001
- // Reduce animation duration
1002
871
  const style = document.createElement('style');
1003
872
  style.textContent = `
1004
873
  *, *::before, *::after {
@@ -1010,18 +879,15 @@
1010
879
  document.head.append(style);
1011
880
  }
1012
881
  }
1013
- // WCAG 3.3.1: Error Identification
1014
882
  initErrorAnnouncements() {
1015
883
  const observer = new MutationObserver((mutations) => {
1016
884
  mutations.forEach((mutation) => {
1017
885
  mutation.addedNodes.forEach((node) => {
1018
886
  if (node.nodeType === Node.ELEMENT_NODE) {
1019
887
  const element = node;
1020
- // Check for error messages
1021
888
  if (element.matches('.alert-danger, .invalid-feedback, .error')) {
1022
889
  this.announce(element.textContent || 'Error occurred', 'assertive');
1023
890
  }
1024
- // Check for success messages
1025
891
  if (element.matches('.alert-success, .success')) {
1026
892
  this.announce(element.textContent || 'Success', 'polite');
1027
893
  }
@@ -1034,14 +900,11 @@
1034
900
  subtree: true
1035
901
  });
1036
902
  }
1037
- // WCAG 1.3.1: Info and Relationships
1038
903
  initTableAccessibility() {
1039
904
  document.querySelectorAll('table').forEach((table) => {
1040
- // Add table role if missing
1041
905
  if (!table.hasAttribute('role')) {
1042
906
  table.setAttribute('role', 'table');
1043
907
  }
1044
- // Ensure headers have proper scope
1045
908
  table.querySelectorAll('th').forEach((th) => {
1046
909
  if (!th.hasAttribute('scope')) {
1047
910
  const isInThead = th.closest('thead');
@@ -1054,7 +917,6 @@
1054
917
  }
1055
918
  }
1056
919
  });
1057
- // Add caption if missing but title exists
1058
920
  if (!table.querySelector('caption') && table.hasAttribute('title')) {
1059
921
  const caption = document.createElement('caption');
1060
922
  caption.textContent = table.getAttribute('title') || '';
@@ -1062,18 +924,15 @@
1062
924
  }
1063
925
  });
1064
926
  }
1065
- // WCAG 3.3.2: Labels or Instructions
1066
927
  initFormAccessibility() {
1067
928
  document.querySelectorAll('input, select, textarea').forEach((input) => {
1068
929
  const htmlInput = input;
1069
- // Ensure all inputs have labels
1070
930
  if (!htmlInput.labels?.length && !htmlInput.hasAttribute('aria-label') && !htmlInput.hasAttribute('aria-labelledby')) {
1071
931
  const placeholder = htmlInput.getAttribute('placeholder');
1072
932
  if (placeholder) {
1073
933
  htmlInput.setAttribute('aria-label', placeholder);
1074
934
  }
1075
935
  }
1076
- // Add required indicators
1077
936
  if (htmlInput.hasAttribute('required')) {
1078
937
  const label = htmlInput.labels?.[0];
1079
938
  if (label && !label.querySelector('.required-indicator')) {
@@ -1083,8 +942,6 @@
1083
942
  label.append(indicator);
1084
943
  }
1085
944
  }
1086
- // Handle invalid state unless the element explicitly opts out via the
1087
- // 'disable-adminlte-validations' class.
1088
945
  if (!htmlInput.classList.contains('disable-adminlte-validations')) {
1089
946
  htmlInput.addEventListener('invalid', () => {
1090
947
  this.handleFormError(htmlInput);
@@ -1100,10 +957,6 @@
1100
957
  errorElement.id = errorId;
1101
958
  errorElement.className = 'invalid-feedback';
1102
959
  errorElement.setAttribute('role', 'alert');
1103
- // Always append the error element as the last child of the parent.
1104
- // This prevents breaking layouts where inputs use Bootstrap's
1105
- // `.input-group-text` decorators, ensuring the error stays below
1106
- // the entire input group.
1107
960
  input.parentNode?.append(errorElement);
1108
961
  }
1109
962
  errorElement.textContent = input.validationMessage;
@@ -1111,7 +964,6 @@
1111
964
  input.classList.add('is-invalid');
1112
965
  this.announce(`Error in ${input.labels?.[0]?.textContent || input.name}: ${input.validationMessage}`, 'assertive');
1113
966
  }
1114
- // Modal focus management
1115
967
  initModalFocusManagement() {
1116
968
  document.addEventListener('shown.bs.modal', (event) => {
1117
969
  const modal = event.target;
@@ -1119,18 +971,15 @@
1119
971
  if (focusableElements.length > 0) {
1120
972
  focusableElements[0].focus();
1121
973
  }
1122
- // Store previous focus
1123
974
  this.focusHistory.push(document.activeElement);
1124
975
  });
1125
976
  document.addEventListener('hidden.bs.modal', () => {
1126
- // Restore previous focus
1127
977
  const previousElement = this.focusHistory.pop();
1128
978
  if (previousElement) {
1129
979
  previousElement.focus();
1130
980
  }
1131
981
  });
1132
982
  }
1133
- // Dropdown focus management
1134
983
  initDropdownFocusManagement() {
1135
984
  document.addEventListener('shown.bs.dropdown', (event) => {
1136
985
  const dropdown = event.target;
@@ -1141,7 +990,6 @@
1141
990
  }
1142
991
  });
1143
992
  }
1144
- // Public API methods
1145
993
  announce(message, priority = 'polite') {
1146
994
  if (!this.liveRegion) {
1147
995
  this.createLiveRegion();
@@ -1149,7 +997,6 @@
1149
997
  if (this.liveRegion) {
1150
998
  this.liveRegion.setAttribute('aria-live', priority);
1151
999
  this.liveRegion.textContent = message;
1152
- // Clear after announcement
1153
1000
  setTimeout(() => {
1154
1001
  if (this.liveRegion) {
1155
1002
  this.liveRegion.textContent = '';
@@ -1161,7 +1008,6 @@
1161
1008
  const element = document.querySelector(selector);
1162
1009
  if (element) {
1163
1010
  element.focus();
1164
- // Ensure element is visible
1165
1011
  element.scrollIntoView({ behavior: 'smooth', block: 'center' });
1166
1012
  }
1167
1013
  }
@@ -1186,7 +1032,6 @@
1186
1032
  });
1187
1033
  }
1188
1034
  addLandmarks() {
1189
- // Add main landmark if missing
1190
1035
  const main = document.querySelector('main');
1191
1036
  if (!main) {
1192
1037
  const appMain = document.querySelector('.app-main');
@@ -1195,7 +1040,6 @@
1195
1040
  appMain.id = 'main';
1196
1041
  }
1197
1042
  }
1198
- // Add navigation landmarks
1199
1043
  document.querySelectorAll('.navbar-nav, .nav').forEach((nav, index) => {
1200
1044
  if (!nav.hasAttribute('role')) {
1201
1045
  nav.setAttribute('role', 'navigation');
@@ -1204,35 +1048,17 @@
1204
1048
  nav.setAttribute('aria-label', `Navigation ${index + 1}`);
1205
1049
  }
1206
1050
  });
1207
- // Add search landmark
1208
1051
  const searchForm = document.querySelector('form[role="search"], .navbar-search');
1209
1052
  if (searchForm && !searchForm.hasAttribute('role')) {
1210
1053
  searchForm.setAttribute('role', 'search');
1211
1054
  }
1212
1055
  }
1213
1056
  }
1214
- // Initialize accessibility when DOM is ready
1215
1057
  const initAccessibility = (config) => {
1216
1058
  return new AccessibilityManager(config);
1217
1059
  };
1218
1060
 
1219
- /**
1220
- * AdminLTE v4.0.0-rc5
1221
- * Author: Colorlib
1222
- * Website: AdminLTE.io <https://adminlte.io>
1223
- * License: Open source - MIT <https://opensource.org/licenses/MIT>
1224
- */
1225
1061
  onDOMContentLoaded(() => {
1226
- /**
1227
- * Initialize AdminLTE Core Components
1228
- * -------------------------------
1229
- */
1230
- const layout = new Layout(document.body);
1231
- layout.holdTransition();
1232
- /**
1233
- * Initialize Accessibility Features - WCAG 2.1 AA Compliance
1234
- * --------------------------------------------------------
1235
- */
1236
1062
  const accessibilityManager = initAccessibility({
1237
1063
  announcements: true,
1238
1064
  skipLinks: true,
@@ -1240,12 +1066,7 @@
1240
1066
  keyboardNavigation: true,
1241
1067
  reducedMotion: true
1242
1068
  });
1243
- // Add semantic landmarks
1244
1069
  accessibilityManager.addLandmarks();
1245
- // Mark app as loaded after initialization
1246
- setTimeout(() => {
1247
- document.body.classList.add('app-loaded');
1248
- }, 400);
1249
1070
  });
1250
1071
 
1251
1072
  exports.CardWidget = CardWidget;