admin-lte 4.0.4 → 4.2.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 (59) hide show
  1. package/CHANGELOG.md +41 -1
  2. package/README.md +6 -6
  3. package/dist/css/adminlte-docs.css +878 -0
  4. package/dist/css/adminlte-docs.css.map +1 -0
  5. package/dist/css/adminlte-docs.min.css +7 -0
  6. package/dist/css/adminlte-docs.min.css.map +1 -0
  7. package/dist/css/adminlte-docs.rtl.css +877 -0
  8. package/dist/css/adminlte-docs.rtl.css.map +1 -0
  9. package/dist/css/adminlte-docs.rtl.min.css +7 -0
  10. package/dist/css/adminlte-docs.rtl.min.css.map +1 -0
  11. package/dist/css/adminlte.css +132 -912
  12. package/dist/css/adminlte.css.map +1 -1
  13. package/dist/css/adminlte.min.css +2 -2
  14. package/dist/css/adminlte.min.css.map +1 -1
  15. package/dist/css/adminlte.rtl.css +132 -912
  16. package/dist/css/adminlte.rtl.css.map +1 -1
  17. package/dist/css/adminlte.rtl.min.css +2 -2
  18. package/dist/css/adminlte.rtl.min.css.map +1 -1
  19. package/dist/js/adminlte.esm.js +1406 -0
  20. package/dist/js/adminlte.esm.js.map +1 -0
  21. package/dist/js/adminlte.esm.min.js +7 -0
  22. package/dist/js/adminlte.esm.min.js.map +1 -0
  23. package/dist/js/adminlte.js +449 -215
  24. package/dist/js/adminlte.js.map +1 -1
  25. package/dist/js/adminlte.min.js +2 -2
  26. package/dist/js/adminlte.min.js.map +1 -1
  27. package/dist/js/types/accessibility.d.ts +46 -0
  28. package/dist/js/types/adminlte.d.ts +10 -0
  29. package/dist/js/types/base-component.d.ts +60 -0
  30. package/dist/js/types/card-widget.d.ts +30 -0
  31. package/dist/js/types/color-mode.d.ts +74 -0
  32. package/dist/js/types/direct-chat.d.ts +19 -0
  33. package/dist/js/types/fullscreen.d.ts +21 -0
  34. package/dist/js/types/layout.d.ts +19 -0
  35. package/dist/js/types/push-menu.d.ts +123 -0
  36. package/dist/js/types/treeview.d.ts +27 -0
  37. package/dist/js/types/util/index.d.ts +79 -0
  38. package/package.json +52 -25
  39. package/src/scss/_app-header.scss +3 -1
  40. package/src/scss/_app-main.scss +4 -0
  41. package/src/scss/_app-sidebar.scss +3 -1
  42. package/src/scss/_bootstrap-overrides.scss +60 -0
  43. package/src/scss/_info-box.scss +3 -1
  44. package/src/scss/_progress-bars.scss +3 -1
  45. package/src/scss/_table.scss +4 -2
  46. package/src/scss/adminlte-docs.scss +25 -0
  47. package/src/scss/adminlte.scss +5 -4
  48. package/src/scss/pages/_login_and_register.scss +3 -1
  49. package/src/ts/accessibility.ts +6 -2
  50. package/src/ts/adminlte.ts +7 -3
  51. package/src/ts/base-component.ts +111 -0
  52. package/src/ts/card-widget.ts +124 -100
  53. package/src/ts/color-mode.ts +254 -0
  54. package/src/ts/direct-chat.ts +38 -32
  55. package/src/ts/fullscreen.ts +32 -23
  56. package/src/ts/push-menu.ts +106 -43
  57. package/src/ts/treeview.ts +84 -50
  58. package/src/ts/util/index.ts +80 -16
  59. package/src/scss/_bootstrap-variables.scss +0 -1766
