@cedx/base 0.13.1 → 0.14.1

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  # Belin.io Base
2
- ![.NET](https://badgen.net/badge/.net/%3E%3D9.0/green) ![Version](https://badgen.net/badge/project/v0.13.1/blue) ![Licence](https://badgen.net/badge/licence/MIT/blue)
2
+ ![.NET](https://badgen.net/badge/.net/%3E%3D9.0/green) ![Version](https://badgen.net/badge/project/v0.14.1/blue) ![Licence](https://badgen.net/badge/licence/MIT/blue)
3
3
 
4
4
  Base library by [Cédric Belin](https://belin.io), full stack developer,
5
5
  implemented in [C#](https://learn.microsoft.com/en-us/dotnet/csharp) and [TypeScript](https://www.typescriptlang.org).
@@ -1 +1 @@
1
- {"version":3,"file":"FileExtensions.d.ts","sourceRoot":"","sources":["../src/Client/FileExtensions.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAUzC;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAM,GAAG,IAAI,CAkBvE;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAatC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAOlD"}
1
+ {"version":3,"file":"FileExtensions.d.ts","sourceRoot":"","sources":["../src/Client/FileExtensions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAUzC;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAM,GAAG,IAAI,CAkBvE;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAatC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAOlD"}
@@ -1,4 +1,3 @@
1
- import { Duration } from "./Duration.js";
2
1
  /**
3
2
  * Downloads the specified file.
4
3
  * @param file The file to be downloaded.
@@ -34,7 +33,7 @@ export function open(file, options = {}) {
34
33
  return;
35
34
  clearInterval(timer);
36
35
  URL.revokeObjectURL(url);
37
- }, 5 * Duration.Second);
36
+ }, 5_000);
38
37
  }
39
38
  /**
40
39
  * Prints the specified file.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A component that moves back one page in the session history when clicked.
3
+ */
4
+ export declare class BackButton extends HTMLElement {
5
+ /**
6
+ * Creates a new back button.
7
+ */
8
+ constructor();
9
+ /**
10
+ * The number of pages to go back.
11
+ */
12
+ get steps(): number;
13
+ set steps(value: number);
14
+ }
15
+ /**
16
+ * Declaration merging.
17
+ */
18
+ declare global {
19
+ /**
20
+ * The map of HTML tag names.
21
+ */
22
+ interface HTMLElementTagNameMap {
23
+ "back-button": BackButton;
24
+ }
25
+ }
26
+ //# sourceMappingURL=BackButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackButton.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/BackButton.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,UAAW,SAAQ,WAAW;IAE1C;;OAEG;;IAaH;;OAEG;IACH,IAAI,KAAK,IAAI,MAAM,CAGlB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAEtB;CACD;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,aAAa,EAAE,UAAU,CAAC;KAC1B;CACD"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * A component that moves back one page in the session history when clicked.
3
+ */
4
+ export class BackButton extends HTMLElement {
5
+ /**
6
+ * Creates a new back button.
7
+ */
8
+ constructor() {
9
+ super();
10
+ this.addEventListener("click", () => history.go(-this.steps));
11
+ }
12
+ /**
13
+ * Registers the component.
14
+ */
15
+ static {
16
+ customElements.define("back-button", this);
17
+ }
18
+ /**
19
+ * The number of pages to go back.
20
+ */
21
+ get steps() {
22
+ const value = Number(this.getAttribute("steps"));
23
+ return Math.max(0, Number.isNaN(value) ? 1 : value);
24
+ }
25
+ set steps(value) {
26
+ this.setAttribute("steps", value.toString());
27
+ }
28
+ }
@@ -25,7 +25,7 @@ export class LoadingIndicator extends HTMLElement {
25
25
  */
26
26
  stop(options = {}) {
27
27
  this.#requestCount--;
28
- if (options.force || this.#requestCount <= 0) {
28
+ if (this.#requestCount <= 0 || options.force) {
29
29
  this.#requestCount = 0;
30
30
  this.hidden = true;
31
31
  }
@@ -1,4 +1,4 @@
1
- import { Context } from "@cedx/base/UI/Context.js";
1
+ import { Context } from "../Context.js";
2
2
  /**
3
3
  * Represents a notification.
4
4
  */
@@ -23,6 +23,10 @@ export declare class Toast extends HTMLElement {
23
23
  */
24
24
  get caption(): string;
25
25
  set caption(value: string);
26
+ /**
27
+ * The child content displayed in the body.
28
+ */
29
+ set childContent(value: DocumentFragment);
26
30
  /**
27
31
  * A contextual modifier.
28
32
  */
@@ -1 +1 @@
1
- {"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/Toast.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,OAAO,EAAiB,MAAM,0BAA0B,CAAC;AAGjE;;GAEG;AACH,qBAAa,KAAM,SAAQ,WAAW;;IAErC;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,kBAAkB,WAA6C;IAuC/E;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IACD,IAAI,SAAS,CAAC,KAAK,EAAE,OAAO,EAG3B;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAG1B;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,MAAM,CAEpB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,MAAM,EAExB;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,WAAW,IAAI,MAAM,CAExB;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED;;;;;OAKG;IACH,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,GAAG,IAAI;IAU/F;;OAEG;IACH,iBAAiB,IAAI,IAAI;IASzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAK5B;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,IAAI,IAAI,IAAI;CA+DZ;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,cAAc,EAAE,KAAK,CAAC;KACtB;CACD"}
1
+ {"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/Toast.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,OAAO,EAAiB,MAAM,eAAe,CAAC;AAEtD;;GAEG;AACH,qBAAa,KAAM,SAAQ,WAAW;;IAErC;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,kBAAkB,WAA6C;IAkC/E;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IACD,IAAI,SAAS,CAAC,KAAK,EAAE,OAAO,EAG3B;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAG1B;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,MAAM,CAEpB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,MAAM,EAExB;IAED;;OAEG;IACH,IAAI,YAAY,CAAC,KAAK,EAAE,gBAAgB,EAEvC;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,WAAW,IAAI,MAAM,CAExB;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAGjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED;;;;;OAKG;IACH,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,GAAG,IAAI;IAkB/F;;OAEG;IACH,iBAAiB,IAAI,IAAI;IASzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAK5B;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,IAAI,IAAI,IAAI;CA+DZ;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,cAAc,EAAE,KAAK,CAAC;KACtB;CACD"}
@@ -1,6 +1,5 @@
1
- import { Duration } from "@cedx/base/Duration.js";
2
- import { Context, getIcon, toCss } from "@cedx/base/UI/Context.js";
3
1
  import { Toast as BootstrapToast } from "bootstrap";
2
+ import { Context, getIcon, toCss } from "../Context.js";
4
3
  /**
5
4
  * Represents a notification.
6
5
  */
@@ -17,10 +16,6 @@ export class Toast extends HTMLElement {
17
16
  * The formatter used to format the relative time.
18
17
  */
19
18
  #formatter;
20
- /**
21
- * The toast header.
22
- */
23
- #header = this.querySelector(".toast-header");
24
19
  /**
25
20
  * The time at which this component was initially shown.
26
21
  */
@@ -72,6 +67,12 @@ export class Toast extends HTMLElement {
72
67
  set caption(value) {
73
68
  this.setAttribute("caption", value);
74
69
  }
70
+ /**
71
+ * The child content displayed in the body.
72
+ */
73
+ set childContent(value) {
74
+ this.querySelector(".toast-body").replaceChildren(...value.childNodes);
75
+ }
75
76
  /**
76
77
  * A contextual modifier.
77
78
  */
@@ -97,7 +98,7 @@ export class Toast extends HTMLElement {
97
98
  */
98
99
  get delay() {
99
100
  const value = Number(this.getAttribute("delay"));
100
- return Math.max(1, Number.isNaN(value) ? 5_000 : value);
101
+ return Math.max(0, Number.isNaN(value) ? 5_000 : value);
101
102
  }
102
103
  set delay(value) {
103
104
  this.setAttribute("delay", value.toString());
@@ -113,7 +114,7 @@ export class Toast extends HTMLElement {
113
114
  */
114
115
  get icon() {
115
116
  const value = this.getAttribute("icon") ?? "";
116
- return value.trim() || getIcon(Context.Info);
117
+ return value.trim() || getIcon(this.context);
117
118
  }
118
119
  set icon(value) {
119
120
  this.setAttribute("icon", value);
@@ -148,7 +149,7 @@ export class Toast extends HTMLElement {
148
149
  connectedCallback() {
149
150
  const toast = this.querySelector(".toast");
150
151
  toast.addEventListener("hidden.bs.toast", () => clearInterval(this.#timer));
151
- toast.addEventListener("show.bs.toast", () => this.#timer = window.setInterval(this.#updateElapsedTime, Duration.Second));
152
+ toast.addEventListener("show.bs.toast", () => this.#timer = window.setInterval(this.#updateElapsedTime, 1_000));
152
153
  const { animation, autoHide: autohide, delay } = this;
153
154
  this.#toast = new BootstrapToast(toast, { animation, autohide, delay });
154
155
  }
@@ -193,7 +194,7 @@ export class Toast extends HTMLElement {
193
194
  * @param value The new value.
194
195
  */
195
196
  #updateCaption(value) {
196
- this.#header.querySelector("b").textContent = value.trim();
197
+ this.querySelector(".toast-header b").textContent = value.trim();
197
198
  }
198
199
  /**
199
200
  * Updates the title displayed in the header.
@@ -201,25 +202,25 @@ export class Toast extends HTMLElement {
201
202
  */
202
203
  #updateContext(value) {
203
204
  const contexts = Object.values(Context);
204
- let { classList } = this.#header;
205
+ let { classList } = this.querySelector(".toast-header");
205
206
  classList.remove(...contexts.map(context => `toast-header-${toCss(context)}`));
206
- classList.add(`toast-header-${value}`);
207
- ({ classList } = this.#header.querySelector(".icon"));
207
+ classList.add(`toast-header-${toCss(value)}`);
208
+ ({ classList } = this.querySelector(".toast-header .icon"));
208
209
  classList.remove(...contexts.map(context => `text-${toCss(context)}`));
209
- classList.add(`text-${value}`);
210
+ classList.add(`text-${toCss(value)}`);
210
211
  }
211
212
  /**
212
213
  * Updates the label corresponding to the elapsed time.
213
214
  */
214
215
  #updateElapsedTime = () => {
215
216
  const { elapsedTime } = this;
216
- this.#header.querySelector("small").textContent = elapsedTime > 0 ? this.#formatTime(elapsedTime / Duration.Second) : "";
217
+ this.querySelector(".toast-header small").textContent = elapsedTime > 0 ? this.#formatTime(elapsedTime / 1_000) : "";
217
218
  };
218
219
  /**
219
220
  * Updates the icon displayed next to the caption.
220
221
  * @param value The new value.
221
222
  */
222
223
  #updateIcon(value) {
223
- this.#header.querySelector(".icon").textContent = value.trim() || getIcon(Context.Info);
224
+ this.querySelector(".toast-header .icon").textContent = value.trim() || getIcon(this.context);
224
225
  }
225
226
  }
@@ -0,0 +1,115 @@
1
+ import { Context } from "../Context.js";
2
+ import { Position } from "../Position.js";
3
+ /**
4
+ * Represents a notification.
5
+ */
6
+ export interface IToast {
7
+ /**
8
+ * Value indicating whether to apply a fade transition.
9
+ */
10
+ animation?: boolean;
11
+ /**
12
+ * Value indicating whether to automatically hide the toast.
13
+ */
14
+ autoHide?: boolean;
15
+ /**
16
+ * The title displayed in the header.
17
+ */
18
+ caption: string;
19
+ /**
20
+ * The child content displayed in the body.
21
+ */
22
+ childContent: DocumentFragment | string;
23
+ /**
24
+ * The default contextual modifier.
25
+ */
26
+ context?: Context;
27
+ /**
28
+ * The culture used to format the relative time.
29
+ */
30
+ culture?: Intl.Locale;
31
+ /**
32
+ * The delay, in milliseconds, to hide the toast.
33
+ */
34
+ delay?: number;
35
+ /**
36
+ * The icon displayed next to the caption.
37
+ */
38
+ icon?: string;
39
+ }
40
+ /**
41
+ * Manages the notification messages.
42
+ */
43
+ export declare class Toaster extends HTMLElement {
44
+ #private;
45
+ /**
46
+ * The list of observed attributes.
47
+ */
48
+ static readonly observedAttributes: string[];
49
+ /**
50
+ * Value indicating whether to apply a fade transition.
51
+ */
52
+ get animation(): boolean;
53
+ set animation(value: boolean);
54
+ /**
55
+ * Value indicating whether to automatically hide the toasts.
56
+ */
57
+ get autoHide(): boolean;
58
+ set autoHide(value: boolean);
59
+ /**
60
+ * The default contextual modifier.
61
+ */
62
+ get context(): Context;
63
+ set context(value: Context);
64
+ /**
65
+ * The default culture used to format the relative times.
66
+ */
67
+ get culture(): Intl.Locale;
68
+ set culture(value: Intl.Locale);
69
+ /**
70
+ * The default delay, in milliseconds, to hide the toasts.
71
+ */
72
+ get delay(): number;
73
+ set delay(value: number);
74
+ /**
75
+ * The default icon displayed next to the captions.
76
+ */
77
+ get icon(): string;
78
+ set icon(value: string);
79
+ /**
80
+ * The toaster placement.
81
+ */
82
+ get position(): Position;
83
+ set position(value: Position);
84
+ /**
85
+ * Method invoked when an attribute has been changed.
86
+ * @param attribute The attribute name.
87
+ * @param oldValue The previous attribute value.
88
+ * @param newValue The new attribute value.
89
+ */
90
+ attributeChangedCallback(attribute: string, oldValue: string | null, newValue: string | null): void;
91
+ /**
92
+ * Shows a toast.
93
+ * @param context The contextual modifier.
94
+ * @param caption The title displayed in the toast header.
95
+ * @param childContent The child content displayed in the toast body.
96
+ */
97
+ notify(context: Context, caption: string, childContent: DocumentFragment | string): void;
98
+ /**
99
+ * Shows the specified toast.
100
+ * @param toast The toast to show.
101
+ */
102
+ show(toast: IToast): void;
103
+ }
104
+ /**
105
+ * Declaration merging.
106
+ */
107
+ declare global {
108
+ /**
109
+ * The map of HTML tag names.
110
+ */
111
+ interface HTMLElementTagNameMap {
112
+ "toaster-container": Toaster;
113
+ }
114
+ }
115
+ //# sourceMappingURL=Toaster.d.ts.map
@@ -0,0 +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;AAE/C;;GAEG;AACH,MAAM,WAAW,MAAM;IAEtB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,YAAY,EAAE,gBAAgB,GAAC,MAAM,CAAC;IAEtC;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC;IAEtB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,qBAAa,OAAQ,SAAQ,WAAW;;IAEvC;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,kBAAkB,WAAgB;IAclD;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IACD,IAAI,SAAS,CAAC,KAAK,EAAE,OAAO,EAG3B;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAG1B;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,CAEjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;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;IAS/F;;;;;OAKG;IACH,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,gBAAgB,GAAC,MAAM,GAAG,IAAI;IAItF;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;CA2BzB;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,mBAAmB,EAAE,OAAO,CAAC;KAC7B;CACD"}
@@ -0,0 +1,147 @@
1
+ import { Context } from "../Context.js";
2
+ import { createDocumentFragment } from "../ElementExtensions.js";
3
+ import { Position, toCss } from "../Position.js";
4
+ /**
5
+ * Manages the notification messages.
6
+ */
7
+ export class Toaster extends HTMLElement {
8
+ /**
9
+ * The list of observed attributes.
10
+ */
11
+ static observedAttributes = ["position"];
12
+ /**
13
+ * The template for a toast.
14
+ */
15
+ #toastTemplate = this.querySelector("template").content;
16
+ /**
17
+ * Registers the component.
18
+ */
19
+ static {
20
+ customElements.define("toaster-container", this);
21
+ }
22
+ /**
23
+ * Value indicating whether to apply a fade transition.
24
+ */
25
+ get animation() {
26
+ return this.hasAttribute("animation");
27
+ }
28
+ set animation(value) {
29
+ if (value)
30
+ this.setAttribute("animation", "");
31
+ else
32
+ this.removeAttribute("animation");
33
+ }
34
+ /**
35
+ * Value indicating whether to automatically hide the toasts.
36
+ */
37
+ get autoHide() {
38
+ return this.hasAttribute("autohide");
39
+ }
40
+ set autoHide(value) {
41
+ if (value)
42
+ this.setAttribute("autohide", "");
43
+ else
44
+ this.removeAttribute("autohide");
45
+ }
46
+ /**
47
+ * The default contextual modifier.
48
+ */
49
+ get context() {
50
+ const value = this.getAttribute("context");
51
+ return Object.values(Context).includes(value) ? value : Context.Info;
52
+ }
53
+ set context(value) {
54
+ this.setAttribute("context", value);
55
+ }
56
+ /**
57
+ * The default culture used to format the relative times.
58
+ */
59
+ get culture() {
60
+ const value = this.getAttribute("culture") ?? "";
61
+ return new Intl.Locale(value.trim() || navigator.language);
62
+ }
63
+ set culture(value) {
64
+ this.setAttribute("culture", value.toString());
65
+ }
66
+ /**
67
+ * The default delay, in milliseconds, to hide the toasts.
68
+ */
69
+ get delay() {
70
+ const value = Number(this.getAttribute("delay"));
71
+ return Math.max(1, Number.isNaN(value) ? 5_000 : value);
72
+ }
73
+ set delay(value) {
74
+ this.setAttribute("delay", value.toString());
75
+ }
76
+ /**
77
+ * The default icon displayed next to the captions.
78
+ */
79
+ get icon() {
80
+ return (this.getAttribute("icon") ?? "").trim();
81
+ }
82
+ set icon(value) {
83
+ this.setAttribute("icon", value);
84
+ }
85
+ /**
86
+ * The toaster placement.
87
+ */
88
+ get position() {
89
+ const value = this.getAttribute("position");
90
+ return Object.values(Position).includes(value) ? value : Position.BottomEnd;
91
+ }
92
+ set position(value) {
93
+ this.setAttribute("position", value);
94
+ }
95
+ /**
96
+ * Method invoked when an attribute has been changed.
97
+ * @param attribute The attribute name.
98
+ * @param oldValue The previous attribute value.
99
+ * @param newValue The new attribute value.
100
+ */
101
+ attributeChangedCallback(attribute, oldValue, newValue) {
102
+ if (newValue != oldValue)
103
+ switch (attribute) {
104
+ case "position":
105
+ this.#updatePosition(Object.values(Position).includes(newValue) ? newValue : Position.BottomEnd);
106
+ break;
107
+ // No default
108
+ }
109
+ }
110
+ /**
111
+ * Shows a toast.
112
+ * @param context The contextual modifier.
113
+ * @param caption The title displayed in the toast header.
114
+ * @param childContent The child content displayed in the toast body.
115
+ */
116
+ notify(context, caption, childContent) {
117
+ this.show({ context, caption, childContent });
118
+ }
119
+ /**
120
+ * Shows the specified toast.
121
+ * @param toast The toast to show.
122
+ */
123
+ show(toast) {
124
+ const item = document.createElement("toaster-item");
125
+ item.addEventListener("hidden.bs.toast", () => item.remove());
126
+ item.appendChild(this.#toastTemplate.cloneNode(true).querySelector(".toast"));
127
+ item.animation = toast.animation ?? this.animation;
128
+ item.autoHide = toast.autoHide ?? this.autoHide;
129
+ item.caption = toast.caption;
130
+ item.childContent = typeof toast.childContent == "string" ? createDocumentFragment(toast.childContent) : toast.childContent;
131
+ item.context = toast.context ?? this.context;
132
+ item.culture = toast.culture ?? this.culture;
133
+ item.delay = toast.delay ?? this.delay;
134
+ item.icon = toast.icon ?? this.icon;
135
+ this.firstElementChild.appendChild(item);
136
+ item.show();
137
+ }
138
+ /**
139
+ * Updates the toaster placement.
140
+ * @param value The new value.
141
+ */
142
+ #updatePosition(value) {
143
+ const { classList } = this.firstElementChild;
144
+ classList.remove(...Object.values(Position).flatMap(position => toCss(position).split(" ")));
145
+ classList.add(...toCss(value).split(" "));
146
+ }
147
+ }
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.13.1",
10
+ "version": "0.14.1",
11
11
  "devDependencies": {
12
12
  "@playwright/browser-chromium": "^1.55.0",
13
13
  "@types/bootstrap": "^5.2.10",
@@ -1,5 +1,3 @@
1
- import {Duration} from "./Duration.js";
2
-
3
1
  /**
4
2
  * Downloads the specified file.
5
3
  * @param file The file to be downloaded.
@@ -38,7 +36,7 @@ export function open(file: File, options: {newTab?: boolean} = {}): void {
38
36
  if (!handle.closed) return;
39
37
  clearInterval(timer);
40
38
  URL.revokeObjectURL(url);
41
- }, 5 * Duration.Second);
39
+ }, 5_000);
42
40
  }
43
41
 
44
42
  /**
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A component that moves back one page in the session history when clicked.
3
+ */
4
+ export class BackButton extends HTMLElement {
5
+
6
+ /**
7
+ * Creates a new back button.
8
+ */
9
+ constructor() {
10
+ super();
11
+ this.addEventListener("click", () => history.go(-this.steps));
12
+ }
13
+
14
+ /**
15
+ * Registers the component.
16
+ */
17
+ static {
18
+ customElements.define("back-button", this);
19
+ }
20
+
21
+ /**
22
+ * The number of pages to go back.
23
+ */
24
+ get steps(): number {
25
+ const value = Number(this.getAttribute("steps"));
26
+ return Math.max(0, Number.isNaN(value) ? 1 : value);
27
+ }
28
+ set steps(value: number) {
29
+ this.setAttribute("steps", value.toString());
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Declaration merging.
35
+ */
36
+ declare global {
37
+
38
+ /**
39
+ * The map of HTML tag names.
40
+ */
41
+ interface HTMLElementTagNameMap {
42
+ "back-button": BackButton;
43
+ }
44
+ }
@@ -29,7 +29,7 @@ export class LoadingIndicator extends HTMLElement {
29
29
  */
30
30
  stop(options: {force?: boolean} = {}): void {
31
31
  this.#requestCount--;
32
- if (options.force || this.#requestCount <= 0) {
32
+ if (this.#requestCount <= 0 || options.force) {
33
33
  this.#requestCount = 0;
34
34
  this.hidden = true;
35
35
  }
@@ -1,6 +1,5 @@
1
- import {Duration} from "@cedx/base/Duration.js";
2
- import {Context, getIcon, toCss} from "@cedx/base/UI/Context.js";
3
1
  import {Toast as BootstrapToast} from "bootstrap";
2
+ import {Context, getIcon, toCss} from "../Context.js";
4
3
 
5
4
  /**
6
5
  * Represents a notification.
@@ -22,11 +21,6 @@ export class Toast extends HTMLElement {
22
21
  */
23
22
  #formatter!: Intl.RelativeTimeFormat;
24
23
 
25
- /**
26
- * The toast header.
27
- */
28
- readonly #header = this.querySelector(".toast-header")!;
29
-
30
24
  /**
31
25
  * The time at which this component was initially shown.
32
26
  */
@@ -81,6 +75,13 @@ export class Toast extends HTMLElement {
81
75
  this.setAttribute("caption", value);
82
76
  }
83
77
 
78
+ /**
79
+ * The child content displayed in the body.
80
+ */
81
+ set childContent(value: DocumentFragment) { // eslint-disable-line accessor-pairs
82
+ this.querySelector(".toast-body")!.replaceChildren(...value.childNodes);
83
+ }
84
+
84
85
  /**
85
86
  * A contextual modifier.
86
87
  */
@@ -108,7 +109,7 @@ export class Toast extends HTMLElement {
108
109
  */
109
110
  get delay(): number {
110
111
  const value = Number(this.getAttribute("delay"));
111
- return Math.max(1, Number.isNaN(value) ? 5_000 : value);
112
+ return Math.max(0, Number.isNaN(value) ? 5_000 : value);
112
113
  }
113
114
  set delay(value: number) {
114
115
  this.setAttribute("delay", value.toString());
@@ -126,7 +127,7 @@ export class Toast extends HTMLElement {
126
127
  */
127
128
  get icon(): string {
128
129
  const value = this.getAttribute("icon") ?? "";
129
- return value.trim() || getIcon(Context.Info);
130
+ return value.trim() || getIcon(this.context);
130
131
  }
131
132
  set icon(value: string) {
132
133
  this.setAttribute("icon", value);
@@ -140,10 +141,18 @@ export class Toast extends HTMLElement {
140
141
  */
141
142
  attributeChangedCallback(attribute: string, oldValue: string|null, newValue: string|null): void {
142
143
  if (newValue != oldValue) switch (attribute) {
143
- case "caption": this.#updateCaption(newValue ?? ""); break;
144
- case "context": this.#updateContext(Object.values(Context).includes(newValue as Context) ? newValue as Context : Context.Info); break;
145
- case "culture": this.#formatter = new Intl.RelativeTimeFormat((newValue ?? "").trim() || navigator.language, {style: "long"}); break;
146
- case "icon": this.#updateIcon(newValue ?? ""); break;
144
+ case "caption":
145
+ this.#updateCaption(newValue ?? "");
146
+ break;
147
+ case "context":
148
+ this.#updateContext(Object.values(Context).includes(newValue as Context) ? newValue as Context : Context.Info);
149
+ break;
150
+ case "culture":
151
+ this.#formatter = new Intl.RelativeTimeFormat((newValue ?? "").trim() || navigator.language, {style: "long"});
152
+ break;
153
+ case "icon":
154
+ this.#updateIcon(newValue ?? "");
155
+ break;
147
156
  // No default
148
157
  }
149
158
  }
@@ -154,7 +163,7 @@ export class Toast extends HTMLElement {
154
163
  connectedCallback(): void {
155
164
  const toast = this.querySelector(".toast")!;
156
165
  toast.addEventListener("hidden.bs.toast", () => clearInterval(this.#timer));
157
- toast.addEventListener("show.bs.toast", () => this.#timer = window.setInterval(this.#updateElapsedTime, Duration.Second));
166
+ toast.addEventListener("show.bs.toast", () => this.#timer = window.setInterval(this.#updateElapsedTime, 1_000));
158
167
 
159
168
  const {animation, autoHide: autohide, delay} = this;
160
169
  this.#toast = new BootstrapToast(toast, {animation, autohide, delay});
@@ -207,7 +216,7 @@ export class Toast extends HTMLElement {
207
216
  * @param value The new value.
208
217
  */
209
218
  #updateCaption(value: string): void {
210
- this.#header.querySelector("b")!.textContent = value.trim();
219
+ this.querySelector(".toast-header b")!.textContent = value.trim();
211
220
  }
212
221
 
213
222
  /**
@@ -217,13 +226,13 @@ export class Toast extends HTMLElement {
217
226
  #updateContext(value: Context): void {
218
227
  const contexts = Object.values(Context);
219
228
 
220
- let {classList} = this.#header;
229
+ let {classList} = this.querySelector(".toast-header")!;
221
230
  classList.remove(...contexts.map(context => `toast-header-${toCss(context)}`));
222
- classList.add(`toast-header-${value}`);
231
+ classList.add(`toast-header-${toCss(value)}`);
223
232
 
224
- ({classList} = this.#header.querySelector(".icon")!);
233
+ ({classList} = this.querySelector(".toast-header .icon")!);
225
234
  classList.remove(...contexts.map(context => `text-${toCss(context)}`));
226
- classList.add(`text-${value}`);
235
+ classList.add(`text-${toCss(value)}`);
227
236
  }
228
237
 
229
238
  /**
@@ -231,7 +240,7 @@ export class Toast extends HTMLElement {
231
240
  */
232
241
  readonly #updateElapsedTime: () => void = () => {
233
242
  const {elapsedTime} = this;
234
- this.#header.querySelector("small")!.textContent = elapsedTime > 0 ? this.#formatTime(elapsedTime / Duration.Second) : "";
243
+ this.querySelector(".toast-header small")!.textContent = elapsedTime > 0 ? this.#formatTime(elapsedTime / 1_000) : "";
235
244
  };
236
245
 
237
246
  /**
@@ -239,7 +248,7 @@ export class Toast extends HTMLElement {
239
248
  * @param value The new value.
240
249
  */
241
250
  #updateIcon(value: string): void {
242
- this.#header.querySelector(".icon")!.textContent = value.trim() || getIcon(Context.Info);
251
+ this.querySelector(".toast-header .icon")!.textContent = value.trim() || getIcon(this.context);
243
252
  }
244
253
  }
245
254
 
@@ -0,0 +1,218 @@
1
+ import {Context} from "../Context.js";
2
+ import {createDocumentFragment} from "../ElementExtensions.js";
3
+ import {Position, toCss} from "../Position.js";
4
+
5
+ /**
6
+ * Represents a notification.
7
+ */
8
+ export interface IToast {
9
+
10
+ /**
11
+ * Value indicating whether to apply a fade transition.
12
+ */
13
+ animation?: boolean;
14
+
15
+ /**
16
+ * Value indicating whether to automatically hide the toast.
17
+ */
18
+ autoHide?: boolean;
19
+
20
+ /**
21
+ * The title displayed in the header.
22
+ */
23
+ caption: string;
24
+
25
+ /**
26
+ * The child content displayed in the body.
27
+ */
28
+ childContent: DocumentFragment|string;
29
+
30
+ /**
31
+ * The default contextual modifier.
32
+ */
33
+ context?: Context;
34
+
35
+ /**
36
+ * The culture used to format the relative time.
37
+ */
38
+ culture?: Intl.Locale;
39
+
40
+ /**
41
+ * The delay, in milliseconds, to hide the toast.
42
+ */
43
+ delay?: number;
44
+
45
+ /**
46
+ * The icon displayed next to the caption.
47
+ */
48
+ icon?: string;
49
+ }
50
+
51
+ /**
52
+ * Manages the notification messages.
53
+ */
54
+ export class Toaster extends HTMLElement {
55
+
56
+ /**
57
+ * The list of observed attributes.
58
+ */
59
+ static readonly observedAttributes = ["position"];
60
+
61
+ /**
62
+ * The template for a toast.
63
+ */
64
+ readonly #toastTemplate: DocumentFragment = this.querySelector("template")!.content;
65
+
66
+ /**
67
+ * Registers the component.
68
+ */
69
+ static {
70
+ customElements.define("toaster-container", this);
71
+ }
72
+
73
+ /**
74
+ * Value indicating whether to apply a fade transition.
75
+ */
76
+ get animation(): boolean {
77
+ return this.hasAttribute("animation");
78
+ }
79
+ set animation(value: boolean) {
80
+ if (value) this.setAttribute("animation", "");
81
+ else this.removeAttribute("animation");
82
+ }
83
+
84
+ /**
85
+ * Value indicating whether to automatically hide the toasts.
86
+ */
87
+ get autoHide(): boolean {
88
+ return this.hasAttribute("autohide");
89
+ }
90
+ set autoHide(value: boolean) {
91
+ if (value) this.setAttribute("autohide", "");
92
+ else this.removeAttribute("autohide");
93
+ }
94
+
95
+ /**
96
+ * The default contextual modifier.
97
+ */
98
+ get context(): Context {
99
+ const value = this.getAttribute("context") as Context;
100
+ return Object.values(Context).includes(value) ? value : Context.Info;
101
+ }
102
+ set context(value: Context) {
103
+ this.setAttribute("context", value);
104
+ }
105
+
106
+ /**
107
+ * The default culture used to format the relative times.
108
+ */
109
+ get culture(): Intl.Locale {
110
+ const value = this.getAttribute("culture") ?? "";
111
+ return new Intl.Locale(value.trim() || navigator.language);
112
+ }
113
+ set culture(value: Intl.Locale) {
114
+ this.setAttribute("culture", value.toString());
115
+ }
116
+
117
+ /**
118
+ * The default delay, in milliseconds, to hide the toasts.
119
+ */
120
+ get delay(): number {
121
+ const value = Number(this.getAttribute("delay"));
122
+ return Math.max(1, Number.isNaN(value) ? 5_000 : value);
123
+ }
124
+ set delay(value: number) {
125
+ this.setAttribute("delay", value.toString());
126
+ }
127
+
128
+ /**
129
+ * The default icon displayed next to the captions.
130
+ */
131
+ get icon(): string {
132
+ return (this.getAttribute("icon") ?? "").trim();
133
+ }
134
+ set icon(value: string) {
135
+ this.setAttribute("icon", value);
136
+ }
137
+
138
+ /**
139
+ * The toaster placement.
140
+ */
141
+ get position(): Position {
142
+ const value = this.getAttribute("position") as Position;
143
+ return Object.values(Position).includes(value) ? value : Position.BottomEnd;
144
+ }
145
+ set position(value: Position) {
146
+ this.setAttribute("position", value);
147
+ }
148
+
149
+ /**
150
+ * Method invoked when an attribute has been changed.
151
+ * @param attribute The attribute name.
152
+ * @param oldValue The previous attribute value.
153
+ * @param newValue The new attribute value.
154
+ */
155
+ attributeChangedCallback(attribute: string, oldValue: string|null, newValue: string|null): void {
156
+ if (newValue != oldValue) switch (attribute) {
157
+ case "position":
158
+ this.#updatePosition(Object.values(Position).includes(newValue as Position) ? newValue as Position : Position.BottomEnd);
159
+ break;
160
+ // No default
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Shows a toast.
166
+ * @param context The contextual modifier.
167
+ * @param caption The title displayed in the toast header.
168
+ * @param childContent The child content displayed in the toast body.
169
+ */
170
+ notify(context: Context, caption: string, childContent: DocumentFragment|string): void {
171
+ this.show({context, caption, childContent});
172
+ }
173
+
174
+ /**
175
+ * Shows the specified toast.
176
+ * @param toast The toast to show.
177
+ */
178
+ show(toast: IToast): void {
179
+ const item = document.createElement("toaster-item");
180
+ item.addEventListener("hidden.bs.toast", () => item.remove());
181
+ item.appendChild((this.#toastTemplate.cloneNode(true) as DocumentFragment).querySelector(".toast")!);
182
+
183
+ item.animation = toast.animation ?? this.animation;
184
+ item.autoHide = toast.autoHide ?? this.autoHide;
185
+ item.caption = toast.caption;
186
+ item.childContent = typeof toast.childContent == "string" ? createDocumentFragment(toast.childContent) : toast.childContent;
187
+ item.context = toast.context ?? this.context;
188
+ item.culture = toast.culture ?? this.culture;
189
+ item.delay = toast.delay ?? this.delay;
190
+ item.icon = toast.icon ?? this.icon;
191
+
192
+ this.firstElementChild!.appendChild(item);
193
+ item.show();
194
+ }
195
+
196
+ /**
197
+ * Updates the toaster placement.
198
+ * @param value The new value.
199
+ */
200
+ #updatePosition(value: Position): void {
201
+ const {classList} = this.firstElementChild!;
202
+ classList.remove(...Object.values(Position).flatMap(position => toCss(position).split(" ")));
203
+ classList.add(...toCss(value).split(" "));
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Declaration merging.
209
+ */
210
+ declare global {
211
+
212
+ /**
213
+ * The map of HTML tag names.
214
+ */
215
+ interface HTMLElementTagNameMap {
216
+ "toaster-container": Toaster;
217
+ }
218
+ }