admin-lte 4.0.0-beta3 → 4.0.0-rc2

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 (74) hide show
  1. package/.browserslistrc +9 -4
  2. package/ACCESSIBILITY-COMPLIANCE.md +369 -0
  3. package/CHANGELOG.md +314 -0
  4. package/README.md +43 -7
  5. package/dist/css/adminlte.css +1412 -1264
  6. package/dist/css/adminlte.css.map +1 -1
  7. package/dist/css/adminlte.min.css +1 -1
  8. package/dist/css/adminlte.min.css.map +1 -1
  9. package/dist/css/adminlte.rtl.css +1430 -1256
  10. package/dist/css/adminlte.rtl.css.map +1 -1
  11. package/dist/css/adminlte.rtl.min.css +1 -1
  12. package/dist/css/adminlte.rtl.min.css.map +1 -1
  13. package/dist/js/adminlte.js +786 -345
  14. package/dist/js/adminlte.js.map +1 -1
  15. package/dist/js/adminlte.min.js +3 -3
  16. package/dist/js/adminlte.min.js.map +1 -1
  17. package/dist/pages/UI/general.html +41 -9
  18. package/dist/pages/UI/icons.html +38 -6
  19. package/dist/pages/UI/timeline.html +38 -6
  20. package/dist/pages/docs/browser-support.html +39 -25
  21. package/dist/pages/docs/color-mode.html +44 -31
  22. package/dist/pages/docs/components/main-header.html +39 -25
  23. package/dist/pages/docs/components/main-sidebar.html +40 -27
  24. package/dist/pages/docs/faq.html +38 -23
  25. package/dist/pages/docs/how-to-contribute.html +39 -25
  26. package/dist/pages/docs/introduction.html +44 -35
  27. package/dist/pages/docs/javascript/treeview.html +39 -25
  28. package/dist/pages/docs/layout.html +38 -6
  29. package/dist/pages/docs/license.html +38 -23
  30. package/dist/pages/examples/lockscreen.html +17 -5
  31. package/dist/pages/examples/login-v2.html +16 -4
  32. package/dist/pages/examples/login.html +16 -4
  33. package/dist/pages/examples/register-v2.html +16 -4
  34. package/dist/pages/examples/register.html +16 -4
  35. package/dist/pages/forms/general.html +38 -6
  36. package/dist/pages/generate/theme.html +155 -5
  37. package/dist/pages/index.html +42 -27
  38. package/dist/pages/index2.html +39 -7
  39. package/dist/pages/index3.html +38 -6
  40. package/dist/pages/layout/collapsed-sidebar.html +38 -6
  41. package/dist/pages/layout/fixed-complete.html +862 -0
  42. package/dist/pages/layout/fixed-footer.html +861 -0
  43. package/dist/pages/layout/fixed-header.html +861 -0
  44. package/dist/pages/layout/fixed-sidebar.html +38 -6
  45. package/dist/pages/layout/layout-custom-area.html +38 -6
  46. package/dist/pages/layout/layout-rtl.html +38 -6
  47. package/dist/pages/layout/logo-switch.html +17 -5
  48. package/dist/pages/layout/sidebar-mini.html +38 -6
  49. package/dist/pages/layout/unfixed-sidebar.html +38 -6
  50. package/dist/pages/tables/simple.html +38 -6
  51. package/dist/pages/widgets/cards.html +38 -6
  52. package/dist/pages/widgets/info-box.html +38 -6
  53. package/dist/pages/widgets/small-box.html +38 -6
  54. package/eslint.config.js +81 -0
  55. package/package.json +34 -31
  56. package/src/scss/_accessibility.scss +292 -0
  57. package/src/scss/_app-footer.scss +8 -0
  58. package/src/scss/_app-header.scss +8 -0
  59. package/src/scss/_table.scss +1 -9
  60. package/src/scss/_variables.scss +2 -1
  61. package/src/scss/adminlte.scss +3 -0
  62. package/src/ts/accessibility.ts +533 -0
  63. package/src/ts/adminlte.ts +48 -9
  64. package/src/ts/card-widget.ts +1 -2
  65. package/src/ts/push-menu.ts +1 -2
  66. package/src/ts/util/index.ts +39 -7
  67. package/.astro/content-assets.mjs +0 -1
  68. package/.astro/content-modules.mjs +0 -1
  69. package/.astro/content.d.ts +0 -166
  70. package/.astro/data-store.json +0 -1
  71. package/.astro/settings.json +0 -5
  72. package/.astro/types.d.ts +0 -2
  73. package/adminlte-v4.0.0-beta1-with-dist.zip +0 -0
  74. package/adminlte-v4.0.0-beta3.zip +0 -0