@@ -0,0 +1,254 @@
1
+ /**
2
+ * --------------------------------------------
3
+ * @file AdminLTE color-mode.ts
4
+ * @description Color mode (light/dark/auto) switcher for AdminLTE.
5
+ * Resolves the theme from, in order: the visitor's stored choice, the theme
6
+ * the page itself declared in <html data-bs-theme="…">, and finally the OS
7
+ * preference. Keeps [data-bs-theme-value] toggles and [data-lte-theme-icon]
8
+ * indicator icons in sync.
9
+ *
10
+ * Ships in the bundle so applications no longer need to copy the demo's
11
+ * inline script. The tiny no-flash snippet in <head> (see _head.astro)
12
+ * remains inline by design — it must run before first paint. That snippet
13
+ * flags the values it computes itself with [data-lte-theme-resolved], so a
14
+ * theme authored in the markup can be told apart from one it resolved.
15
+ *
16
+ * Applications with their own theming opt out entirely with
17
+ * <html data-lte-color-mode="off">.
18
+ * @license MIT
19
+ * --------------------------------------------
20
+ */
21
+
22
+ import { getLifecycleSignal, onDOMContentLoaded } from './util/index'
23
+
24
+ /**
25
+ * Constants
26
+ * ====================================================
27
+ */
28
+
29
+ const DATA_KEY = 'lte.color-mode'
30
+ const EVENT_KEY = `.${DATA_KEY}`
31
+ const EVENT_CHANGED = `changed${EVENT_KEY}`
32
+
33
+ const STORAGE_KEY = 'lte-theme'
34
+
35
+ const ATTRIBUTE_THEME = 'data-bs-theme'
36
+ const ATTRIBUTE_TOGGLE = 'data-bs-theme-value'
37
+ const ATTRIBUTE_DISABLED = 'data-lte-color-mode'
38
+ const ATTRIBUTE_RESOLVED = 'data-lte-theme-resolved'
39
+
40
+ const SELECTOR_TOGGLE = `[${ATTRIBUTE_TOGGLE}]`
41
+ const SELECTOR_ICON = '[data-lte-theme-icon]'
42
+
43
+ type Theme = 'light' | 'dark' | 'auto'
44
+
45
+ const THEMES = new Set<string>(['light', 'dark', 'auto'])
46
+
47
+ const isValidTheme = (value: string): value is Theme => THEMES.has(value)
48
+
49
+ /**
50
+ * Applications with their own theming take over by adding
51
+ * `data-lte-color-mode="off"` to <html>: ColorMode then never writes
52
+ * `data-bs-theme` — not on load, not on a toggle click, not when the OS
53
+ * preference changes. This is also the escape hatch for custom Bootstrap
54
+ * themes, whose names ColorMode cannot resolve (#6084).
55
+ *
56
+ * Read live rather than captured, so it can be flipped at runtime.
57
+ */
58
+ const isDisabled = (): boolean =>
59
+ document.documentElement.getAttribute(ATTRIBUTE_DISABLED) === 'off'
60
+
61
+ /**
62
+ * The theme the page itself declared in <html data-bs-theme="…">, or null when
63
+ * it declared none.
64
+ *
65
+ * Captured once, at module evaluation, because the attribute is both an input
66
+ * and an output: after the first `_applyTheme()` it holds ColorMode's own
67
+ * write, which must not be mistaken for the page's intent on a later lifecycle
68
+ * pass (Turbo, `initialize()`). Reading it here runs before any of those.
69
+ *
70
+ * The pre-paint snippet in <head> writes before this module is even fetched,
71
+ * so it marks the values it computed itself with [data-lte-theme-resolved] —
72
+ * those are not authored, and are ignored.
73
+ */
74
+ const readMarkupTheme = (): Theme | null => {
75
+ const { documentElement } = document
76
+
77
+ if (documentElement.hasAttribute(ATTRIBUTE_RESOLVED)) {
78
+ return null
79
+ }
80
+
81
+ const declared = documentElement.getAttribute(ATTRIBUTE_THEME)
82
+
83
+ return declared && isValidTheme(declared) ? declared : null
84
+ }
85
+
86
+ const MARKUP_THEME = readMarkupTheme()
87
+
88
+ /**
89
+ * Class Definition
90
+ * ====================================================
91
+ */
92
+
93
+ class ColorMode {
94
+ /**
95
+ * Read the persisted theme choice, or null when nothing was stored or
96
+ * localStorage is unavailable (private mode, sandboxed iframe).
97
+ */
98
+ getStoredTheme(): Theme | null {
99
+ try {
100
+ const stored = localStorage.getItem(STORAGE_KEY)
101
+ return stored && isValidTheme(stored) ? stored : null
102
+ } catch {
103
+ return null
104
+ }
105
+ }
106
+
107
+ /**
108
+ * The theme declared in the markup, for applications that render it
109
+ * server-side from a cookie or a user record. Null when the page declared
110
+ * none, or when the value is a custom Bootstrap theme ColorMode cannot
111
+ * resolve — see `isDisabled` for those.
112
+ */
113
+ getMarkupTheme(): Theme | null {
114
+ return MARKUP_THEME
115
+ }
116
+
117
+ /**
118
+ * The user's effective choice: the stored theme, then the theme declared in
119
+ * the markup, falling back to the OS preference. Storage comes first because
120
+ * it is the visitor's own click on this device; markup is only the default
121
+ * the page shipped with.
122
+ */
123
+ getPreferredTheme(): Theme {
124
+ const preferred = this.getStoredTheme() ?? this.getMarkupTheme()
125
+ if (preferred) {
126
+ return preferred
127
+ }
128
+
129
+ return this._prefersDark() ? 'dark' : 'light'
130
+ }
131
+
132
+ /**
133
+ * Resolve "auto" against the OS preference.
134
+ */
135
+ resolveTheme(theme: Theme): 'light' | 'dark' {
136
+ if (theme === 'auto') {
137
+ return this._prefersDark() ? 'dark' : 'light'
138
+ }
139
+
140
+ return theme
141
+ }
142
+
143
+ /**
144
+ * Apply a theme and persist the choice. Dispatches
145
+ * `changed.lte.color-mode` on the document with { theme, resolved }.
146
+ */
147
+ setTheme(theme: Theme): void {
148
+ try {
149
+ localStorage.setItem(STORAGE_KEY, theme)
150
+ } catch {
151
+ // localStorage may be unavailable; the theme still applies for this page.
152
+ }
153
+
154
+ this._applyTheme(theme)
155
+ this._showActiveTheme(theme)
156
+
157
+ document.dispatchEvent(new CustomEvent(EVENT_CHANGED, {
158
+ detail: { theme, resolved: this.resolveTheme(theme) }
159
+ }))
160
+ }
161
+
162
+ /**
163
+ * Apply without persisting — used on init and when the OS preference
164
+ * changes while in "auto" mode.
165
+ */
166
+ _applyTheme(theme: Theme): void {
167
+ const resolved = this.resolveTheme(theme)
168
+ document.documentElement.setAttribute(ATTRIBUTE_THEME, resolved)
169
+ document.documentElement.style.colorScheme = resolved
170
+ }
171
+
172
+ /**
173
+ * Whether the OS preference is currently dark.
174
+ */
175
+ _prefersDark(): boolean {
176
+ return globalThis.matchMedia('(prefers-color-scheme: dark)').matches
177
+ }
178
+
179
+ /**
180
+ * Sync the [data-bs-theme-value] toggles (active state, pressed state,
181
+ * check mark) and the [data-lte-theme-icon] indicator icons.
182
+ */
183
+ _showActiveTheme(theme: Theme): void {
184
+ document.querySelectorAll(SELECTOR_TOGGLE).forEach(toggle => {
185
+ const isActive = toggle.getAttribute(ATTRIBUTE_TOGGLE) === theme
186
+ toggle.classList.toggle('active', isActive)
187
+ toggle.setAttribute('aria-pressed', String(isActive))
188
+ toggle.querySelector('.bi-check-lg')?.classList.toggle('d-none', !isActive)
189
+ })
190
+
191
+ document.querySelectorAll(SELECTOR_ICON).forEach(icon => {
192
+ icon.classList.toggle('d-none', (icon as HTMLElement).dataset.lteThemeIcon !== theme)
193
+ })
194
+ }
195
+
196
+ /**
197
+ * Apply the preferred theme and sync the UI without persisting anything.
198
+ */
199
+ init(): void {
200
+ if (isDisabled()) {
201
+ return
202
+ }
203
+
204
+ const theme = this.getPreferredTheme()
205
+ this._applyTheme(theme)
206
+ this._showActiveTheme(theme)
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Data API implementation
212
+ * ====================================================
213
+ * Toggle clicks are delegated on `document`, so switcher buttons added after
214
+ * load work and the listener survives Turbo's <body> swaps. The class is
215
+ * stateless — everything lives in localStorage and the DOM.
216
+ */
217
+
218
+ document.addEventListener('click', event => {
219
+ const target = event.target
220
+
221
+ if (!(target instanceof Element) || isDisabled()) {
222
+ return
223
+ }
224
+
225
+ const toggle = target.closest(SELECTOR_TOGGLE)
226
+ const theme = toggle?.getAttribute(ATTRIBUTE_TOGGLE)
227
+
228
+ if (theme && isValidTheme(theme)) {
229
+ new ColorMode().setTheme(theme)
230
+ }
231
+ })
232
+
233
+ onDOMContentLoaded(() => {
234
+ const colorMode = new ColorMode()
235
+ colorMode.init()
236
+
237
+ // Follow the OS only while the OS *is* the effective choice: nothing stored
238
+ // and nothing declared in the markup, or an explicit "auto". A theme the
239
+ // page declared is a preference too, and outlives an OS change (#6093).
240
+ globalThis.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
241
+ if (isDisabled()) {
242
+ return
243
+ }
244
+
245
+ const preferred = colorMode.getStoredTheme() ?? colorMode.getMarkupTheme()
246
+
247
+ if (!preferred || preferred === 'auto') {
248
+ colorMode._applyTheme('auto')
249
+ colorMode._showActiveTheme(preferred ?? 'auto')
250
+ }
251
+ }, { signal: getLifecycleSignal() })
252
+ })
253
+
254
+ export default ColorMode
@@ -6,17 +6,15 @@
6
6
  * --------------------------------------------
