admin-lte 4.0.2 → 4.1.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.
- package/ACCESSIBILITY-COMPLIANCE.md +60 -339
- package/CHANGELOG.md +66 -0
- package/README.md +15 -2
- package/dist/css/adminlte-docs.css +878 -0
- package/dist/css/adminlte-docs.css.map +1 -0
- package/dist/css/adminlte-docs.min.css +7 -0
- package/dist/css/adminlte-docs.min.css.map +1 -0
- package/dist/css/adminlte-docs.rtl.css +877 -0
- package/dist/css/adminlte-docs.rtl.css.map +1 -0
- package/dist/css/adminlte-docs.rtl.min.css +7 -0
- package/dist/css/adminlte-docs.rtl.min.css.map +1 -0
- package/dist/css/adminlte.css +1176 -2718
- package/dist/css/adminlte.css.map +1 -1
- package/dist/css/adminlte.min.css +2 -2
- package/dist/css/adminlte.min.css.map +1 -1
- package/dist/css/adminlte.rtl.css +1176 -2718
- package/dist/css/adminlte.rtl.css.map +1 -1
- package/dist/css/adminlte.rtl.min.css +2 -2
- package/dist/css/adminlte.rtl.min.css.map +1 -1
- package/dist/js/adminlte.esm.js +1365 -0
- package/dist/js/adminlte.esm.js.map +1 -0
- package/dist/js/adminlte.esm.min.js +7 -0
- package/dist/js/adminlte.esm.min.js.map +1 -0
- package/dist/js/adminlte.js +606 -310
- package/dist/js/adminlte.js.map +1 -1
- package/dist/js/adminlte.min.js +2 -2
- package/dist/js/adminlte.min.js.map +1 -1
- package/dist/js/types/accessibility.d.ts +46 -0
- package/dist/js/types/adminlte.d.ts +9 -0
- package/dist/js/types/base-component.d.ts +36 -0
- package/dist/js/types/card-widget.d.ts +30 -0
- package/dist/js/types/color-mode.d.ts +56 -0
- package/dist/js/types/direct-chat.d.ts +19 -0
- package/dist/js/types/fullscreen.d.ts +21 -0
- package/dist/js/types/layout.d.ts +19 -0
- package/dist/js/types/push-menu.d.ts +102 -0
- package/dist/js/types/treeview.d.ts +27 -0
- package/dist/js/types/util/index.d.ts +42 -0
- package/package.json +60 -21
- package/src/scss/_app-header.scss +3 -1
- package/src/scss/_app-main.scss +4 -0
- package/src/scss/_app-sidebar.scss +3 -1
- package/src/scss/_bootstrap-overrides.scss +60 -0
- package/src/scss/_callouts.scss +13 -3
- package/src/scss/_dropdown.scss +3 -1
- package/src/scss/_info-box.scss +3 -1
- package/src/scss/_progress-bars.scss +3 -1
- package/src/scss/_table.scss +4 -2
- package/src/scss/adminlte-docs.scss +25 -0
- package/src/scss/adminlte.scss +5 -4
- package/src/scss/pages/_login_and_register.scss +3 -1
- package/src/scss/parts/_core.scss +0 -1
- package/src/ts/accessibility.ts +129 -69
- package/src/ts/adminlte.ts +3 -1
- package/src/ts/base-component.ts +84 -0
- package/src/ts/card-widget.ts +134 -85
- package/src/ts/color-mode.ts +174 -0
- package/src/ts/direct-chat.ts +38 -32
- package/src/ts/fullscreen.ts +78 -63
- package/src/ts/layout.ts +2 -1
- package/src/ts/push-menu.ts +93 -44
- package/src/ts/treeview.ts +107 -47
- package/src/ts/util/index.ts +114 -29
- package/.browserslistrc +0 -17
- package/.prettierrc +0 -6
- package/CODE_OF_CONDUCT.md +0 -43
- package/SECURITY.md +0 -99
- package/dist/UI/general.html +0 -2317
- package/dist/UI/icons.html +0 -1126
- package/dist/UI/timeline.html +0 -1212
- package/dist/docs/browser-support.html +0 -826
- package/dist/docs/color-mode.html +0 -1142
- package/dist/docs/components/main-header.html +0 -1209
- package/dist/docs/components/main-sidebar.html +0 -924
- package/dist/docs/customization.html +0 -1080
- package/dist/docs/deployment.html +0 -1145
- package/dist/docs/faq.html +0 -1392
- package/dist/docs/getting-started.html +0 -991
- package/dist/docs/how-to-contribute.html +0 -890
- package/dist/docs/index.html +0 -16
- package/dist/docs/integrations.html +0 -1231
- package/dist/docs/introduction.html +0 -1008
- package/dist/docs/javascript/accessibility.html +0 -949
- package/dist/docs/javascript/card-widget.html +0 -1019
- package/dist/docs/javascript/direct-chat.html +0 -896
- package/dist/docs/javascript/fullscreen.html +0 -905
- package/dist/docs/javascript/layout.html +0 -908
- package/dist/docs/javascript/plugins-overview.html +0 -1144
- package/dist/docs/javascript/pushmenu.html +0 -1045
- package/dist/docs/javascript/treeview.html +0 -1024
- package/dist/docs/layout-blueprint.html +0 -1087
- package/dist/docs/layout.html +0 -842
- package/dist/docs/license.html +0 -807
- package/dist/docs/migration.html +0 -1149
- package/dist/docs/recipes.html +0 -1185
- package/dist/docs/rtl.html +0 -957
- package/dist/examples/lockscreen.html +0 -249
- package/dist/examples/login-v2.html +0 -282
- package/dist/examples/login.html +0 -272
- package/dist/examples/register-v2.html +0 -291
- package/dist/examples/register.html +0 -277
- package/dist/forms/elements.html +0 -1405
- package/dist/forms/layout.html +0 -1267
- package/dist/forms/validation.html +0 -1275
- package/dist/forms/wizard.html +0 -1410
- package/dist/generate/theme.html +0 -1082
- package/dist/index.html +0 -1745
- package/dist/index2.html +0 -2277
- package/dist/index3.html +0 -1532
- package/dist/layout/collapsed-sidebar-without-hover.html +0 -1144
- package/dist/layout/collapsed-sidebar.html +0 -1137
- package/dist/layout/fixed-complete.html +0 -1138
- package/dist/layout/fixed-footer.html +0 -1136
- package/dist/layout/fixed-header.html +0 -1136
- package/dist/layout/fixed-sidebar.html +0 -1138
- package/dist/layout/layout-custom-area.html +0 -1177
- package/dist/layout/layout-rtl.html +0 -1137
- package/dist/layout/logo-switch.html +0 -683
- package/dist/layout/sidebar-mini.html +0 -1137
- package/dist/layout/unfixed-sidebar.html +0 -1136
- package/dist/mailbox/compose.html +0 -1151
- package/dist/mailbox/inbox.html +0 -1509
- package/dist/mailbox/read.html +0 -1202
- package/dist/pages/404.html +0 -239
- package/dist/pages/500.html +0 -227
- package/dist/pages/calendar.html +0 -1266
- package/dist/pages/chat.html +0 -1427
- package/dist/pages/faq.html +0 -1383
- package/dist/pages/file-manager.html +0 -1835
- package/dist/pages/invoice.html +0 -1199
- package/dist/pages/kanban.html +0 -1439
- package/dist/pages/maintenance.html +0 -230
- package/dist/pages/pricing.html +0 -1279
- package/dist/pages/profile.html +0 -1382
- package/dist/pages/projects.html +0 -1644
- package/dist/pages/settings.html +0 -1387
- package/dist/tables/data.html +0 -1319
- package/dist/tables/simple.html +0 -1435
- package/dist/widgets/cards.html +0 -1356
- package/dist/widgets/info-box.html +0 -1429
- package/dist/widgets/small-box.html +0 -1225
- package/eslint.config.js +0 -82
- package/github-issue-response.md +0 -40
- package/index.html +0 -12
- package/netlify.toml +0 -16
- package/src/scss/_bootstrap-variables.scss +0 -1766
package/src/ts/card-widget.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* --------------------------------------------
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { BaseComponent, dispatchCustomEvent } from './base-component'
|
|
9
10
|
import {
|
|
10
|
-
onDOMContentLoaded,
|
|
11
11
|
slideUp,
|
|
12
12
|
slideDown
|
|
13
13
|
} from './util/index'
|
|
@@ -17,11 +17,18 @@ import {
|
|
|
17
17
|
* ====================================================
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
const
|
|
21
|
-
const EVENT_KEY =
|
|
20
|
+
const NAME = 'card-widget'
|
|
21
|
+
const EVENT_KEY = `.lte.${NAME}`
|
|
22
|
+
|
|
23
|
+
// Cancelable "before" events, dispatched on the card when an action starts.
|
|
24
|
+
const EVENT_COLLAPSE = `collapse${EVENT_KEY}`
|
|
25
|
+
const EVENT_EXPAND = `expand${EVENT_KEY}`
|
|
26
|
+
const EVENT_REMOVE = `remove${EVENT_KEY}`
|
|
27
|
+
|
|
28
|
+
// "After" events, dispatched on the card when the action has completed.
|
|
22
29
|
const EVENT_COLLAPSED = `collapsed${EVENT_KEY}`
|
|
23
30
|
const EVENT_EXPANDED = `expanded${EVENT_KEY}`
|
|
24
|
-
const
|
|
31
|
+
const EVENT_REMOVED = `removed${EVENT_KEY}`
|
|
25
32
|
const EVENT_MAXIMIZED = `maximized${EVENT_KEY}`
|
|
26
33
|
const EVENT_MINIMIZED = `minimized${EVENT_KEY}`
|
|
27
34
|
|
|
@@ -53,14 +60,24 @@ const Default: Config = {
|
|
|
53
60
|
maximizeTrigger: SELECTOR_DATA_MAXIMIZE
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
class CardWidget {
|
|
57
|
-
|
|
63
|
+
class CardWidget extends BaseComponent {
|
|
64
|
+
static get NAME(): string {
|
|
65
|
+
return NAME
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static getInstance(element: Element | null | undefined): CardWidget | null {
|
|
69
|
+
return this._getInstance(element) as CardWidget | null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static getOrCreateInstance(element: HTMLElement, config: Partial<Config> = {}): CardWidget {
|
|
73
|
+
return this.getInstance(element) ?? new this(element, config)
|
|
74
|
+
}
|
|
75
|
+
|
|
58
76
|
_parent: HTMLElement | undefined
|
|
59
|
-
_clone: HTMLElement | undefined
|
|
60
77
|
_config: Config
|
|
61
78
|
|
|
62
|
-
constructor(element: HTMLElement, config: Config) {
|
|
63
|
-
|
|
79
|
+
constructor(element: HTMLElement, config: Partial<Config> = {}) {
|
|
80
|
+
super(element)
|
|
64
81
|
this._parent = element.closest(SELECTOR_CARD) as HTMLElement | undefined
|
|
65
82
|
|
|
66
83
|
if (element.classList.contains(CLASS_NAME_CARD)) {
|
|
@@ -71,68 +88,93 @@ class CardWidget {
|
|
|
71
88
|
}
|
|
72
89
|
|
|
73
90
|
collapse() {
|
|
74
|
-
|
|
91
|
+
if (!this._parent) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
75
94
|
|
|
76
|
-
if (this._parent) {
|
|
77
|
-
|
|
95
|
+
if (dispatchCustomEvent(this._parent, EVENT_COLLAPSE, { cancelable: true }).defaultPrevented) {
|
|
96
|
+
return
|
|
97
|
+
}
|
|
78
98
|
|
|
79
|
-
|
|
80
|
-
|
|
99
|
+
this._parent.classList.add(CLASS_NAME_COLLAPSING)
|
|
100
|
+
this._parent.classList.remove(CLASS_NAME_EXPANDING)
|
|
81
101
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
slideUp(el, this._config.animationSpeed)
|
|
85
|
-
}
|
|
86
|
-
})
|
|
102
|
+
// Only target direct children to avoid affecting nested cards
|
|
103
|
+
const elm = this._parent.querySelectorAll(`:scope > ${SELECTOR_CARD_BODY}, :scope > ${SELECTOR_CARD_FOOTER}`)
|
|
87
104
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}, this._config.animationSpeed)
|
|
94
|
-
}
|
|
105
|
+
elm.forEach(el => {
|
|
106
|
+
if (el instanceof HTMLElement) {
|
|
107
|
+
slideUp(el, this._config.animationSpeed)
|
|
108
|
+
}
|
|
109
|
+
})
|
|
95
110
|
|
|
96
|
-
|
|
111
|
+
setTimeout(() => {
|
|
112
|
+
// Guard against a stale timer: if expand() ran while this collapse
|
|
113
|
+
// was still animating, the card must not end up marked collapsed.
|
|
114
|
+
if (this._parent?.classList.contains(CLASS_NAME_COLLAPSING)) {
|
|
115
|
+
this._parent.classList.add(CLASS_NAME_COLLAPSED)
|
|
116
|
+
this._parent.classList.remove(CLASS_NAME_COLLAPSING)
|
|
117
|
+
dispatchCustomEvent(this._parent, EVENT_COLLAPSED)
|
|
118
|
+
}
|
|
119
|
+
}, this._config.animationSpeed)
|
|
97
120
|
}
|
|
98
121
|
|
|
99
122
|
expand() {
|
|
100
|
-
|
|
123
|
+
if (!this._parent) {
|
|
124
|
+
return
|
|
125
|
+
}
|
|
101
126
|
|
|
102
|
-
if (this._parent) {
|
|
103
|
-
|
|
127
|
+
if (dispatchCustomEvent(this._parent, EVENT_EXPAND, { cancelable: true }).defaultPrevented) {
|
|
128
|
+
return
|
|
129
|
+
}
|
|
104
130
|
|
|
105
|
-
|
|
106
|
-
|
|
131
|
+
this._parent.classList.add(CLASS_NAME_EXPANDING)
|
|
132
|
+
this._parent.classList.remove(CLASS_NAME_COLLAPSING, CLASS_NAME_COLLAPSED)
|
|
107
133
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
slideDown(el, this._config.animationSpeed)
|
|
111
|
-
}
|
|
112
|
-
})
|
|
134
|
+
// Only target direct children to avoid affecting nested cards
|
|
135
|
+
const elm = this._parent.querySelectorAll(`:scope > ${SELECTOR_CARD_BODY}, :scope > ${SELECTOR_CARD_FOOTER}`)
|
|
113
136
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
137
|
+
elm.forEach(el => {
|
|
138
|
+
if (el instanceof HTMLElement) {
|
|
139
|
+
slideDown(el, this._config.animationSpeed)
|
|
140
|
+
}
|
|
141
|
+
})
|
|
120
142
|
|
|
121
|
-
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
// Same stale-timer guard as collapse(), in the other direction.
|
|
145
|
+
if (this._parent?.classList.contains(CLASS_NAME_EXPANDING)) {
|
|
146
|
+
this._parent.classList.remove(CLASS_NAME_EXPANDING)
|
|
147
|
+
dispatchCustomEvent(this._parent, EVENT_EXPANDED)
|
|
148
|
+
}
|
|
149
|
+
}, this._config.animationSpeed)
|
|
122
150
|
}
|
|
123
151
|
|
|
124
152
|
remove() {
|
|
125
|
-
|
|
153
|
+
if (!this._parent) {
|
|
154
|
+
return
|
|
155
|
+
}
|
|
126
156
|
|
|
127
|
-
if (this._parent) {
|
|
128
|
-
|
|
157
|
+
if (dispatchCustomEvent(this._parent, EVENT_REMOVE, { cancelable: true }).defaultPrevented) {
|
|
158
|
+
return
|
|
129
159
|
}
|
|
130
160
|
|
|
131
|
-
this.
|
|
161
|
+
const parent = this._parent
|
|
162
|
+
slideUp(parent, this._config.animationSpeed)
|
|
163
|
+
|
|
164
|
+
// Actually remove the card once the animation finishes — hiding it
|
|
165
|
+
// would keep its form fields submitting and its ids in the document.
|
|
166
|
+
// `removed` is dispatched just before detachment so it can still bubble.
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
dispatchCustomEvent(parent, EVENT_REMOVED)
|
|
169
|
+
parent.remove()
|
|
170
|
+
this.dispose()
|
|
171
|
+
}, this._config.animationSpeed)
|
|
132
172
|
}
|
|
133
173
|
|
|
134
174
|
toggle() {
|
|
135
|
-
|
|
175
|
+
// A card mid-collapse counts as collapsed so a second click during the
|
|
176
|
+
// animation reverses it instead of being swallowed.
|
|
177
|
+
if (this._parent?.classList.contains(CLASS_NAME_COLLAPSED) || this._parent?.classList.contains(CLASS_NAME_COLLAPSING)) {
|
|
136
178
|
this.expand()
|
|
137
179
|
return
|
|
138
180
|
}
|
|
@@ -141,8 +183,6 @@ class CardWidget {
|
|
|
141
183
|
}
|
|
142
184
|
|
|
143
185
|
maximize() {
|
|
144
|
-
const event = new Event(EVENT_MAXIMIZED)
|
|
145
|
-
|
|
146
186
|
if (this._parent) {
|
|
147
187
|
this._parent.style.height = `${this._parent.offsetHeight}px`
|
|
148
188
|
this._parent.style.width = `${this._parent.offsetWidth}px`
|
|
@@ -161,16 +201,14 @@ class CardWidget {
|
|
|
161
201
|
if (this._parent.classList.contains(CLASS_NAME_COLLAPSED)) {
|
|
162
202
|
this._parent.classList.add(CLASS_NAME_WAS_COLLAPSED)
|
|
163
203
|
}
|
|
204
|
+
|
|
205
|
+
dispatchCustomEvent(this._parent, EVENT_MAXIMIZED)
|
|
164
206
|
}
|
|
165
207
|
}, 150)
|
|
166
208
|
}
|
|
167
|
-
|
|
168
|
-
this._element?.dispatchEvent(event)
|
|
169
209
|
}
|
|
170
210
|
|
|
171
211
|
minimize() {
|
|
172
|
-
const event = new Event(EVENT_MINIMIZED)
|
|
173
|
-
|
|
174
212
|
if (this._parent) {
|
|
175
213
|
this._parent.style.height = 'auto'
|
|
176
214
|
this._parent.style.width = 'auto'
|
|
@@ -189,11 +227,19 @@ class CardWidget {
|
|
|
189
227
|
if (this._parent?.classList.contains(CLASS_NAME_WAS_COLLAPSED)) {
|
|
190
228
|
this._parent.classList.remove(CLASS_NAME_WAS_COLLAPSED)
|
|
191
229
|
}
|
|
230
|
+
|
|
231
|
+
dispatchCustomEvent(this._parent, EVENT_MINIMIZED)
|
|
232
|
+
|
|
233
|
+
// Drop the inline sizing/transition once the restore transition has
|
|
234
|
+
// finished so the card doesn't carry stale styles forever.
|
|
235
|
+
setTimeout(() => {
|
|
236
|
+
this._parent?.style.removeProperty('height')
|
|
237
|
+
this._parent?.style.removeProperty('width')
|
|
238
|
+
this._parent?.style.removeProperty('transition')
|
|
239
|
+
}, 150)
|
|
192
240
|
}
|
|
193
241
|
}, 10)
|
|
194
242
|
}
|
|
195
|
-
|
|
196
|
-
this._element?.dispatchEvent(event)
|
|
197
243
|
}
|
|
198
244
|
|
|
199
245
|
toggleMaximize() {
|
|
@@ -210,41 +256,44 @@ class CardWidget {
|
|
|
210
256
|
*
|
|
211
257
|
* Data Api implementation
|
|
212
258
|
* ====================================================
|
|
259
|
+
* A single delegated listener on `document` handles all card toggles — so
|
|
260
|
+
* cards inserted later (AJAX, Turbo Frames) work without re-initialisation,
|
|
261
|
+
* and the listener itself survives Turbo's <body> swaps.
|
|
213
262
|
*/
|
|
214
263
|
|
|
215
|
-
|
|
216
|
-
const
|
|
264
|
+
document.addEventListener('click', event => {
|
|
265
|
+
const target = event.target
|
|
217
266
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const target = event.target as HTMLElement
|
|
222
|
-
const data = new CardWidget(target, Default)
|
|
223
|
-
data.toggle()
|
|
224
|
-
})
|
|
225
|
-
})
|
|
267
|
+
if (!(target instanceof Element)) {
|
|
268
|
+
return
|
|
269
|
+
}
|
|
226
270
|
|
|
227
|
-
const
|
|
271
|
+
const collapseTrigger = target.closest(SELECTOR_DATA_COLLAPSE)
|
|
272
|
+
const removeTrigger = target.closest(SELECTOR_DATA_REMOVE)
|
|
273
|
+
const maximizeTrigger = target.closest(SELECTOR_DATA_MAXIMIZE)
|
|
274
|
+
const trigger = collapseTrigger ?? removeTrigger ?? maximizeTrigger
|
|
228
275
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const target = event.target as HTMLElement
|
|
233
|
-
const data = new CardWidget(target, Default)
|
|
234
|
-
data.remove()
|
|
235
|
-
})
|
|
236
|
-
})
|
|
276
|
+
if (!trigger) {
|
|
277
|
+
return
|
|
278
|
+
}
|
|
237
279
|
|
|
238
|
-
|
|
280
|
+
event.preventDefault()
|
|
239
281
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
282
|
+
const card = trigger.closest(SELECTOR_CARD) as HTMLElement | null
|
|
283
|
+
|
|
284
|
+
if (!card) {
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const widget = CardWidget.getOrCreateInstance(card)
|
|
289
|
+
|
|
290
|
+
if (collapseTrigger) {
|
|
291
|
+
widget.toggle()
|
|
292
|
+
} else if (removeTrigger) {
|
|
293
|
+
widget.remove()
|
|
294
|
+
} else {
|
|
295
|
+
widget.toggleMaximize()
|
|
296
|
+
}
|
|
248
297
|
})
|
|
249
298
|
|
|
250
299
|
export default CardWidget
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* --------------------------------------------
|
|
3
|
+
* @file AdminLTE color-mode.ts
|
|
4
|
+
* @description Color mode (light/dark/auto) switcher for AdminLTE.
|
|
5
|
+
* Persists the choice in localStorage, follows the OS preference in
|
|
6
|
+
* "auto" mode, and keeps [data-bs-theme-value] toggles and
|
|
7
|
+
* [data-lte-theme-icon] indicator icons in sync.
|
|
8
|
+
*
|
|
9
|
+
* Ships in the bundle so applications no longer need to copy the demo's
|
|
10
|
+
* inline script. The tiny no-flash snippet in <head> (see _head.astro)
|
|
11
|
+
* remains inline by design — it must run before first paint.
|
|
12
|
+
* @license MIT
|
|
13
|
+
* --------------------------------------------
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { getLifecycleSignal, onDOMContentLoaded } from './util/index'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Constants
|
|
20
|
+
* ====================================================
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const DATA_KEY = 'lte.color-mode'
|
|
24
|
+
const EVENT_KEY = `.${DATA_KEY}`
|
|
25
|
+
const EVENT_CHANGED = `changed${EVENT_KEY}`
|
|
26
|
+
|
|
27
|
+
const STORAGE_KEY = 'lte-theme'
|
|
28
|
+
|
|
29
|
+
const SELECTOR_TOGGLE = '[data-bs-theme-value]'
|
|
30
|
+
const SELECTOR_ICON = '[data-lte-theme-icon]'
|
|
31
|
+
|
|
32
|
+
type Theme = 'light' | 'dark' | 'auto'
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Class Definition
|
|
36
|
+
* ====================================================
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
class ColorMode {
|
|
40
|
+
/**
|
|
41
|
+
* Read the persisted theme choice, or null when nothing was stored or
|
|
42
|
+
* localStorage is unavailable (private mode, sandboxed iframe).
|
|
43
|
+
*/
|
|
44
|
+
getStoredTheme(): Theme | null {
|
|
45
|
+
try {
|
|
46
|
+
const stored = localStorage.getItem(STORAGE_KEY)
|
|
47
|
+
return stored && ['light', 'dark', 'auto'].includes(stored) ? stored as Theme : null
|
|
48
|
+
} catch {
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The user's effective choice: the stored theme, falling back to the OS
|
|
55
|
+
* preference.
|
|
56
|
+
*/
|
|
57
|
+
getPreferredTheme(): Theme {
|
|
58
|
+
const stored = this.getStoredTheme()
|
|
59
|
+
if (stored) {
|
|
60
|
+
return stored
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return this._prefersDark() ? 'dark' : 'light'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolve "auto" against the OS preference.
|
|
68
|
+
*/
|
|
69
|
+
resolveTheme(theme: Theme): 'light' | 'dark' {
|
|
70
|
+
if (theme === 'auto') {
|
|
71
|
+
return this._prefersDark() ? 'dark' : 'light'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return theme
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Apply a theme and persist the choice. Dispatches
|
|
79
|
+
* `changed.lte.color-mode` on the document with { theme, resolved }.
|
|
80
|
+
*/
|
|
81
|
+
setTheme(theme: Theme): void {
|
|
82
|
+
try {
|
|
83
|
+
localStorage.setItem(STORAGE_KEY, theme)
|
|
84
|
+
} catch {
|
|
85
|
+
// localStorage may be unavailable; the theme still applies for this page.
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this._applyTheme(theme)
|
|
89
|
+
this._showActiveTheme(theme)
|
|
90
|
+
|
|
91
|
+
document.dispatchEvent(new CustomEvent(EVENT_CHANGED, {
|
|
92
|
+
detail: { theme, resolved: this.resolveTheme(theme) }
|
|
93
|
+
}))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Apply without persisting — used on init and when the OS preference
|
|
98
|
+
* changes while in "auto" mode.
|
|
99
|
+
*/
|
|
100
|
+
_applyTheme(theme: Theme): void {
|
|
101
|
+
const resolved = this.resolveTheme(theme)
|
|
102
|
+
document.documentElement.setAttribute('data-bs-theme', resolved)
|
|
103
|
+
document.documentElement.style.colorScheme = resolved
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_prefersDark(): boolean {
|
|
107
|
+
return globalThis.matchMedia('(prefers-color-scheme: dark)').matches
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Sync the [data-bs-theme-value] toggles (active state, pressed state,
|
|
112
|
+
* check mark) and the [data-lte-theme-icon] indicator icons.
|
|
113
|
+
*/
|
|
114
|
+
_showActiveTheme(theme: Theme): void {
|
|
115
|
+
document.querySelectorAll(SELECTOR_TOGGLE).forEach(toggle => {
|
|
116
|
+
const isActive = toggle.getAttribute('data-bs-theme-value') === theme
|
|
117
|
+
toggle.classList.toggle('active', isActive)
|
|
118
|
+
toggle.setAttribute('aria-pressed', String(isActive))
|
|
119
|
+
toggle.querySelector('.bi-check-lg')?.classList.toggle('d-none', !isActive)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
document.querySelectorAll(SELECTOR_ICON).forEach(icon => {
|
|
123
|
+
icon.classList.toggle('d-none', (icon as HTMLElement).dataset.lteThemeIcon !== theme)
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Apply the preferred theme and sync the UI without persisting anything.
|
|
129
|
+
*/
|
|
130
|
+
init(): void {
|
|
131
|
+
const theme = this.getPreferredTheme()
|
|
132
|
+
this._applyTheme(theme)
|
|
133
|
+
this._showActiveTheme(theme)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Data API implementation
|
|
139
|
+
* ====================================================
|
|
140
|
+
* Toggle clicks are delegated on `document`, so switcher buttons added after
|
|
141
|
+
* load work and the listener survives Turbo's <body> swaps. The class is
|
|
142
|
+
* stateless — everything lives in localStorage and the DOM.
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
document.addEventListener('click', event => {
|
|
146
|
+
const target = event.target
|
|
147
|
+
|
|
148
|
+
if (!(target instanceof Element)) {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const toggle = target.closest(SELECTOR_TOGGLE)
|
|
153
|
+
const theme = toggle?.getAttribute('data-bs-theme-value') as Theme | null
|
|
154
|
+
|
|
155
|
+
if (theme) {
|
|
156
|
+
new ColorMode().setTheme(theme)
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
onDOMContentLoaded(() => {
|
|
161
|
+
const colorMode = new ColorMode()
|
|
162
|
+
colorMode.init()
|
|
163
|
+
|
|
164
|
+
// Follow the OS while no explicit choice (or "auto") is stored.
|
|
165
|
+
globalThis.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
|
166
|
+
const stored = colorMode.getStoredTheme()
|
|
167
|
+
if (!stored || stored === 'auto') {
|
|
168
|
+
colorMode._applyTheme('auto')
|
|
169
|
+
colorMode._showActiveTheme(stored ?? 'auto')
|
|
170
|
+
}
|
|
171
|
+
}, { signal: getLifecycleSignal() })
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
export default ColorMode
|
package/src/ts/direct-chat.ts
CHANGED
|
@@ -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
|
|
19
|
-
const EVENT_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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|