@@ -0,0 +1,533 @@
1
+ /**
2
+ * AdminLTE Accessibility Module
3
+ * WCAG 2.1 AA Compliance Features
4
+ */
5
+
6
+ export interface AccessibilityConfig {
7
+ announcements: boolean
8
+ skipLinks: boolean
9
+ focusManagement: boolean
10
+ keyboardNavigation: boolean
11
+ reducedMotion: boolean
12
+ }
13
+
14
+ export class AccessibilityManager {
15
+ private config: AccessibilityConfig
16
+ private liveRegion: HTMLElement | null = null
17
+ private focusHistory: HTMLElement[] = []
18
+
19
+ constructor(config: Partial<AccessibilityConfig> = {}) {
20
+ this.config = {
21
+ announcements: true,
22
+ skipLinks: true,
23
+ focusManagement: true,
24
+ keyboardNavigation: true,
25
+ reducedMotion: true,
26
+ ...config
27
+ }
28
+
29
+ this.init()
30
+ }
31
+
32
+ private init(): void {
33
+ if (this.config.announcements) {
34
+ this.createLiveRegion()
35
+ }
36
+
37
+ if (this.config.skipLinks) {
38
+ this.addSkipLinks()
39
+ }
40
+
41
+ if (this.config.focusManagement) {
42
+ this.initFocusManagement()
43
+ }
44
+
45
+ if (this.config.keyboardNavigation) {
46
+ this.initKeyboardNavigation()
47
+ }
48
+
49
+ if (this.config.reducedMotion) {
50
+ this.respectReducedMotion()
51
+ }
52
+
53
+ this.initErrorAnnouncements()
54
+ this.initTableAccessibility()
55
+ this.initFormAccessibility()
56
+ }
57
+
58
+ // WCAG 4.1.3: Status Messages
59
+ private createLiveRegion(): void {
60
+ if (this.liveRegion) return
61
+
62
+ this.liveRegion = document.createElement('div')
63
+ this.liveRegion.id = 'live-region'
64
+ this.liveRegion.className = 'live-region'
65
+ this.liveRegion.setAttribute('aria-live', 'polite')
66
+ this.liveRegion.setAttribute('aria-atomic', 'true')
67
+ this.liveRegion.setAttribute('role', 'status')
68
+
69
+ document.body.append(this.liveRegion)
70
+ }
71
+
72
+ // WCAG 2.4.1: Bypass Blocks
73
+ private addSkipLinks(): void {
74
+ const skipLinksContainer = document.createElement('div')
75
+ skipLinksContainer.className = 'skip-links'
76
+
77
+ const skipToMain = document.createElement('a')
78
+ skipToMain.href = '#main'
79
+ skipToMain.className = 'skip-link'
80
+ skipToMain.textContent = 'Skip to main content'
81
+
82
+ const skipToNav = document.createElement('a')
83
+ skipToNav.href = '#navigation'
84
+ skipToNav.className = 'skip-link'
85
+ skipToNav.textContent = 'Skip to navigation'
86
+
87
+ skipLinksContainer.append(skipToMain)
88
+ skipLinksContainer.append(skipToNav)
89
+
90
+ document.body.insertBefore(skipLinksContainer, document.body.firstChild)
91
+
92
+ // Ensure targets exist and are focusable
93
+ this.ensureSkipTargets()
94
+ }
95
+
96
+ private ensureSkipTargets(): void {
97
+ const main = document.querySelector('#main, main, [role="main"]')
98
+ if (main && !main.id) {
99
+ main.id = 'main'
100
+ }
101
+ if (main && !main.hasAttribute('tabindex')) {
102
+ main.setAttribute('tabindex', '-1')
103
+ }
104
+
105
+ const nav = document.querySelector('#navigation, nav, [role="navigation"]')
106
+ if (nav && !nav.id) {
107
+ nav.id = 'navigation'
108
+ }
109
+ if (nav && !nav.hasAttribute('tabindex')) {
110
+ nav.setAttribute('tabindex', '-1')
111
+ }
112
+ }
113
+
114
+ // WCAG 2.4.3: Focus Order & 2.4.7: Focus Visible
115
+ private initFocusManagement(): void {
116
+ document.addEventListener('keydown', (event) => {
117
+ if (event.key === 'Tab') {
118
+ this.handleTabNavigation(event)
119
+ }
120
+ if (event.key === 'Escape') {
121
+ this.handleEscapeKey(event)
122
+ }
123
+ })
124
+
125
+ // Focus management for modals and dropdowns
126
+ this.initModalFocusManagement()
127
+ this.initDropdownFocusManagement()
128
+ }
129
+
130
+ private handleTabNavigation(event: KeyboardEvent): void {
131
+ const focusableElements = this.getFocusableElements()
132
+ const currentIndex = focusableElements.indexOf(document.activeElement as HTMLElement)
133
+
134
+ if (event.shiftKey) {
135
+ // Shift+Tab (backward)
136
+ if (currentIndex <= 0) {
137
+ event.preventDefault()
138
+ focusableElements.at(-1)?.focus()
139
+ }
140
+ } else if (currentIndex >= focusableElements.length - 1) {
141
+ // Tab (forward)
142
+ event.preventDefault()
143
+ focusableElements[0]?.focus()
144
+ }
145
+ }
146
+
147
+ private getFocusableElements(): HTMLElement[] {
148
+ const selector = [
149
+ 'a[href]',
150
+ 'button:not([disabled])',
151
+ 'input:not([disabled])',
152
+ 'select:not([disabled])',
153
+ 'textarea:not([disabled])',
154
+ '[tabindex]:not([tabindex="-1"])',
155
+ '[contenteditable="true"]'
156
+ ].join(', ')
157
+
158
+ return Array.from(document.querySelectorAll(selector)) as HTMLElement[]
159
+ }
160
+
161
+ private handleEscapeKey(event: KeyboardEvent): void {
162
+ // Close modals, dropdowns, etc.
163
+ const activeModal = document.querySelector('.modal.show')
164
+ const activeDropdown = document.querySelector('.dropdown-menu.show')
165
+
166
+ if (activeModal) {
167
+ const closeButton = activeModal.querySelector('[data-bs-dismiss="modal"]') as HTMLElement
168
+ closeButton?.click()
169
+ event.preventDefault()
170
+ } else if (activeDropdown) {
171
+ const toggleButton = document.querySelector('[data-bs-toggle="dropdown"][aria-expanded="true"]') as HTMLElement
172
+ toggleButton?.click()
173
+ event.preventDefault()
174
+ }
175
+ }
176
+
177
+ // WCAG 2.1.1: Keyboard Access
178
+ private initKeyboardNavigation(): void {
179
+ // Add keyboard support for custom components
180
+ document.addEventListener('keydown', (event) => {
181
+ const target = event.target as HTMLElement
182
+
183
+ // Handle arrow key navigation for menus
184
+ if (target.closest('.nav, .navbar-nav, .dropdown-menu')) {
185
+ this.handleMenuNavigation(event)
186
+ }
187
+
188
+ // Handle Enter and Space for custom buttons
189
+ if ((event.key === 'Enter' || event.key === ' ') && target.hasAttribute('role') && target.getAttribute('role') === 'button' && !target.matches('button, input[type="button"], input[type="submit"]')) {
190
+ event.preventDefault()
191
+ target.click()
192
+ }
193
+ })
194
+ }
195
+
196
+ private handleMenuNavigation(event: KeyboardEvent): void {
197
+ if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) {
198
+ return
199
+ }
200
+
201
+ const currentElement = event.target as HTMLElement
202
+ const menuItems = Array.from(currentElement.closest('.nav, .navbar-nav, .dropdown-menu')?.querySelectorAll('a, button') || []) as HTMLElement[]
203
+ const currentIndex = menuItems.indexOf(currentElement)
204
+
205
+ let nextIndex: number
206
+
207
+ switch (event.key) {
208
+ case 'ArrowDown':
209
+ case 'ArrowRight': {
210
+ nextIndex = currentIndex < menuItems.length - 1 ? currentIndex + 1 : 0
211
+ break
212
+ }
213
+ case 'ArrowUp':
214
+ case 'ArrowLeft': {
215
+ nextIndex = currentIndex > 0 ? currentIndex - 1 : menuItems.length - 1
216
+ break
217
+ }
218
+ case 'Home': {
219
+ nextIndex = 0
220
+ break
221
+ }
222
+ case 'End': {
223
+ nextIndex = menuItems.length - 1
224
+ break
225
+ }
226
+ default: {
227
+ return
228
+ }
229
+ }
230
+
231
+ event.preventDefault()
232
+ menuItems[nextIndex]?.focus()
233
+ }
234
+
235
+ // WCAG 2.3.3: Animation from Interactions
236
+ private respectReducedMotion(): void {
237
+ const prefersReducedMotion = globalThis.matchMedia('(prefers-reduced-motion: reduce)').matches
238
+
239
+ if (prefersReducedMotion) {
240
+ document.body.classList.add('reduce-motion')
241
+
242
+ // Disable smooth scrolling
243
+ document.documentElement.style.scrollBehavior = 'auto'
244
+
245
+ // Reduce animation duration
246
+ const style = document.createElement('style')
247
+ style.textContent = `
248
+ *, *::before, *::after {
249
+ animation-duration: 0.01ms !important;
250
+ animation-iteration-count: 1 !important;
251
+ transition-duration: 0.01ms !important;
252
+ }
253
+ `
254
+ document.head.append(style)
255
+ }
256
+ }
257
+
258
+ // WCAG 3.3.1: Error Identification
259
+ private initErrorAnnouncements(): void {
260
+ const observer = new MutationObserver((mutations) => {
261
+ mutations.forEach((mutation) => {
262
+ mutation.addedNodes.forEach((node) => {
263
+ if (node.nodeType === Node.ELEMENT_NODE) {
264
+ const element = node as Element
265
+
266
+ // Check for error messages
267
+ if (element.matches('.alert-danger, .invalid-feedback, .error')) {
268
+ this.announce(element.textContent || 'Error occurred', 'assertive')
269
+ }
270
+
271
+ // Check for success messages
272
+ if (element.matches('.alert-success, .success')) {
273
+ this.announce(element.textContent || 'Success', 'polite')
274
+ }
275
+ }
276
+ })
277
+ })
278
+ })
279
+
280
+ observer.observe(document.body, {
281
+ childList: true,
282
+ subtree: true
283
+ })
284
+ }
285
+
286
+ // WCAG 1.3.1: Info and Relationships
287
+ private initTableAccessibility(): void {
288
+ document.querySelectorAll('table').forEach((table) => {
289
+ // Add table role if missing
290
+ if (!table.hasAttribute('role')) {
291
+ table.setAttribute('role', 'table')
292
+ }
293
+
294
+ // Ensure headers have proper scope
295
+ table.querySelectorAll('th').forEach((th) => {
296
+ if (!th.hasAttribute('scope')) {
297
+ const isInThead = th.closest('thead')
298
+ const isFirstColumn = th.cellIndex === 0
299
+
300
+ if (isInThead) {
301
+ th.setAttribute('scope', 'col')
302
+ } else if (isFirstColumn) {
303
+ th.setAttribute('scope', 'row')
304
+ }
305
+ }
306
+ })
307
+
308
+ // Add caption if missing but title exists
309
+ if (!table.querySelector('caption') && table.hasAttribute('title')) {
310
+ const caption = document.createElement('caption')
311
+ caption.textContent = table.getAttribute('title') || ''
312
+ table.insertBefore(caption, table.firstChild)
313
+ }
314
+ })
315
+ }
316
+
317
+ // WCAG 3.3.2: Labels or Instructions
318
+ private initFormAccessibility(): void {
319
+ document.querySelectorAll('input, select, textarea').forEach((input) => {
320
+ const htmlInput = input as HTMLInputElement
321
+
322
+ // Ensure all inputs have labels
323
+ if (!htmlInput.labels?.length && !htmlInput.hasAttribute('aria-label') && !htmlInput.hasAttribute('aria-labelledby')) {
324
+ const placeholder = htmlInput.getAttribute('placeholder')
325
+ if (placeholder) {
326
+ htmlInput.setAttribute('aria-label', placeholder)
327
+ }
328
+ }
329
+
330
+ // Add required indicators
331
+ if (htmlInput.hasAttribute('required')) {
332
+ const label = htmlInput.labels?.[0]
333
+ if (label && !label.querySelector('.required-indicator')) {
334
+ const indicator = document.createElement('span')
335
+ indicator.className = 'required-indicator sr-only'
336
+ indicator.textContent = ' (required)'
337
+ label.append(indicator)
338
+ }
339
+ }
340
+
341
+ // Handle invalid states
342
+ htmlInput.addEventListener('invalid', () => {
343
+ this.handleFormError(htmlInput)
344
+ })
345
+ })
346
+ }
347
+
348
+ private handleFormError(input: HTMLInputElement): void {
349
+ const errorId = `${input.id || input.name}-error`
350
+ let errorElement = document.getElementById(errorId)
351
+
352
+ if (!errorElement) {
353
+ errorElement = document.createElement('div')
354
+ errorElement.id = errorId
355
+ errorElement.className = 'invalid-feedback'
356
+ errorElement.setAttribute('role', 'alert')
357
+ input.parentNode?.insertBefore(errorElement, input.nextSibling)
358
+ }
359
+
360
+ errorElement.textContent = input.validationMessage
361
+ input.setAttribute('aria-describedby', errorId)
362
+ input.classList.add('is-invalid')
363
+
364
+ this.announce(`Error in ${input.labels?.[0]?.textContent || input.name}: ${input.validationMessage}`, 'assertive')
365
+ }
366
+
367
+ // Modal focus management
368
+ private initModalFocusManagement(): void {
369
+ document.addEventListener('shown.bs.modal', (event) => {
370
+ const modal = event.target as HTMLElement
371
+ const focusableElements = modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')
372
+
373
+ if (focusableElements.length > 0) {
374
+ (focusableElements[0] as HTMLElement).focus()
375
+ }
376
+
377
+ // Store previous focus
378
+ this.focusHistory.push(document.activeElement as HTMLElement)
379
+ })
380
+
381
+ document.addEventListener('hidden.bs.modal', () => {
382
+ // Restore previous focus
383
+ const previousElement = this.focusHistory.pop()
384
+ if (previousElement) {
385
+ previousElement.focus()
386
+ }
387
+ })
388
+ }
389
+
390
+ // Dropdown focus management
391
+ private initDropdownFocusManagement(): void {
392
+ document.addEventListener('shown.bs.dropdown', (event) => {
393
+ const dropdown = event.target as HTMLElement
394
+ const menu = dropdown.querySelector('.dropdown-menu')
395
+ const firstItem = menu?.querySelector('a, button') as HTMLElement
396
+
397
+ if (firstItem) {
398
+ firstItem.focus()
399
+ }
400
+ })
401
+ }
402
+
403
+ // Public API methods
404
+ public announce(message: string, priority: 'polite' | 'assertive' = 'polite'): void {
405
+ if (!this.liveRegion) {
406
+ this.createLiveRegion()
407
+ }
408
+
409
+ if (this.liveRegion) {
410
+ this.liveRegion.setAttribute('aria-live', priority)
411
+ this.liveRegion.textContent = message
412
+
413
+ // Clear after announcement
414
+ setTimeout(() => {
415
+ if (this.liveRegion) {
416
+ this.liveRegion.textContent = ''
417
+ }
418
+ }, 1000)
419
+ }
420
+ }
421
+
422
+ public focusElement(selector: string): void {
423
+ const element = document.querySelector(selector) as HTMLElement
424
+ if (element) {
425
+ element.focus()
426
+
427
+ // Ensure element is visible
428
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' })
429
+ }
430
+ }
431
+
432
+ public trapFocus(container: HTMLElement): void {
433
+ const focusableElements = container.querySelectorAll(
434
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
435
+ ) as NodeListOf<HTMLElement>
436
+
437
+ const focusableArray = Array.from(focusableElements)
438
+ const firstElement = focusableArray[0]
439
+ const lastElement = focusableArray.at(-1)
440
+
441
+ container.addEventListener('keydown', (event) => {
442
+ if (event.key === 'Tab') {
443
+ if (event.shiftKey) {
444
+ if (document.activeElement === firstElement) {
445
+ lastElement?.focus()
446
+ event.preventDefault()
447
+ }
448
+ } else if (document.activeElement === lastElement) {
449
+ firstElement.focus()
450
+ event.preventDefault()
451
+ }
452
+ }
453
+ })
454
+ }
455
+
456
+ public addLandmarks(): void {
457
+ // Add main landmark if missing
458
+ const main = document.querySelector('main')
459
+ if (!main) {
460
+ const appMain = document.querySelector('.app-main')
461
+ if (appMain) {
462
+ appMain.setAttribute('role', 'main')
463
+ appMain.id = 'main'
464
+ }
465
+ }
466
+
467
+ // Add navigation landmarks
468
+ document.querySelectorAll('.navbar-nav, .nav').forEach((nav, index) => {
469
+ if (!nav.hasAttribute('role')) {
470
+ nav.setAttribute('role', 'navigation')
471
+ }
472
+ if (!nav.hasAttribute('aria-label')) {
473
+ nav.setAttribute('aria-label', `Navigation ${index + 1}`)
474
+ }
475
+ })
476
+
477
+ // Add search landmark
478
+ const searchForm = document.querySelector('form[role="search"], .navbar-search')
479
+ if (searchForm && !searchForm.hasAttribute('role')) {
480
+ searchForm.setAttribute('role', 'search')
481
+ }
482
+ }
483
+ }
484
+
485
+ // Initialize accessibility when DOM is ready
486
+ export const initAccessibility = (config?: Partial<AccessibilityConfig>): AccessibilityManager => {
487
+ return new AccessibilityManager(config)
488
+ }
489
+
490
+ // Utility function for luminance calculation
491
+ const getLuminance = (color: string): number => {
492
+ const rgb = color.match(/\d+/g)?.map(Number) || [0, 0, 0]
493
+ const [r, g, b] = rgb.map(c => {
494
+ c = c / 255
495
+ return c <= 0.039_28 ? c / 12.92 : (c + 0.055) ** 2.4 / (1.055 ** 2.4)
496
+ })
497
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b
498
+ }
499
+
500
+ // Export utility functions
501
+ export const accessibilityUtils = {
502
+ // WCAG 1.4.3: Contrast checking utility
503
+ checkColorContrast: (foreground: string, background: string): { ratio: number; passes: boolean } => {
504
+ const l1 = getLuminance(foreground)
505
+ const l2 = getLuminance(background)
506
+ const ratio = (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05)
507
+
508
+ return {
509
+ ratio: Math.round(ratio * 100) / 100,
510
+ passes: ratio >= 4.5
511
+ }
512
+ },
513
+
514
+ // Generate unique IDs for accessibility
515
+ generateId: (prefix: string = 'a11y'): string => {
516
+ return `${prefix}-${Math.random().toString(36).slice(2, 11)}`
517
+ },
518
+
519
+ // Check if element is focusable
520
+ isFocusable: (element: HTMLElement): boolean => {
521
+ const focusableSelectors = [
522
+ 'a[href]',
523
+ 'button:not([disabled])',
524
+ 'input:not([disabled])',
525
+ 'select:not([disabled])',
526
+ 'textarea:not([disabled])',
527
+ '[tabindex]:not([tabindex="-1"])',
528
+ '[contenteditable="true"]'
529
+ ]
530
+
531
+ return focusableSelectors.some(selector => element.matches(selector))
532
+ }
533
+ }
@@ -1,15 +1,54 @@
1
- import Layout from './layout'
2
- import PushMenu from './push-menu'
3
- import Treeview from './treeview'
4
- import DirectChat from './direct-chat'
5
- import CardWidget from './card-widget'
6
- import FullScreen from './fullscreen'
1
+ import { onDOMContentLoaded } from './util/index.js'
2
+ import Layout from './layout.js'
3
+ import CardWidget from './card-widget.js'
4
+ import Treeview from './treeview.js'
5
+ import DirectChat from './direct-chat.js'
6
+ import FullScreen from './fullscreen.js'
7
+ import PushMenu from './push-menu.js'
8
+ import { initAccessibility } from './accessibility.js'
9
+
10
+ /**
11
+ * AdminLTE v4.0.0-rc1
12
+ * Author: Colorlib
13
+ * Website: AdminLTE.io <https://adminlte.io>
14
+ * License: Open source - MIT <https://opensource.org/licenses/MIT>
15
+ */
16
+
17
+ onDOMContentLoaded(() => {
18
+ /**
19
+ * Initialize AdminLTE Core Components
20
+ * -------------------------------
21
+ */
22
+ const layout = new Layout(document.body)
23
+ layout.holdTransition()
24
+
25
+ /**
26
+ * Initialize Accessibility Features - WCAG 2.1 AA Compliance
27
+ * --------------------------------------------------------
28
+ */
29
+ const accessibilityManager = initAccessibility({
30
+ announcements: true,
31
+ skipLinks: true,
32
+ focusManagement: true,
33
+ keyboardNavigation: true,
34
+ reducedMotion: true
35
+ })
36
+
37
+ // Add semantic landmarks
38
+ accessibilityManager.addLandmarks()
39
+
40
+ // Mark app as loaded after initialization
41
+ setTimeout(() => {
42
+ document.body.classList.add('app-loaded')
43
+ }, 400)
44
+ })
7
45
 