7
7
  */
8
8
 
9
- import {
10
- onDOMContentLoaded
11
- } from './util/index'
9
+ import { BaseComponent, dispatchCustomEvent } from './base-component'
12
10
 
13
11
  /**
14
12
  * Constants
15
13
  * ====================================================
16
14
  */
17
15
 
18
- const DATA_KEY = 'lte.direct-chat'
19
- const EVENT_KEY = `.${DATA_KEY}`
16
+ const NAME = 'direct-chat'
17
+ const EVENT_KEY = `.lte.${NAME}`
20
18
  const EVENT_EXPANDED = `expanded${EVENT_KEY}`
21
19
  const EVENT_COLLAPSED = `collapsed${EVENT_KEY}`
22
20
 
@@ -30,25 +28,26 @@ const CLASS_NAME_DIRECT_CHAT_OPEN = 'direct-chat-contacts-open'
30
28
  * ====================================================
31
29
  */
32
30
 
33
- class DirectChat {
34
- _element: HTMLElement
35
- constructor(element: HTMLElement) {
36
- this._element = element
31
+ class DirectChat extends BaseComponent {
32
+ static get NAME(): string {
33
+ return NAME
34
+ }
35
+
36
+ static getInstance(element: Element | null | undefined): DirectChat | null {
37
+ return this._getInstance(element) as DirectChat | null
38
+ }
39
+
40
+ static getOrCreateInstance(element: HTMLElement): DirectChat {
41
+ return this.getInstance(element) ?? new this(element)
37
42
  }
38
43
 
39
44
  toggle(): void {
40
45
  if (this._element.classList.contains(CLASS_NAME_DIRECT_CHAT_OPEN)) {
41
- const event = new Event(EVENT_COLLAPSED)
42
-
43
46
  this._element.classList.remove(CLASS_NAME_DIRECT_CHAT_OPEN)
44
-
45
- this._element.dispatchEvent(event)
47
+ dispatchCustomEvent(this._element, EVENT_COLLAPSED)
46
48
  } else {
47
- const event = new Event(EVENT_EXPANDED)
48
-
49
49
  this._element.classList.add(CLASS_NAME_DIRECT_CHAT_OPEN)
50
-
51
- this._element.dispatchEvent(event)
50
+ dispatchCustomEvent(this._element, EVENT_EXPANDED)
52
51
  }
53
52
  }
54
53
  }
@@ -57,23 +56,30 @@ class DirectChat {
57
56
  *
58
57
  * Data Api implementation
59
58
  * ====================================================
59
+ * One delegated listener on `document`: chat panes inserted later work, and
60
+ * the listener survives Turbo's <body> swaps.
60
61
  */
61
62
 
62
- onDOMContentLoaded(() => {
63
- const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE)
64
-
65
- button.forEach(btn => {
66
- btn.addEventListener('click', event => {
67
- event.preventDefault()
68
- const target = event.target as HTMLElement
69
- const chatPane = target.closest(SELECTOR_DIRECT_CHAT) as HTMLElement | undefined
70
-
71
- if (chatPane) {
72
- const data = new DirectChat(chatPane)
73
- data.toggle()
74
- }
75
- })
76
- })
63
+ document.addEventListener('click', event => {
64
+ const target = event.target
65
+
66
+ if (!(target instanceof Element)) {
67
+ return
68
+ }
69
+
70
+ const trigger = target.closest(SELECTOR_DATA_TOGGLE)
71
+
72
+ if (!trigger) {
73
+ return
74
+ }
75
+
76
+ event.preventDefault()
77
+
78
+ const chatPane = trigger.closest(SELECTOR_DIRECT_CHAT) as HTMLElement | null
79
+
80
+ if (chatPane) {
81
+ DirectChat.getOrCreateInstance(chatPane).toggle()
82
+ }
77
83
  })
78
84
 
79
85
  export default DirectChat
@@ -6,6 +6,7 @@
6
6
  * --------------------------------------------
7
7
  */
8
8
 
9
+ import { BaseComponent, dispatchCustomEvent } from './base-component'
9
10
  import {
10
11
  getLifecycleSignal,
11
12
  onDOMContentLoaded
@@ -15,8 +16,8 @@ import {
15
16
  * Constants
16
17
  * ============================================================================
17
18
  */
18
- const DATA_KEY = 'lte.fullscreen'
19
- const EVENT_KEY = `.${DATA_KEY}`
19
+ const NAME = 'fullscreen'
20
+ const EVENT_KEY = `.lte.${NAME}`
20
21
  const EVENT_MAXIMIZED = `maximized${EVENT_KEY}`
21
22
  const EVENT_MINIMIZED = `minimized${EVENT_KEY}`
22
23
 
@@ -48,7 +49,7 @@ function syncFullScreenState(): void {
48
49
  const eventName = isFullScreen ? EVENT_MAXIMIZED : EVENT_MINIMIZED
49
50
 
50
51
  document.querySelectorAll(SELECTOR_FULLSCREEN_TOGGLE).forEach(button => {
51
- button.dispatchEvent(new Event(eventName))
52
+ dispatchCustomEvent(button, eventName)
52
53
  })
53
54
  }
54
55
 
@@ -56,13 +57,17 @@ function syncFullScreenState(): void {
56
57
  * Class Definition.
57
58
  * ============================================================================
58
59
  */
59
- class FullScreen {
60
- _element: HTMLElement
61
- _config: undefined
60
+ class FullScreen extends BaseComponent {
61
+ static get NAME(): string {
62
+ return NAME
63
+ }
64
+
65
+ static getInstance(element: Element | null | undefined): FullScreen | null {
66
+ return this._getInstance(element) as FullScreen | null
67
+ }
62
68
 
63
- constructor(element: HTMLElement, config?: undefined) {
64
- this._element = element
65
- this._config = config
69
+ static getOrCreateInstance(element: HTMLElement): FullScreen {
70
+ return this.getInstance(element) ?? new this(element)
66
71
  }
67
72
 
68
73
  inFullScreen(): void {
@@ -96,25 +101,29 @@ class FullScreen {
96
101
  /**
97
102
  * Data Api implementation
98
103
  * ============================================================================
104
+ * Toggle clicks are delegated on `document`; the fullscreenchange listener is
105
+ * re-registered per page load with the lifecycle signal.
99
106
  */
100
- onDOMContentLoaded(() => {
101
- document.addEventListener('fullscreenchange', syncFullScreenState, { signal: getLifecycleSignal() })
102
107
 
103
- const buttons = document.querySelectorAll(SELECTOR_FULLSCREEN_TOGGLE)
108
+ document.addEventListener('click', event => {
109
+ const target = event.target
104
110
 
105
- buttons.forEach(btn => {
106
- btn.addEventListener('click', event => {
107
- event.preventDefault()
111
+ if (!(target instanceof Element)) {
112
+ return
113
+ }
108
114
 
109
- const target = event.target as HTMLElement
110
- const button = target.closest(SELECTOR_FULLSCREEN_TOGGLE) as HTMLElement | undefined
115
+ const button = target.closest(SELECTOR_FULLSCREEN_TOGGLE) as HTMLElement | null
111
116
 
112
- if (button) {
113
- const data = new FullScreen(button, undefined)
114
- data.toggleFullScreen()
115
- }
116
- })
117
- })
117
+ if (!button) {
118
+ return
119
+ }
120
+
121
+ event.preventDefault()
122
+ FullScreen.getOrCreateInstance(button).toggleFullScreen()
123
+ })
124
+
125
+ onDOMContentLoaded(() => {
126
+ document.addEventListener('fullscreenchange', syncFullScreenState, { signal: getLifecycleSignal() })
118
127
  })
119
128
 
120
129
  export default FullScreen