@cedx/base 0.13.0 → 0.14.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 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.0/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.0/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,6 +1,6 @@
1
- import { Context } from "@cedx/base/UI/Context.js";
1
+ import { Context } from "../Context.js";
2
2
  /**
3
- * Manages the notification messages.
3
+ * Represents a notification.
4
4
  */
5
5
  export declare class Toast extends HTMLElement {
6
6
  #private;
@@ -14,7 +14,7 @@ export declare class Toast extends HTMLElement {
14
14
  get animation(): boolean;
15
15
  set animation(value: boolean);
16
16
  /**
17
- * Value indicating whether to automatically hide the notification.
17
+ * Value indicating whether to automatically hide the toast.
18
18
  */
19
19
  get autoHide(): boolean;
20
20
  set autoHide(value: boolean);
@@ -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
  */
@@ -34,7 +38,7 @@ export declare class Toast extends HTMLElement {
34
38
  get culture(): Intl.Locale;
35
39
  set culture(value: Intl.Locale);
36
40
  /**
37
- * The delay, in milliseconds, to hide the notification.
41
+ * The delay, in milliseconds, to hide the toast.
38
42
  */
39
43
  get delay(): number;
40
44
  set delay(value: number);
@@ -71,4 +75,15 @@ export declare class Toast extends HTMLElement {
71
75
  */
72
76
  show(): void;
73
77
  }
78
+ /**
79
+ * Declaration merging.
80
+ */
81
+ declare global {
82
+ /**
83
+ * The map of HTML tag names.
84
+ */
85
+ interface HTMLElementTagNameMap {
86
+ "toaster-item": Toast;
87
+ }
88
+ }
74
89
  //# sourceMappingURL=Toast.d.ts.map
@@ -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;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"}
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,8 +1,7 @@
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
- * Manages the notification messages.
4
+ * Represents a notification.
6
5
  */
7
6
  export class Toast extends HTMLElement {
8
7
  /**
@@ -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
  */
@@ -52,7 +47,7 @@ export class Toast extends HTMLElement {
52
47
  this.removeAttribute("animation");
53
48
  }
54
49
  /**
55
- * Value indicating whether to automatically hide the notification.
50
+ * Value indicating whether to automatically hide the toast.
56
51
  */
57
52
  get autoHide() {
58
53
  return this.hasAttribute("autohide");
@@ -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
  */
@@ -93,11 +94,11 @@ export class Toast extends HTMLElement {
93
94
  this.setAttribute("culture", value.toString());
94
95
  }
95
96
  /**
96
- * The delay, in milliseconds, to hide the notification.
97
+ * The delay, in milliseconds, to hide the toast.
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(Context.Info);
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 with the specified message.
93
+ * @param caption The title displayed in the header.
94
+ * @param message The message to show.
95
+ * @param options The toast options.
96
+ */
97
+ notify(context: Context, caption: string, childContent: DocumentFragment | string): void;
98
+ /**
99
+ * Shows a toast with 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,EAAU,MAAM,eAAe,CAAC;AAE/C,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,CAGjB;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,148 @@
1
+ import { Context, getIcon } 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
+ const value = this.getAttribute("icon") ?? "";
81
+ return value.trim() || getIcon(this.context);
82
+ }
83
+ set icon(value) {
84
+ this.setAttribute("icon", value);
85
+ }
86
+ /**
87
+ * The toaster placement.
88
+ */
89
+ get position() {
90
+ const value = this.getAttribute("position");
91
+ return Object.values(Position).includes(value) ? value : Position.BottomEnd;
92
+ }
93
+ set position(value) {
94
+ this.setAttribute("position", value);
95
+ }
96
+ /**
97
+ * Method invoked when an attribute has been changed.
98
+ * @param attribute The attribute name.
99
+ * @param oldValue The previous attribute value.
100
+ * @param newValue The new attribute value.
101
+ */
102
+ attributeChangedCallback(attribute, oldValue, newValue) {
103
+ if (newValue != oldValue)
104
+ switch (attribute) {
105
+ case "position":
106
+ this.#updatePosition(Object.values(Position).includes(newValue) ? newValue : Position.BottomEnd);
107
+ break;
108
+ // No default
109
+ }
110
+ }
111
+ /**
112
+ * Shows a toast with the specified message.
113
+ * @param caption The title displayed in the header.
114
+ * @param message The message to show.
115
+ * @param options The toast options.
116
+ */
117
+ notify(context, caption, childContent) {
118
+ this.show({ context, caption, childContent });
119
+ }
120
+ /**
121
+ * Shows a toast with the specified toast.
122
+ * @param toast The toast to show.
123
+ */
124
+ show(toast) {
125
+ const item = document.createElement("toaster-item");
126
+ item.addEventListener("hidden.bs.toast", () => item.remove());
127
+ item.appendChild(this.#toastTemplate.cloneNode(true).querySelector(".toast"));
128
+ item.animation = toast.animation ?? this.animation;
129
+ item.autoHide = toast.autoHide ?? this.autoHide;
130
+ item.caption = toast.caption;
131
+ item.childContent = typeof toast.childContent == "string" ? createDocumentFragment(toast.childContent) : toast.childContent;
132
+ item.context = toast.context ?? this.context;
133
+ item.culture = toast.culture ?? this.culture;
134
+ item.delay = toast.delay ?? this.delay;
135
+ item.icon = toast.icon ?? this.icon;
136
+ this.firstElementChild.appendChild(item);
137
+ item.show();
138
+ }
139
+ /**
140
+ * Updates the toaster placement.
141
+ * @param value The new value.
142
+ */
143
+ #updatePosition(value) {
144
+ const { classList } = this.firstElementChild;
145
+ classList.remove(...Object.values(Position).flatMap(position => toCss(position).split(" ")));
146
+ classList.add(...toCss(value).split(" "));
147
+ }
148
+ }
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.0",
10
+ "version": "0.14.0",
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,9 +1,8 @@
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
- * Manages the notification messages.
5
+ * Represents a notification.
7
6
  */
8
7
  export class Toast extends HTMLElement {
9
8
 
@@ -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
  */
@@ -61,7 +55,7 @@ export class Toast extends HTMLElement {
61
55
  }
62
56
 
63
57
  /**
64
- * Value indicating whether to automatically hide the notification.
58
+ * Value indicating whether to automatically hide the toast.
65
59
  */
66
60
  get autoHide(): boolean {
67
61
  return this.hasAttribute("autohide");
@@ -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
  */
@@ -104,11 +105,11 @@ export class Toast extends HTMLElement {
104
105
  }
105
106
 
106
107
  /**
107
- * The delay, in milliseconds, to hide the notification.
108
+ * The delay, in milliseconds, to hide the toast.
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);
@@ -150,7 +151,7 @@ export class Toast extends HTMLElement {
150
151
  this.#formatter = new Intl.RelativeTimeFormat((newValue ?? "").trim() || navigator.language, {style: "long"});
151
152
  break;
152
153
  case "icon":
153
- this.#updateIcon(newValue ?? "")
154
+ this.#updateIcon(newValue ?? "");
154
155
  break;
155
156
  // No default
156
157
  }
@@ -162,7 +163,7 @@ export class Toast extends HTMLElement {
162
163
  connectedCallback(): void {
163
164
  const toast = this.querySelector(".toast")!;
164
165
  toast.addEventListener("hidden.bs.toast", () => clearInterval(this.#timer));
165
- 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));
166
167
 
167
168
  const {animation, autoHide: autohide, delay} = this;
168
169
  this.#toast = new BootstrapToast(toast, {animation, autohide, delay});
@@ -215,7 +216,7 @@ export class Toast extends HTMLElement {
215
216
  * @param value The new value.
216
217
  */
217
218
  #updateCaption(value: string): void {
218
- this.#header.querySelector("b")!.textContent = value.trim();
219
+ this.querySelector(".toast-header b")!.textContent = value.trim();
219
220
  }
220
221
 
221
222
  /**
@@ -225,13 +226,13 @@ export class Toast extends HTMLElement {
225
226
  #updateContext(value: Context): void {
226
227
  const contexts = Object.values(Context);
227
228
 
228
- let {classList} = this.#header;
229
+ let {classList} = this.querySelector(".toast-header")!;
229
230
  classList.remove(...contexts.map(context => `toast-header-${toCss(context)}`));
230
- classList.add(`toast-header-${value}`);
231
+ classList.add(`toast-header-${toCss(value)}`);
231
232
 
232
- ({classList} = this.#header.querySelector(".icon")!);
233
+ ({classList} = this.querySelector(".toast-header .icon")!);
233
234
  classList.remove(...contexts.map(context => `text-${toCss(context)}`));
234
- classList.add(`text-${value}`);
235
+ classList.add(`text-${toCss(value)}`);
235
236
  }
236
237
 
237
238
  /**
@@ -239,7 +240,7 @@ export class Toast extends HTMLElement {
239
240
  */
240
241
  readonly #updateElapsedTime: () => void = () => {
241
242
  const {elapsedTime} = this;
242
- 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) : "";
243
244
  };
244
245
 
245
246
  /**
@@ -247,6 +248,19 @@ export class Toast extends HTMLElement {
247
248
  * @param value The new value.
248
249
  */
249
250
  #updateIcon(value: string): void {
250
- this.#header.querySelector(".icon")!.textContent = value.trim() || getIcon(Context.Info);
251
+ this.querySelector(".toast-header .icon")!.textContent = value.trim() || getIcon(Context.Info);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Declaration merging.
257
+ */
258
+ declare global {
259
+
260
+ /**
261
+ * The map of HTML tag names.
262
+ */
263
+ interface HTMLElementTagNameMap {
264
+ "toaster-item": Toast;
251
265
  }
252
266
  }
@@ -0,0 +1,219 @@
1
+ import {Context, getIcon} 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
+ const value = this.getAttribute("icon") ?? "";
133
+ return value.trim() || getIcon(this.context);
134
+ }
135
+ set icon(value: string) {
136
+ this.setAttribute("icon", value);
137
+ }
138
+
139
+ /**
140
+ * The toaster placement.
141
+ */
142
+ get position(): Position {
143
+ const value = this.getAttribute("position") as Position;
144
+ return Object.values(Position).includes(value) ? value : Position.BottomEnd;
145
+ }
146
+ set position(value: Position) {
147
+ this.setAttribute("position", value);
148
+ }
149
+
150
+ /**
151
+ * Method invoked when an attribute has been changed.
152
+ * @param attribute The attribute name.
153
+ * @param oldValue The previous attribute value.
154
+ * @param newValue The new attribute value.
155
+ */
156
+ attributeChangedCallback(attribute: string, oldValue: string|null, newValue: string|null): void {
157
+ if (newValue != oldValue) switch (attribute) {
158
+ case "position":
159
+ this.#updatePosition(Object.values(Position).includes(newValue as Position) ? newValue as Position : Position.BottomEnd);
160
+ break;
161
+ // No default
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Shows a toast with the specified message.
167
+ * @param caption The title displayed in the header.
168
+ * @param message The message to show.
169
+ * @param options The toast options.
170
+ */
171
+ notify(context: Context, caption: string, childContent: DocumentFragment|string): void {
172
+ this.show({context, caption, childContent});
173
+ }
174
+
175
+ /**
176
+ * Shows a toast with the specified toast.
177
+ * @param toast The toast to show.
178
+ */
179
+ show(toast: IToast): void {
180
+ const item = document.createElement("toaster-item");
181
+ item.addEventListener("hidden.bs.toast", () => item.remove());
182
+ item.appendChild((this.#toastTemplate.cloneNode(true) as DocumentFragment).querySelector(".toast")!);
183
+
184
+ item.animation = toast.animation ?? this.animation;
185
+ item.autoHide = toast.autoHide ?? this.autoHide;
186
+ item.caption = toast.caption;
187
+ item.childContent = typeof toast.childContent == "string" ? createDocumentFragment(toast.childContent) : toast.childContent;
188
+ item.context = toast.context ?? this.context;
189
+ item.culture = toast.culture ?? this.culture;
190
+ item.delay = toast.delay ?? this.delay;
191
+ item.icon = toast.icon ?? this.icon;
192
+
193
+ this.firstElementChild!.appendChild(item);
194
+ item.show();
195
+ }
196
+
197
+ /**
198
+ * Updates the toaster placement.
199
+ * @param value The new value.
200
+ */
201
+ #updatePosition(value: Position): void {
202
+ const {classList} = this.firstElementChild!;
203
+ classList.remove(...Object.values(Position).flatMap(position => toCss(position).split(" ")));
204
+ classList.add(...toCss(value).split(" "));
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Declaration merging.
210
+ */
211
+ declare global {
212
+
213
+ /**
214
+ * The map of HTML tag names.
215
+ */
216
+ interface HTMLElementTagNameMap {
217
+ "toaster-container": Toaster;
218
+ }
219
+ }