8
46
  export {
9
47
  Layout,
10
- PushMenu,
48
+ CardWidget,
11
49
  Treeview,
12
50
  DirectChat,
13
- CardWidget,
14
- FullScreen
51
+ FullScreen,
52
+ PushMenu,
53
+ initAccessibility
15
54
  }
@@ -111,8 +111,7 @@ class CardWidget {
111
111
 
112
112
  setTimeout(() => {
113
113
  if (this._parent) {
114
- this._parent.classList.remove(CLASS_NAME_COLLAPSED)
115
- this._parent.classList.remove(CLASS_NAME_EXPANDING)
114
+ this._parent.classList.remove(CLASS_NAME_COLLAPSED, CLASS_NAME_EXPANDING)
116
115
  }
117
116
  }, this._config.animationSpeed)
118
117
  }
@@ -59,7 +59,6 @@ class PushMenu {
59
59
  this._config = { ...Defaults, ...config }
60
60
  }
61
61
 
62
- // TODO
63
62
  menusClose() {
64
63
  const navTreeview = document.querySelectorAll<HTMLElement>(SELECTOR_NAV_TREEVIEW)
65
64
 
@@ -100,7 +99,7 @@ class PushMenu {
100
99
  const sidebarExpandList = document.querySelector(SELECTOR_SIDEBAR_EXPAND)?.classList ?? []
101
100
  const sidebarExpand = Array.from(sidebarExpandList).find(className => className.startsWith(CLASS_NAME_SIDEBAR_EXPAND)) ?? ''
102
101
  const sidebar = document.getElementsByClassName(sidebarExpand)[0]
103
- const sidebarContent = window.getComputedStyle(sidebar, '::before').getPropertyValue('content')
102
+ const sidebarContent = globalThis.getComputedStyle(sidebar, '::before').getPropertyValue('content')
104
103
  this._config = { ...this._config, sidebarBreakpoint: Number(sidebarContent.replace(/[^\d.-]/g, '')) }
105
104
 
106
105
  if (window.innerWidth <= this._config.sidebarBreakpoint) {