@cedx/base 0.17.0 → 0.18.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/ReadMe.md +1 -1
- package/lib/UI/Components/DialogButton.d.ts.map +1 -1
- package/lib/UI/Components/DialogButton.js +12 -3
- package/lib/UI/Components/MessageBox.d.ts +165 -0
- package/lib/UI/Components/MessageBox.d.ts.map +1 -0
- package/lib/UI/Components/MessageBox.js +320 -0
- package/lib/UI/Components/ThemeDropdown.d.ts.map +1 -1
- package/lib/UI/Components/Toast.d.ts.map +1 -1
- package/lib/UI/Components/Toast.js +31 -16
- package/lib/UI/Components/Toaster.d.ts +6 -2
- package/lib/UI/Components/Toaster.d.ts.map +1 -1
- package/lib/UI/Components/Toaster.js +19 -12
- package/package.json +2 -2
- package/src/Client/UI/Components/DialogButton.ts +6 -3
- package/src/Client/UI/Components/MessageBox.ts +417 -0
- package/src/Client/UI/Components/ThemeDropdown.ts +3 -9
- package/src/Client/UI/Components/Toast.ts +26 -15
- package/src/Client/UI/Components/Toaster.ts +27 -11
|
@@ -10,6 +10,10 @@ export declare class Toaster extends HTMLElement {
|
|
|
10
10
|
* The list of observed attributes.
|
|
11
11
|
*/
|
|
12
12
|
static readonly observedAttributes: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new toaster.
|
|
15
|
+
*/
|
|
16
|
+
constructor();
|
|
13
17
|
/**
|
|
14
18
|
* Value indicating whether to apply a fade transition.
|
|
15
19
|
*/
|
|
@@ -58,9 +62,9 @@ export declare class Toaster extends HTMLElement {
|
|
|
58
62
|
* @param caption The title displayed in the toast header.
|
|
59
63
|
* @param body The child content displayed in the toast body.
|
|
60
64
|
*/
|
|
61
|
-
|
|
65
|
+
show(context: Context, caption: string, body: DocumentFragment | string): void;
|
|
62
66
|
/**
|
|
63
|
-
* Shows
|
|
67
|
+
* Shows a toast.
|
|
64
68
|
* @param toast The toast to show.
|
|
65
69
|
*/
|
|
66
70
|
show(toast: IToast): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toaster.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/Toaster.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAEtC,OAAO,EAAC,QAAQ,EAAQ,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,YAAY,CAAC;AAEvC;;GAEG;AACH,qBAAa,OAAQ,SAAQ,WAAW;;IAEvC;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,kBAAkB,WAAgB;
|
|
1
|
+
{"version":3,"file":"Toaster.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/Toaster.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAEtC,OAAO,EAAC,QAAQ,EAAQ,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,YAAY,CAAC;AAEvC;;GAEG;AACH,qBAAa,OAAQ,SAAQ,WAAW;;IAEvC;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,kBAAkB,WAAgB;IAOlD;;OAEG;;IAaH;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IACD,IAAI,SAAS,CAAC,KAAK,EAAE,OAAO,EAE3B;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,OAAO,CAGrB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAEzB;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,IAAI,CAAC,MAAM,CAGzB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAE7B;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,MAAM,CAGlB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAEtB;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,GAAC,IAAI,CAGtB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,GAAC,IAAI,EAG1B;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,QAAQ,CAGvB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAE3B;IAED;;;;;OAKG;IACH,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,GAAG,IAAI;IAO/F;;;;;OAKG;IACH,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAC,MAAM,GAAG,IAAI;IAE5E;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;CAsCzB;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,mBAAmB,EAAE,OAAO,CAAC;KAC7B;CACD"}
|
|
@@ -13,6 +13,14 @@ export class Toaster extends HTMLElement {
|
|
|
13
13
|
* The template for a toast.
|
|
14
14
|
*/
|
|
15
15
|
#toastTemplate = this.querySelector("template").content;
|
|
16
|
+
/**
|
|
17
|
+
* Creates a new toaster.
|
|
18
|
+
*/
|
|
19
|
+
constructor() {
|
|
20
|
+
super();
|
|
21
|
+
for (const toast of this.querySelectorAll("toaster-item"))
|
|
22
|
+
toast.addEventListener("hidden.bs.toast", () => toast.remove());
|
|
23
|
+
}
|
|
16
24
|
/**
|
|
17
25
|
* Registers the component.
|
|
18
26
|
*/
|
|
@@ -75,7 +83,10 @@ export class Toaster extends HTMLElement {
|
|
|
75
83
|
return value.trim() || null;
|
|
76
84
|
}
|
|
77
85
|
set icon(value) {
|
|
78
|
-
|
|
86
|
+
if (value)
|
|
87
|
+
this.setAttribute("icon", value);
|
|
88
|
+
else
|
|
89
|
+
this.removeAttribute("icon");
|
|
79
90
|
}
|
|
80
91
|
/**
|
|
81
92
|
* The toaster placement.
|
|
@@ -104,21 +115,17 @@ export class Toaster extends HTMLElement {
|
|
|
104
115
|
}
|
|
105
116
|
/**
|
|
106
117
|
* Shows a toast.
|
|
107
|
-
* @param
|
|
118
|
+
* @param toast The toast to show, or the contextual modifier.
|
|
108
119
|
* @param caption The title displayed in the toast header.
|
|
109
120
|
* @param body The child content displayed in the toast body.
|
|
110
121
|
*/
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Shows the specified toast.
|
|
116
|
-
* @param toast The toast to show.
|
|
117
|
-
*/
|
|
118
|
-
show(toast) {
|
|
122
|
+
show(toast, caption = "", body = "") {
|
|
123
|
+
if (typeof toast == "string")
|
|
124
|
+
toast = { context: toast, caption, body };
|
|
119
125
|
const item = document.createElement("toaster-item");
|
|
120
|
-
|
|
121
|
-
|
|
126
|
+
const childContent = this.#toastTemplate.cloneNode(true).querySelector(".toast");
|
|
127
|
+
childContent.addEventListener("hidden.bs.toast", () => item.remove());
|
|
128
|
+
item.appendChild(childContent);
|
|
122
129
|
item.animation = toast.animation ?? this.animation;
|
|
123
130
|
item.autoHide = toast.autoHide ?? this.autoHide;
|
|
124
131
|
item.body = typeof toast.body == "string" ? createDocumentFragment(toast.body) : toast.body;
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"name": "@cedx/base",
|
|
8
8
|
"repository": "cedx/base",
|
|
9
9
|
"type": "module",
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.18.0",
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@playwright/browser-chromium": "^1.55.0",
|
|
13
13
|
"@types/bootstrap": "^5.2.10",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"mocha": "^11.7.1",
|
|
22
22
|
"playwright": "^1.55.0",
|
|
23
23
|
"serve-handler": "^6.1.6",
|
|
24
|
-
"typedoc": "^0.28.
|
|
24
|
+
"typedoc": "^0.28.11",
|
|
25
25
|
"typescript": "^5.9.2",
|
|
26
26
|
"typescript-eslint": "^8.40.0"
|
|
27
27
|
},
|
|
@@ -58,7 +58,8 @@ export class DialogButton extends HTMLElement {
|
|
|
58
58
|
return Object.values(Context).includes(value) ? value : null;
|
|
59
59
|
}
|
|
60
60
|
set context(value: Context|null) {
|
|
61
|
-
this.
|
|
61
|
+
if (value) this.setAttribute("context", value);
|
|
62
|
+
else this.removeAttribute("context");
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
/**
|
|
@@ -69,7 +70,8 @@ export class DialogButton extends HTMLElement {
|
|
|
69
70
|
return value.trim() || null;
|
|
70
71
|
}
|
|
71
72
|
set icon(value: string|null) {
|
|
72
|
-
this.
|
|
73
|
+
if (value) this.setAttribute("icon", value);
|
|
74
|
+
else this.removeAttribute("icon");
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
/**
|
|
@@ -101,7 +103,8 @@ export class DialogButton extends HTMLElement {
|
|
|
101
103
|
return Object.values(Variant).includes(value) ? value : null;
|
|
102
104
|
}
|
|
103
105
|
set variant(value: Variant|null) {
|
|
104
|
-
this.
|
|
106
|
+
if (value) this.setAttribute("variant", value);
|
|
107
|
+
else this.removeAttribute("variant");
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
/**
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import {Modal} from "bootstrap";
|
|
2
|
+
import {Context, getIcon, toCss} from "../Context.js";
|
|
3
|
+
import {DialogResult} from "../DialogResult.js";
|
|
4
|
+
import {createDocumentFragment} from "../ElementExtensions.js";
|
|
5
|
+
import {Variant} from "../Variant.js";
|
|
6
|
+
import type {DialogButton, IDialogButton} from "./DialogButton.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Represents a message to display in a dialog box.
|
|
10
|
+
*/
|
|
11
|
+
export interface IMessage {
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Value indicating whether to apply a fade transition.
|
|
15
|
+
*/
|
|
16
|
+
animation?: boolean;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The child content displayed in the body.
|
|
20
|
+
*/
|
|
21
|
+
body: DocumentFragment|string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The title displayed in the header.
|
|
25
|
+
*/
|
|
26
|
+
caption: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Value indicating whether to vertically center this message box.
|
|
30
|
+
*/
|
|
31
|
+
centered?: boolean;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A contextual modifier.
|
|
35
|
+
*/
|
|
36
|
+
context?: Context;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The child content displayed in the footer.
|
|
40
|
+
*/
|
|
41
|
+
footer?: DocumentFragment|string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The icon displayed next to the body.
|
|
45
|
+
*/
|
|
46
|
+
icon?: string|null;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Value indicating whether the body is scrollable.
|
|
50
|
+
*/
|
|
51
|
+
scrollable?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Displays a message window, also known as dialog box, which presents a message to the user.
|
|
56
|
+
*/
|
|
57
|
+
export class MessageBox extends HTMLElement {
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The list of observed attributes.
|
|
61
|
+
*/
|
|
62
|
+
static readonly observedAttributes = ["animation", "caption", "centered", "context", "icon", "modal", "scrollable"];
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The template for a button.
|
|
66
|
+
*/
|
|
67
|
+
readonly #buttonTemplate: DocumentFragment = this.querySelector("template")!.content;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The underlying Bootstrap modal.
|
|
71
|
+
*/
|
|
72
|
+
#modal!: Modal;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The function invoked to return the dialog box result.
|
|
76
|
+
*/
|
|
77
|
+
#resolve: (value: DialogResult) => void = () => { /* Noop */ };
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The dialog result.
|
|
81
|
+
*/
|
|
82
|
+
#result: DialogResult = DialogResult.None;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Creates a new message box.
|
|
86
|
+
*/
|
|
87
|
+
constructor() {
|
|
88
|
+
super();
|
|
89
|
+
this.firstElementChild!.addEventListener("hidden.bs.modal", () => this.#resolve(this.#result));
|
|
90
|
+
this.querySelector(".btn-close")!.addEventListener("click", this.#close);
|
|
91
|
+
for (const button of this.querySelectorAll(".modal-footer button")) button.addEventListener("click", this.#close);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Registers the component.
|
|
96
|
+
*/
|
|
97
|
+
static {
|
|
98
|
+
customElements.define("message-box", this);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Value indicating whether to apply a fade transition.
|
|
103
|
+
*/
|
|
104
|
+
get animation(): boolean {
|
|
105
|
+
return this.hasAttribute("animation");
|
|
106
|
+
}
|
|
107
|
+
set animation(value: boolean) {
|
|
108
|
+
this.toggleAttribute("animation", value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The child content displayed in the body.
|
|
113
|
+
*/
|
|
114
|
+
set body(value: DocumentFragment) { // eslint-disable-line accessor-pairs
|
|
115
|
+
this.querySelector(".modal-body > div")!.replaceChildren(...value.childNodes);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The title displayed in the header.
|
|
120
|
+
*/
|
|
121
|
+
get caption(): string {
|
|
122
|
+
return (this.getAttribute("caption") ?? "").trim();
|
|
123
|
+
}
|
|
124
|
+
set caption(value: string) {
|
|
125
|
+
this.setAttribute("caption", value);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Value indicating whether to vertically center this message box.
|
|
130
|
+
*/
|
|
131
|
+
get centered(): boolean {
|
|
132
|
+
return this.hasAttribute("centered");
|
|
133
|
+
}
|
|
134
|
+
set centered(value: boolean) {
|
|
135
|
+
this.toggleAttribute("centered", value);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A contextual modifier.
|
|
140
|
+
*/
|
|
141
|
+
get context(): Context {
|
|
142
|
+
const value = this.getAttribute("context") as Context;
|
|
143
|
+
return Object.values(Context).includes(value) ? value : Context.Info;
|
|
144
|
+
}
|
|
145
|
+
set context(value: Context) {
|
|
146
|
+
this.setAttribute("context", value);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The child content displayed in the footer.
|
|
151
|
+
*/
|
|
152
|
+
set footer(value: DocumentFragment) { // eslint-disable-line accessor-pairs
|
|
153
|
+
const footer = this.querySelector<HTMLElement>(".modal-footer")!;
|
|
154
|
+
footer.hidden = !value.hasChildNodes();
|
|
155
|
+
footer.replaceChildren(...value.childNodes);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The icon displayed next to the body.
|
|
160
|
+
*/
|
|
161
|
+
get icon(): string|null {
|
|
162
|
+
const value = this.getAttribute("icon") ?? "";
|
|
163
|
+
return value.trim() || null;
|
|
164
|
+
}
|
|
165
|
+
set icon(value: string|null) {
|
|
166
|
+
this.toggleAttribute("icon", Boolean(value));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Value indicating whether to this message box will not close when clicking outside of it.
|
|
171
|
+
*/
|
|
172
|
+
get modal(): boolean {
|
|
173
|
+
return this.hasAttribute("modal");
|
|
174
|
+
}
|
|
175
|
+
set modal(value: boolean) {
|
|
176
|
+
this.toggleAttribute("modal", value);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Value indicating whether to initially show this message box.
|
|
181
|
+
*/
|
|
182
|
+
get open(): boolean {
|
|
183
|
+
return this.hasAttribute("open");
|
|
184
|
+
}
|
|
185
|
+
set open(value: boolean) {
|
|
186
|
+
this.toggleAttribute("open", value);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Value indicating whether the body is scrollable.
|
|
191
|
+
*/
|
|
192
|
+
get scrollable(): boolean {
|
|
193
|
+
return this.hasAttribute("scrollable");
|
|
194
|
+
}
|
|
195
|
+
set scrollable(value: boolean) {
|
|
196
|
+
this.toggleAttribute("scrollable", value);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Method invoked when an attribute has been changed.
|
|
201
|
+
* @param attribute The attribute name.
|
|
202
|
+
* @param oldValue The previous attribute value.
|
|
203
|
+
* @param newValue The new attribute value.
|
|
204
|
+
*/
|
|
205
|
+
attributeChangedCallback(attribute: string, oldValue: string|null, newValue: string|null): void {
|
|
206
|
+
if (newValue != oldValue) switch (attribute) {
|
|
207
|
+
case "animation": this.#updateAnimation(newValue != null); break;
|
|
208
|
+
case "caption": this.#updateCaption(newValue ?? ""); break;
|
|
209
|
+
case "centered": this.#updateCentered(newValue != null); break;
|
|
210
|
+
case "context": this.#updateContext(Object.values(Context).includes(newValue as Context) ? newValue as Context : Context.Info); break;
|
|
211
|
+
case "icon": this.#updateIcon(newValue); break;
|
|
212
|
+
case "modal": this.#updateModal(newValue != null); break;
|
|
213
|
+
case "scrollable": this.#updateScrolling(newValue != null); break;
|
|
214
|
+
// No default
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Shows an alert message with an "OK" button.
|
|
220
|
+
* @param context The contextual modifier.
|
|
221
|
+
* @param caption The title displayed in the header.
|
|
222
|
+
* @param body The child content displayed in the body.
|
|
223
|
+
* @returns The dialog box result.
|
|
224
|
+
*/
|
|
225
|
+
async alert(context: Context, caption: string, body: DocumentFragment|string): Promise<DialogResult> {
|
|
226
|
+
return await this.show(context, caption, body, [
|
|
227
|
+
{label: "OK", value: DialogResult.OK, variant: Variant.Primary}
|
|
228
|
+
]);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Closes this message box.
|
|
233
|
+
* @param result The dialog box result.
|
|
234
|
+
*/
|
|
235
|
+
close(result: DialogResult = DialogResult.None): void {
|
|
236
|
+
this.#result = result;
|
|
237
|
+
this.#modal.hide();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Shows a confirmation message with two buttons, "OK" and "Cancel".
|
|
242
|
+
* @param context The contextual modifier.
|
|
243
|
+
* @param caption The title displayed in the header.
|
|
244
|
+
* @param body The child content displayed in the body.
|
|
245
|
+
* @returns The dialog box result.
|
|
246
|
+
*/
|
|
247
|
+
async confirm(context: Context, caption: string, body: DocumentFragment|string): Promise<DialogResult> {
|
|
248
|
+
return await this.show(context, caption, body, [
|
|
249
|
+
{label: "OK", value: DialogResult.OK, variant: Variant.Primary},
|
|
250
|
+
{label: "Annuler", value: DialogResult.Cancel, variant: Variant.Secondary}
|
|
251
|
+
]);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Method invoked when this component is connected.
|
|
256
|
+
*/
|
|
257
|
+
connectedCallback(): void {
|
|
258
|
+
this.#modal = new Modal(this.firstElementChild!);
|
|
259
|
+
if (this.open) void this.show();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Method invoked when this component is disconnected.
|
|
264
|
+
*/
|
|
265
|
+
disconnectedCallback(): void {
|
|
266
|
+
this.#modal.dispose();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Shows a message.
|
|
271
|
+
* @param message The message to show.
|
|
272
|
+
* @returns The dialog box result.
|
|
273
|
+
*/
|
|
274
|
+
show(message?: IMessage): Promise<DialogResult>;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Shows a message.
|
|
278
|
+
* @param context The contextual modifier.
|
|
279
|
+
* @param caption The title displayed in the header.
|
|
280
|
+
* @param body The child content displayed in the body.
|
|
281
|
+
* @param buttons The buttons displayed in the footer.
|
|
282
|
+
* @returns The dialog box result.
|
|
283
|
+
*/
|
|
284
|
+
show(context: Context, caption: string, body: DocumentFragment|string, buttons?: IDialogButton[]): Promise<DialogResult>;
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Shows a message.
|
|
288
|
+
* @param message The message to show, or the contextual modifier.
|
|
289
|
+
* @param caption The title displayed in the header.
|
|
290
|
+
* @param body The child content displayed in the body.
|
|
291
|
+
* @param buttons The buttons displayed in the footer.
|
|
292
|
+
* @returns The dialog box result.
|
|
293
|
+
*/
|
|
294
|
+
show(message: IMessage|Context|null = null, caption = "", body: DocumentFragment|string = "", buttons: IDialogButton[] = []): Promise<DialogResult> {
|
|
295
|
+
if (typeof message == "string") {
|
|
296
|
+
const footer = document.createDocumentFragment();
|
|
297
|
+
footer.append(...buttons.map(button => this.#createButton(button)));
|
|
298
|
+
message = {context: message, caption, body, footer};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (typeof message == "object" && message) {
|
|
302
|
+
this.body = typeof message.body == "string" ? createDocumentFragment(message.body) : message.body;
|
|
303
|
+
this.caption = message.caption;
|
|
304
|
+
this.context = message.context ?? Context.Info;
|
|
305
|
+
this.icon = message.icon ?? getIcon(this.context);
|
|
306
|
+
|
|
307
|
+
const footer = typeof message.footer == "string" ? createDocumentFragment(message.footer) : (message.footer ?? document.createDocumentFragment());
|
|
308
|
+
for (const button of footer.querySelectorAll("button")) button.addEventListener("click", this.#close);
|
|
309
|
+
this.footer = footer;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const {promise, resolve} = Promise.withResolvers<DialogResult>();
|
|
313
|
+
this.#resolve = resolve;
|
|
314
|
+
this.#result = DialogResult.None;
|
|
315
|
+
this.#modal.show();
|
|
316
|
+
return promise;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Closes this message box.
|
|
321
|
+
* @param event The dispatched event.
|
|
322
|
+
*/
|
|
323
|
+
readonly #close: (event: Event) => void = event => {
|
|
324
|
+
const button = (event.target as Element).closest("button")!;
|
|
325
|
+
this.close(Object.values(DialogResult).includes(button.value as DialogResult) ? button.value as DialogResult : DialogResult.None);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Creates the component instance corresponding to the specified button.
|
|
330
|
+
* @param button An object describing the appearance of the button.
|
|
331
|
+
* @returns The component instance corresponding to the specified button.
|
|
332
|
+
*/
|
|
333
|
+
#createButton(button: IDialogButton): DialogButton {
|
|
334
|
+
const element = document.createElement("dialog-button");
|
|
335
|
+
const childContent = (this.#buttonTemplate.cloneNode(true) as DocumentFragment).querySelector("button")!;
|
|
336
|
+
childContent.addEventListener("click", this.#close);
|
|
337
|
+
element.appendChild(childContent);
|
|
338
|
+
|
|
339
|
+
element.context = button.context ?? null;
|
|
340
|
+
element.icon = button.icon ?? null;
|
|
341
|
+
element.label = button.label ?? "";
|
|
342
|
+
element.value = button.value ?? DialogResult.None;
|
|
343
|
+
element.variant = button.variant ?? null;
|
|
344
|
+
return element;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Updates the message box animation.
|
|
349
|
+
* @param value The new value.
|
|
350
|
+
*/
|
|
351
|
+
#updateAnimation(value: boolean): void {
|
|
352
|
+
this.firstElementChild!.classList.toggle("fade", value);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Updates the title displayed in the header.
|
|
357
|
+
* @param value The new value.
|
|
358
|
+
*/
|
|
359
|
+
#updateCaption(value: string): void {
|
|
360
|
+
this.querySelector(".modal-title")!.textContent = value.trim();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Updates the value indicating whether to vertically center this message box.
|
|
365
|
+
* @param value The new value.
|
|
366
|
+
*/
|
|
367
|
+
#updateCentered(value: boolean): void {
|
|
368
|
+
this.querySelector(".modal-dialog")!.classList.toggle("modal-dialog-centered", value);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Updates the contextual modifier.
|
|
373
|
+
* @param value The new value.
|
|
374
|
+
*/
|
|
375
|
+
#updateContext(value: Context): void {
|
|
376
|
+
const {classList} = this.querySelector(".modal-body > .icon")!;
|
|
377
|
+
classList.remove(...Object.values(Context).map(context => `text-${toCss(context)}`));
|
|
378
|
+
classList.add(`text-${toCss(value)}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Updates the icon displayed next to the body.
|
|
383
|
+
* @param value The new value.
|
|
384
|
+
*/
|
|
385
|
+
#updateIcon(value: string|null): void {
|
|
386
|
+
this.querySelector(".modal-body > .icon")!.textContent = (value ?? "").trim() || getIcon(this.context);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Updates the value indicating whether to this message box will not close when clicking outside of it.
|
|
391
|
+
* @param value The new value.
|
|
392
|
+
*/
|
|
393
|
+
#updateModal(value: boolean): void {
|
|
394
|
+
(this.firstElementChild! as HTMLElement).dataset.bsBackdrop = value ? "static" : "true";
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Updates the body scrolling.
|
|
399
|
+
* @param value The new value.
|
|
400
|
+
*/
|
|
401
|
+
#updateScrolling(value: boolean): void {
|
|
402
|
+
this.querySelector(".modal-dialog")!.classList.toggle("modal-dialog-scrollable", value);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Declaration merging.
|
|
408
|
+
*/
|
|
409
|
+
declare global {
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* The map of HTML tag names.
|
|
413
|
+
*/
|
|
414
|
+
interface HTMLElementTagNameMap {
|
|
415
|
+
"message-box": MessageBox;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
@@ -89,15 +89,9 @@ export class ThemeDropdown extends HTMLElement {
|
|
|
89
89
|
*/
|
|
90
90
|
attributeChangedCallback(attribute: string, oldValue: string|null, newValue: string|null): void {
|
|
91
91
|
if (newValue != oldValue) switch (attribute) {
|
|
92
|
-
case "alignment":
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
case "apptheme":
|
|
96
|
-
this.#updateAppTheme(Object.values(AppTheme).includes(newValue as AppTheme) ? newValue as AppTheme : AppTheme.System);
|
|
97
|
-
break;
|
|
98
|
-
case "label":
|
|
99
|
-
this.#updateLabel(newValue ?? "");
|
|
100
|
-
break;
|
|
92
|
+
case "alignment": this.#updateAlignment(Object.values(Alignment).includes(newValue as Alignment) ? newValue as Alignment : Alignment.End); break;
|
|
93
|
+
case "apptheme": this.#updateAppTheme(Object.values(AppTheme).includes(newValue as AppTheme) ? newValue as AppTheme : AppTheme.System); break;
|
|
94
|
+
case "label": this.#updateLabel(newValue ?? ""); break;
|
|
101
95
|
// No default
|
|
102
96
|
}
|
|
103
97
|
}
|
|
@@ -55,7 +55,7 @@ export class Toast extends HTMLElement {
|
|
|
55
55
|
/**
|
|
56
56
|
* The list of observed attributes.
|
|
57
57
|
*/
|
|
58
|
-
static readonly observedAttributes = ["animation", "caption", "context", "culture", "
|
|
58
|
+
static readonly observedAttributes = ["animation", "autohide", "caption", "context", "culture", "delay", "icon"];
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* The time units.
|
|
@@ -174,7 +174,8 @@ export class Toast extends HTMLElement {
|
|
|
174
174
|
return value.trim() || null;
|
|
175
175
|
}
|
|
176
176
|
set icon(value: string|null) {
|
|
177
|
-
this.
|
|
177
|
+
if (value) this.setAttribute("icon", value);
|
|
178
|
+
else this.removeAttribute("icon");
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
/**
|
|
@@ -196,11 +197,12 @@ export class Toast extends HTMLElement {
|
|
|
196
197
|
attributeChangedCallback(attribute: string, oldValue: string|null, newValue: string|null): void {
|
|
197
198
|
if (newValue != oldValue) switch (attribute) {
|
|
198
199
|
case "animation": this.#updateAnimation(newValue != null); break;
|
|
200
|
+
case "autohide": this.#updateAutoHide(newValue != null); break;
|
|
199
201
|
case "caption": this.#updateCaption(newValue ?? ""); break;
|
|
200
202
|
case "context": this.#updateContext(Object.values(Context).includes(newValue as Context) ? newValue as Context : Context.Info); break;
|
|
201
203
|
case "culture": this.#formatter = new Intl.RelativeTimeFormat((newValue ?? "").trim() || navigator.language, {style: "long"}); break;
|
|
204
|
+
case "delay": this.#updateDelay(Number(newValue)); break;
|
|
202
205
|
case "icon": this.#updateIcon(newValue); break;
|
|
203
|
-
case "open": this.#updateVisibility(newValue != null); break;
|
|
204
206
|
// No default
|
|
205
207
|
}
|
|
206
208
|
}
|
|
@@ -220,8 +222,8 @@ export class Toast extends HTMLElement {
|
|
|
220
222
|
toast.addEventListener("hidden.bs.toast", () => clearInterval(this.#timer));
|
|
221
223
|
toast.addEventListener("show.bs.toast", () => this.#timer = window.setInterval(this.#updateElapsedTime, 1_000));
|
|
222
224
|
|
|
223
|
-
|
|
224
|
-
this
|
|
225
|
+
this.#toast = new BootstrapToast(toast);
|
|
226
|
+
if (this.open) this.show();
|
|
225
227
|
}
|
|
226
228
|
|
|
227
229
|
/**
|
|
@@ -260,11 +262,19 @@ export class Toast extends HTMLElement {
|
|
|
260
262
|
}
|
|
261
263
|
|
|
262
264
|
/**
|
|
263
|
-
* Updates the
|
|
265
|
+
* Updates the value indicating whether to apply a fade transition.
|
|
264
266
|
* @param value The new value.
|
|
265
267
|
*/
|
|
266
268
|
#updateAnimation(value: boolean): void {
|
|
267
|
-
this.firstElementChild
|
|
269
|
+
(this.firstElementChild! as HTMLElement).dataset.bsAnimation = value ? "true" : "false";
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Updates the value indicating whether to automatically hide this toast.
|
|
274
|
+
* @param value The new value.
|
|
275
|
+
*/
|
|
276
|
+
#updateAutoHide(value: boolean): void {
|
|
277
|
+
(this.firstElementChild! as HTMLElement).dataset.bsAutohide = value ? "true" : "false";
|
|
268
278
|
}
|
|
269
279
|
|
|
270
280
|
/**
|
|
@@ -291,6 +301,15 @@ export class Toast extends HTMLElement {
|
|
|
291
301
|
classList.add(`text-${toCss(value)}`);
|
|
292
302
|
}
|
|
293
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Updates the delay to hide the toast.
|
|
306
|
+
* @param value The new value.
|
|
307
|
+
*/
|
|
308
|
+
#updateDelay(value: number): void {
|
|
309
|
+
const delay = Math.max(0, Number.isNaN(value) ? 5_000 : value);
|
|
310
|
+
(this.firstElementChild! as HTMLElement).dataset.bsDelay = delay.toString();
|
|
311
|
+
}
|
|
312
|
+
|
|
294
313
|
/**
|
|
295
314
|
* Updates the label corresponding to the elapsed time.
|
|
296
315
|
*/
|
|
@@ -306,14 +325,6 @@ export class Toast extends HTMLElement {
|
|
|
306
325
|
#updateIcon(value: string|null): void {
|
|
307
326
|
this.querySelector(".toast-header .icon")!.textContent = (value ?? "").trim() || getIcon(this.context);
|
|
308
327
|
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Updates the toast visibility.
|
|
312
|
-
* @param value The new value.
|
|
313
|
-
*/
|
|
314
|
-
#updateVisibility(value: boolean): void {
|
|
315
|
-
this.firstElementChild!.classList.toggle("show", value);
|
|
316
|
-
}
|
|
317
328
|
}
|
|
318
329
|
|
|
319
330
|
/**
|