@cedx/base 0.29.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/ReadMe.md +1 -1
  2. package/lib/String.d.ts +4 -1
  3. package/lib/String.d.ts.map +1 -1
  4. package/lib/String.js +3 -2
  5. package/lib/TimeSpan.d.ts +6 -6
  6. package/lib/TimeSpan.js +6 -6
  7. package/lib/UI/Components/BackButton.d.ts +1 -1
  8. package/lib/UI/Components/BackButton.d.ts.map +1 -1
  9. package/lib/UI/Components/BackButton.js +3 -3
  10. package/lib/UI/Components/DialogBox.js +5 -5
  11. package/lib/UI/Components/FullScreenToggler.d.ts +1 -1
  12. package/lib/UI/Components/FullScreenToggler.d.ts.map +1 -1
  13. package/lib/UI/Components/FullScreenToggler.js +10 -7
  14. package/lib/UI/Components/KeyboardAccelerator.d.ts.map +1 -1
  15. package/lib/UI/Components/KeyboardAccelerator.js +6 -2
  16. package/lib/UI/Components/LoadingIndicator.d.ts +4 -0
  17. package/lib/UI/Components/LoadingIndicator.d.ts.map +1 -1
  18. package/lib/UI/Components/LoadingIndicator.js +6 -0
  19. package/lib/UI/Components/MenuActivator.d.ts.map +1 -1
  20. package/lib/UI/Components/MenuActivator.js +7 -4
  21. package/lib/UI/Components/OfflineIndicator.d.ts +4 -0
  22. package/lib/UI/Components/OfflineIndicator.d.ts.map +1 -1
  23. package/lib/UI/Components/OfflineIndicator.js +12 -3
  24. package/lib/UI/Components/TabActivator.d.ts.map +1 -1
  25. package/lib/UI/Components/ThemeDropdown.d.ts +12 -1
  26. package/lib/UI/Components/ThemeDropdown.d.ts.map +1 -1
  27. package/lib/UI/Components/ThemeDropdown.js +46 -10
  28. package/lib/UI/Components/Toast.d.ts.map +1 -1
  29. package/lib/UI/Components/Toast.js +7 -5
  30. package/lib/UI/Components/Toaster.d.ts.map +1 -1
  31. package/package.json +6 -6
  32. package/src/Client/String.ts +3 -2
  33. package/src/Client/TimeSpan.ts +6 -6
  34. package/src/Client/UI/Components/BackButton.ts +3 -3
  35. package/src/Client/UI/Components/DialogBox.ts +5 -5
  36. package/src/Client/UI/Components/FullScreenToggler.ts +11 -7
  37. package/src/Client/UI/Components/KeyboardAccelerator.ts +7 -2
  38. package/src/Client/UI/Components/LoadingIndicator.ts +7 -0
  39. package/src/Client/UI/Components/MenuActivator.ts +8 -4
  40. package/src/Client/UI/Components/OfflineIndicator.ts +14 -2
  41. package/src/Client/UI/Components/TabActivator.ts +1 -0
  42. package/src/Client/UI/Components/ThemeDropdown.ts +48 -11
  43. package/src/Client/UI/Components/Toast.ts +8 -6
  44. package/src/Client/UI/Components/Toaster.ts +1 -2
package/ReadMe.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Cédric Belin's Base
2
- ![.NET](https://badgen.net/badge/.net/%3E%3D9.0/green) ![Version](https://badgen.net/badge/project/v0.29.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.31.0/blue) ![Licence](https://badgen.net/badge/licence/MIT/blue)
3
3
 
4
4
  Base library by [Cédric Belin](https://cedric-belin.fr), full stack developer,
5
5
  implemented in [C#](https://learn.microsoft.com/en-us/dotnet/csharp) and [TypeScript](https://www.typescriptlang.org).
package/lib/String.d.ts CHANGED
@@ -8,9 +8,12 @@ export declare function capitalize(value: string, locale?: Intl.Locale | string)
8
8
  /**
9
9
  * Replaces all new lines in the specified value by HTML line breaks.
10
10
  * @param value The string to format.
11
+ * @param options Value indicating whether to use XHTML compatible line breaks.
11
12
  * @returns The formatted string.
12
13
  */
13
- export declare function newLineToBr(value: string): string;
14
+ export declare function newLineToBr(value: string, options?: {
15
+ xhtml?: boolean;
16
+ }): string;
14
17
  /**
15
18
  * Reverses the specified string.
16
19
  * @param value The string to reverse.
@@ -1 +1 @@
1
- {"version":3,"file":"String.d.ts","sourceRoot":"","sources":["../src/Client/String.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,IAAI,CAAC,MAAM,GAAC,MAA2B,GAAG,MAAM,CAEjG;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,SAAI,GAAG,MAAM,EAAE,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,SAAQ,GAAG,MAAM,CAEhF"}
1
+ {"version":3,"file":"String.d.ts","sourceRoot":"","sources":["../src/Client/String.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,IAAI,CAAC,MAAM,GAAC,MAA2B,GAAG,MAAM,CAEjG;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAM,GAAG,MAAM,CAElF;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,SAAI,GAAG,MAAM,EAAE,CAE9D;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,SAAQ,GAAG,MAAM,CAEhF"}
package/lib/String.js CHANGED
@@ -10,10 +10,11 @@ export function capitalize(value, locale = navigator.language) {
10
10
  /**
11
11
  * Replaces all new lines in the specified value by HTML line breaks.
12
12
  * @param value The string to format.
13
+ * @param options Value indicating whether to use XHTML compatible line breaks.
13
14
  * @returns The formatted string.
14
15
  */
15
- export function newLineToBr(value) {
16
- return value.split(/\r?\n/g).join("<br>");
16
+ export function newLineToBr(value, options = {}) {
17
+ return value.split(/\r?\n/g).join(options.xhtml ? "<br />" : "<br>");
17
18
  }
18
19
  /**
19
20
  * Reverses the specified string.
package/lib/TimeSpan.d.ts CHANGED
@@ -9,7 +9,7 @@ export declare const TimeSpan: Readonly<{
9
9
  /**
10
10
  * The number of minutes in 1 day.
11
11
  */
12
- MinutesPerDay: 1440;
12
+ MinutesPerDay: number;
13
13
  /**
14
14
  * The number of minutes in 1 hour.
15
15
  */
@@ -17,11 +17,11 @@ export declare const TimeSpan: Readonly<{
17
17
  /**
18
18
  * The number of seconds in 1 day.
19
19
  */
20
- SecondsPerDay: 86400;
20
+ SecondsPerDay: number;
21
21
  /**
22
22
  * The number of seconds in 1 hour.
23
23
  */
24
- SecondsPerHour: 3600;
24
+ SecondsPerHour: number;
25
25
  /**
26
26
  * The number of seconds in 1 minute.
27
27
  */
@@ -29,15 +29,15 @@ export declare const TimeSpan: Readonly<{
29
29
  /**
30
30
  * The number of milliseconds in 1 day.
31
31
  */
32
- MillisecondsPerDay: 86400000;
32
+ MillisecondsPerDay: number;
33
33
  /**
34
34
  * The number of milliseconds in 1 hour.
35
35
  */
36
- MillisecondsPerHour: 3600000;
36
+ MillisecondsPerHour: number;
37
37
  /**
38
38
  * The number of milliseconds in 1 minute.
39
39
  */
40
- MillisecondsPerMinute: 60000;
40
+ MillisecondsPerMinute: number;
41
41
  /**
42
42
  * The number of milliseconds in 1 second.
43
43
  */
package/lib/TimeSpan.js CHANGED
@@ -9,7 +9,7 @@ export const TimeSpan = Object.freeze({
9
9
  /**
10
10
  * The number of minutes in 1 day.
11
11
  */
12
- MinutesPerDay: 1_440,
12
+ MinutesPerDay: 24 * 60,
13
13
  /**
14
14
  * The number of minutes in 1 hour.
15
15
  */
@@ -17,11 +17,11 @@ export const TimeSpan = Object.freeze({
17
17
  /**
18
18
  * The number of seconds in 1 day.
19
19
  */
20
- SecondsPerDay: 86_400,
20
+ SecondsPerDay: 24 * 60 * 60,
21
21
  /**
22
22
  * The number of seconds in 1 hour.
23
23
  */
24
- SecondsPerHour: 3_600,
24
+ SecondsPerHour: 60 * 60,
25
25
  /**
26
26
  * The number of seconds in 1 minute.
27
27
  */
@@ -29,15 +29,15 @@ export const TimeSpan = Object.freeze({
29
29
  /**
30
30
  * The number of milliseconds in 1 day.
31
31
  */
32
- MillisecondsPerDay: 86_400_000,
32
+ MillisecondsPerDay: 24 * 60 * 60 * 1_000,
33
33
  /**
34
34
  * The number of milliseconds in 1 hour.
35
35
  */
36
- MillisecondsPerHour: 3_600_000,
36
+ MillisecondsPerHour: 60 * 60 * 1_000,
37
37
  /**
38
38
  * The number of milliseconds in 1 minute.
39
39
  */
40
- MillisecondsPerMinute: 60_000,
40
+ MillisecondsPerMinute: 60 * 1_000,
41
41
  /**
42
42
  * The number of milliseconds in 1 second.
43
43
  */
@@ -15,7 +15,7 @@ export declare class BackButton extends HTMLElement {
15
15
  * Moves back in the session history.
16
16
  * @param event The dispatched event.
17
17
  */
18
- goBack(event?: Event): void;
18
+ readonly goBack: (event?: Event) => void;
19
19
  }
20
20
  /**
21
21
  * Declaration merging.
@@ -1 +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;IAED;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI;CAI3B;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,aAAa,EAAE,UAAU,CAAC;KAC1B;CACD"}
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;IAED;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,CAGtC;CACF;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,aAAa,EAAE,UAAU,CAAC;KAC1B;CACD"}
@@ -7,7 +7,7 @@ export class BackButton extends HTMLElement {
7
7
  */
8
8
  constructor() {
9
9
  super();
10
- this.addEventListener("click", this.goBack.bind(this), { capture: true });
10
+ this.addEventListener("click", this.goBack, { capture: true });
11
11
  }
12
12
  /**
13
13
  * Registers the component.
@@ -29,8 +29,8 @@ export class BackButton extends HTMLElement {
29
29
  * Moves back in the session history.
30
30
  * @param event The dispatched event.
31
31
  */
32
- goBack(event) {
32
+ goBack = event => {
33
33
  event?.stopPropagation();
34
34
  history.go(-this.steps);
35
- }
35
+ };
36
36
  }
@@ -29,9 +29,9 @@ export class DialogBox extends HTMLElement {
29
29
  constructor() {
30
30
  super();
31
31
  this.firstElementChild.addEventListener("hide.bs.modal", () => this.#resolve(this.#result));
32
- this.querySelector(".btn-close").addEventListener("click", this.#close.bind(this));
32
+ this.querySelector(".btn-close").addEventListener("click", this.#close);
33
33
  for (const button of this.querySelectorAll(".modal-footer button"))
34
- button.addEventListener("click", this.#close.bind(this));
34
+ button.addEventListener("click", this.#close);
35
35
  }
36
36
  /**
37
37
  * Registers the component.
@@ -211,7 +211,7 @@ export class DialogBox extends HTMLElement {
211
211
  if (message) {
212
212
  const footer = message.footer ?? document.createDocumentFragment();
213
213
  for (const button of footer.querySelectorAll("button"))
214
- button.addEventListener("click", this.#close.bind(this));
214
+ button.addEventListener("click", this.#close);
215
215
  this.body = message.body;
216
216
  this.caption = message.caption;
217
217
  this.footer = footer;
@@ -226,10 +226,10 @@ export class DialogBox extends HTMLElement {
226
226
  * Closes this dialog box.
227
227
  * @param event The dispatched event.
228
228
  */
229
- #close(event) {
229
+ #close = event => {
230
230
  event.preventDefault();
231
231
  this.close(event.currentTarget.value);
232
- }
232
+ };
233
233
  /**
234
234
  * Updates the title displayed in the header.
235
235
  * @param value The new value.
@@ -30,7 +30,7 @@ export declare class FullScreenToggler extends HTMLElement {
30
30
  * @param event The dispatched event.
31
31
  * @returns Completes when the full-screen mode has been toggled.
32
32
  */
33
- toggleFullScreen(event?: Event): Promise<void>;
33
+ readonly toggleFullScreen: (event?: Event) => Promise<void>;
34
34
  }
35
35
  /**
36
36
  * Declaration merging.
@@ -1 +1 @@
1
- {"version":3,"file":"FullScreenToggler.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/FullScreenToggler.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,WAAW;;IAYjD;;OAEG;;IAaH;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAGnB;IACD,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,EAEvB;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAMzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAK5B;;;;OAIG;IACG,gBAAgB,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;CAwCpD;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,oBAAoB,EAAE,iBAAiB,CAAC;KACxC;CACD"}
1
+ {"version":3,"file":"FullScreenToggler.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/FullScreenToggler.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,WAAW;;IAiBjD;;OAEG;;IAaH;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAGnB;IACD,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,EAEvB;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAMzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAI5B;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAIzD;CAoCF;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,oBAAoB,EAAE,iBAAiB,CAAC;KACxC;CACD"}
@@ -2,6 +2,10 @@
2
2
  * A component for toggling an element to full-screen.
3
3
  */
4
4
  export class FullScreenToggler extends HTMLElement {
5
+ /**
6
+ * The abort controller used to remove the event listeners.
7
+ */
8
+ #abortController = new AbortController;
5
9
  /**
6
10
  * The target element.
7
11
  */
@@ -15,7 +19,7 @@ export class FullScreenToggler extends HTMLElement {
15
19
  */
16
20
  constructor() {
17
21
  super();
18
- this.addEventListener("click", this.toggleFullScreen.bind(this), { capture: true }); // eslint-disable-line @typescript-eslint/no-misused-promises
22
+ this.addEventListener("click", this.toggleFullScreen, { capture: true }); // eslint-disable-line @typescript-eslint/no-misused-promises
19
23
  }
20
24
  /**
21
25
  * Registers the component.
@@ -46,29 +50,28 @@ export class FullScreenToggler extends HTMLElement {
46
50
  * Method invoked when this component is connected.
47
51
  */
48
52
  connectedCallback() {
49
- document.addEventListener("visibilitychange", this.#onVisibilityChanged);
53
+ document.addEventListener("visibilitychange", this.#onVisibilityChanged, { signal: this.#abortController.signal });
50
54
  this.#element = document.querySelector(this.target) ?? document.body;
51
- this.#element.addEventListener("fullscreenchange", this.#onFullScreenChanged);
55
+ this.#element.addEventListener("fullscreenchange", this.#onFullScreenChanged, { signal: this.#abortController.signal });
52
56
  }
53
57
  /**
54
58
  * Method invoked when this component is disconnected.
55
59
  */
56
60
  disconnectedCallback() {
57
- document.removeEventListener("visibilitychange", this.#onVisibilityChanged);
58
- this.#element.removeEventListener("fullscreenchange", this.#onFullScreenChanged);
61
+ this.#abortController.abort();
59
62
  }
60
63
  /**
61
64
  * Toggles the full-screen mode of the associated element.
62
65
  * @param event The dispatched event.
63
66
  * @returns Completes when the full-screen mode has been toggled.
64
67
  */
65
- async toggleFullScreen(event) {
68
+ toggleFullScreen = async (event) => {
66
69
  event?.stopPropagation();
67
70
  if (document.fullscreenElement)
68
71
  await document.exitFullscreen();
69
72
  else
70
73
  await this.#element.requestFullscreen();
71
- }
74
+ };
72
75
  /**
73
76
  * Acquires a new wake lock.
74
77
  * @returns Completes when the wake lock has been acquired.
@@ -1 +1 @@
1
- {"version":3,"file":"KeyboardAccelerator.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/KeyboardAccelerator.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,WAAW;;IAcnD;;OAEG;IACH,IAAI,GAAG,IAAI,MAAM,CAEhB;IACD,IAAI,GAAG,CAAC,KAAK,EAAE,MAAM,EAEpB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAItB;IACD,IAAI,SAAS,CAAC,KAAK,EAAE,MAAM,EAK1B;IAED;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAIzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;CAuB5B;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,sBAAsB,EAAE,mBAAmB,CAAC;KAC5C;CACD"}
1
+ {"version":3,"file":"KeyboardAccelerator.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/KeyboardAccelerator.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,WAAW;;IAmBnD;;OAEG;IACH,IAAI,GAAG,IAAI,MAAM,CAEhB;IACD,IAAI,GAAG,CAAC,KAAK,EAAE,MAAM,EAEpB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAItB;IACD,IAAI,SAAS,CAAC,KAAK,EAAE,MAAM,EAK1B;IAED;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAIzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;CAuB5B;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,sBAAsB,EAAE,mBAAmB,CAAC;KAC5C;CACD"}
@@ -3,6 +3,10 @@ import { KeyboardModifiers } from "../KeyboardModifiers.js";
3
3
  * Represents a shortcut key associated with an element.
4
4
  */
5
5
  export class KeyboardAccelerator extends HTMLElement {
6
+ /**
7
+ * The abort controller used to remove the event listeners.
8
+ */
9
+ #abortController = new AbortController;
6
10
  /**
7
11
  * The mapping between the modifier names and their values.
8
12
  */
@@ -40,13 +44,13 @@ export class KeyboardAccelerator extends HTMLElement {
40
44
  * Method invoked when this component is connected.
41
45
  */
42
46
  connectedCallback() {
43
- addEventListener("keyup", this.#activateChildContent, { capture: true });
47
+ addEventListener("keyup", this.#activateChildContent, { capture: true, signal: this.#abortController.signal });
44
48
  }
45
49
  /**
46
50
  * Method invoked when this component is disconnected.
47
51
  */
48
52
  disconnectedCallback() {
49
- removeEventListener("keyup", this.#activateChildContent, { capture: true });
53
+ this.#abortController.abort();
50
54
  }
51
55
  /**
52
56
  * Activates the child content when the specified keyboard event designates the same key and modifiers as this keyboard accelerator.
@@ -12,6 +12,10 @@ export declare class LoadingIndicator extends HTMLElement {
12
12
  */
13
13
  get fade(): boolean;
14
14
  set fade(value: boolean);
15
+ /**
16
+ * Value indicating whether this component is shown.
17
+ */
18
+ get isShown(): boolean;
15
19
  /**
16
20
  * Value indicating whether to initially show this component.
17
21
  */
@@ -1 +1 @@
1
- {"version":3,"file":"LoadingIndicator.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/LoadingIndicator.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,WAAW;;IAEhD;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,kBAAkB,WAAY;IAc9C;;OAEG;IACH,IAAI,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED;;;;;OAKG;IACH,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,GAAG,IAAI;IAO/F;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAIzB;;;OAGG;IACH,IAAI,CAAC,OAAO,GAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAM,GAAG,IAAI;IAS3C;;OAEG;IACH,IAAI,IAAI,IAAI;CAaZ;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,mBAAmB,EAAE,gBAAgB,CAAC;KACtC;CACD"}
1
+ {"version":3,"file":"LoadingIndicator.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/LoadingIndicator.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,WAAW;;IAEhD;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,kBAAkB,WAAY;IAc9C;;OAEG;IACH,IAAI,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED;;;;;OAKG;IACH,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,GAAG,IAAI;IAO/F;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAIzB;;;OAGG;IACH,IAAI,CAAC,OAAO,GAAE;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAM,GAAG,IAAI;IAS3C;;OAEG;IACH,IAAI,IAAI,IAAI;CAaZ;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,mBAAmB,EAAE,gBAAgB,CAAC;KACtC;CACD"}
@@ -25,6 +25,12 @@ export class LoadingIndicator extends HTMLElement {
25
25
  set fade(value) {
26
26
  this.toggleAttribute("fade", value);
27
27
  }
28
+ /**
29
+ * Value indicating whether this component is shown.
30
+ */
31
+ get isShown() {
32
+ return this.classList.contains("show");
33
+ }
28
34
  /**
29
35
  * Value indicating whether to initially show this component.
30
36
  */
@@ -1 +1 @@
1
- {"version":3,"file":"MenuActivator.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/MenuActivator.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,aAAc,SAAQ,WAAW;;IAS7C;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAMzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;CAgB5B;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,gBAAgB,EAAE,aAAa,CAAC;KAChC;CACD"}
1
+ {"version":3,"file":"MenuActivator.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/MenuActivator.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,aAAc,SAAQ,WAAW;;IAc7C;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAMzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;CAe5B;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,gBAAgB,EAAE,aAAa,CAAC;KAChC;CACD"}
@@ -2,6 +2,10 @@
2
2
  * A component that activates the items of a menu, based on the current document URL.
3
3
  */
4
4
  export class MenuActivator extends HTMLElement {
5
+ /**
6
+ * The abort controller used to remove the event listeners.
7
+ */
8
+ #abortController = new AbortController;
5
9
  /**
6
10
  * Registers the component.
7
11
  */
@@ -13,15 +17,14 @@ export class MenuActivator extends HTMLElement {
13
17
  */
14
18
  connectedCallback() {
15
19
  this.#update();
16
- addEventListener("popstate", this.#update);
17
- document.body.addEventListener("htmx:afterRequest", this.#update);
20
+ addEventListener("popstate", this.#update, { signal: this.#abortController.signal });
21
+ document.body.addEventListener("htmx:afterRequest", this.#update, { signal: this.#abortController.signal });
18
22
  }
19
23
  /**
20
24
  * Method invoked when this component is disconnected.
21
25
  */
22
26
  disconnectedCallback() {
23
- removeEventListener("popstate", this.#update);
24
- document.body.removeEventListener("htmx:afterRequest", this.#update);
27
+ this.#abortController.abort();
25
28
  }
26
29
  /**
27
30
  * Updates this component.
@@ -12,6 +12,10 @@ export declare class OfflineIndicator extends HTMLElement {
12
12
  */
13
13
  get fade(): boolean;
14
14
  set fade(value: boolean);
15
+ /**
16
+ * Value indicating whether this component is shown.
17
+ */
18
+ get isShown(): boolean;
15
19
  /**
16
20
  * Value indicating whether to initially show this component.
17
21
  */
@@ -1 +1 @@
1
- {"version":3,"file":"OfflineIndicator.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/OfflineIndicator.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,WAAW;;IAEhD;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,kBAAkB,WAAY;IAS9C;;OAEG;IACH,IAAI,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED;;;;;OAKG;IACH,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,GAAG,IAAI;IAO/F;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAMzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAI5B;;OAEG;IACH,IAAI,IAAI,IAAI;IAKZ;;OAEG;IACH,IAAI,IAAI,IAAI;CAoBZ;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,mBAAmB,EAAE,gBAAgB,CAAC;KACtC;CACD"}
1
+ {"version":3,"file":"OfflineIndicator.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/OfflineIndicator.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,WAAW;;IAEhD;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,kBAAkB,WAAY;IAc9C;;OAEG;IACH,IAAI,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED;;;;;OAKG;IACH,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,GAAG,IAAI;IAO/F;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAMzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAI5B;;OAEG;IACH,IAAI,IAAI,IAAI;IAKZ;;OAEG;IACH,IAAI,IAAI,IAAI;CAoBZ;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,mBAAmB,EAAE,gBAAgB,CAAC;KACtC;CACD"}
@@ -6,6 +6,10 @@ export class OfflineIndicator extends HTMLElement {
6
6
  * The list of observed attributes.
7
7
  */
8
8
  static observedAttributes = ["fade"];
9
+ /**
10
+ * The abort controller used to remove the event listeners.
11
+ */
12
+ #abortController = new AbortController;
9
13
  /**
10
14
  * Registers the component.
11
15
  */
@@ -21,6 +25,12 @@ export class OfflineIndicator extends HTMLElement {
21
25
  set fade(value) {
22
26
  this.toggleAttribute("fade", value);
23
27
  }
28
+ /**
29
+ * Value indicating whether this component is shown.
30
+ */
31
+ get isShown() {
32
+ return this.classList.contains("show");
33
+ }
24
34
  /**
25
35
  * Value indicating whether to initially show this component.
26
36
  */
@@ -50,7 +60,7 @@ export class OfflineIndicator extends HTMLElement {
50
60
  */
51
61
  connectedCallback() {
52
62
  for (const event of ["online", "offline"])
53
- addEventListener(event, this.#updateVisibility);
63
+ addEventListener(event, this.#updateVisibility, { signal: this.#abortController.signal });
54
64
  if (this.open)
55
65
  this.show();
56
66
  else
@@ -60,8 +70,7 @@ export class OfflineIndicator extends HTMLElement {
60
70
  * Method invoked when this component is disconnected.
61
71
  */
62
72
  disconnectedCallback() {
63
- for (const event of ["online", "offline"])
64
- removeEventListener(event, this.#updateVisibility);
73
+ this.#abortController.abort();
65
74
  }
66
75
  /**
67
76
  * Hides this offline indicator.
@@ -1 +1 @@
1
- {"version":3,"file":"TabActivator.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/TabActivator.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAE9C;;GAEG;AACH,qBAAa,YAAa,SAAQ,WAAW;IAE5C;;OAEG;;IAeH;;OAEG;IACH,IAAI,cAAc,IAAI,MAAM,CAG3B;IACD,IAAI,cAAc,CAAC,KAAK,EAAE,MAAM,EAE/B;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,UAAU,CAAC,OAAO,CAEhC;IAED;;OAEG;IACH,IAAI,WAAW,IAAI,WAAW,CAG7B;IACD,IAAI,WAAW,CAAC,KAAK,EAAE,WAAW,EAEjC;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,MAAM,CAGvB;IACD,IAAI,UAAU,CAAC,KAAK,EAAE,MAAM,EAE3B;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,UAAU,CAAC,WAAW,CAAC,CAElC;IAED;;OAEG;IACH,iBAAiB,IAAI,IAAI;CAGzB;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,eAAe,EAAE,YAAY,CAAC;KAC9B;CACD"}
1
+ {"version":3,"file":"TabActivator.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/TabActivator.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,WAAW,EAAC,MAAM,mBAAmB,CAAC;AAE9C;;GAEG;AACH,qBAAa,YAAa,SAAQ,WAAW;IAE5C;;OAEG;;IAgBH;;OAEG;IACH,IAAI,cAAc,IAAI,MAAM,CAG3B;IACD,IAAI,cAAc,CAAC,KAAK,EAAE,MAAM,EAE/B;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,UAAU,CAAC,OAAO,CAEhC;IAED;;OAEG;IACH,IAAI,WAAW,IAAI,WAAW,CAG7B;IACD,IAAI,WAAW,CAAC,KAAK,EAAE,WAAW,EAEjC;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,MAAM,CAGvB;IACD,IAAI,UAAU,CAAC,KAAK,EAAE,MAAM,EAE3B;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,UAAU,CAAC,WAAW,CAAC,CAElC;IAED;;OAEG;IACH,iBAAiB,IAAI,IAAI;CAGzB;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,eAAe,EAAE,YAAY,CAAC;KAC9B;CACD"}
@@ -23,6 +23,16 @@ export declare class ThemeDropdown extends HTMLElement {
23
23
  */
24
24
  get appTheme(): AppTheme;
25
25
  set appTheme(value: AppTheme);
26
+ /**
27
+ * Value indicating whether to store the application theme in a cookie.
28
+ */
29
+ get cookie(): boolean;
30
+ set cookie(value: boolean);
31
+ /**
32
+ * The URI for which the associated cookie is valid.
33
+ */
34
+ get cookieDomain(): string;
35
+ set cookieDomain(value: string);
26
36
  /**
27
37
  * The label of the dropdown menu.
28
38
  */
@@ -46,8 +56,9 @@ export declare class ThemeDropdown extends HTMLElement {
46
56
  close(): void;
47
57
  /**
48
58
  * Method invoked when this component is connected.
59
+ * @returns Completes when this component has been connected.
49
60
  */
50
- connectedCallback(): void;
61
+ connectedCallback(): Promise<void>;
51
62
  /**
52
63
  * Method invoked when this component is disconnected.
53
64
  */
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeDropdown.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/ThemeDropdown.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAC,QAAQ,EAAU,MAAM,gBAAgB,CAAC;AAEjD;;GAEG;AACH,qBAAa,aAAc,SAAQ,WAAW;;IAE7C;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,kBAAkB,WAAsC;IAYxE;;OAEG;;IAcH;;OAEG;IACH,IAAI,SAAS,IAAI,SAAS,CAGzB;IACD,IAAI,SAAS,CAAC,KAAK,EAAE,SAAS,EAE7B;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,QAAQ,CAGvB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAE3B;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,MAAM,CAGlB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAEtB;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,MAAM,CAGvB;IACD,IAAI,UAAU,CAAC,KAAK,EAAE,MAAM,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;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAQzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAK5B;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,IAAI,IAAI,IAAI;CAgDZ;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,gBAAgB,EAAE,aAAa,CAAC;KAChC;CACD"}
1
+ {"version":3,"file":"ThemeDropdown.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/ThemeDropdown.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAC,QAAQ,EAAU,MAAM,gBAAgB,CAAC;AAEjD;;GAEG;AACH,qBAAa,aAAc,SAAQ,WAAW;;IAE7C;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,kBAAkB,WAAsC;IAiBxE;;OAEG;;IAaH;;OAEG;IACH,IAAI,SAAS,IAAI,SAAS,CAGzB;IACD,IAAI,SAAS,CAAC,KAAK,EAAE,SAAS,EAE7B;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,QAAQ,CAGvB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAE3B;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,OAAO,CAEpB;IACD,IAAI,MAAM,CAAC,KAAK,EAAE,OAAO,EAExB;IAED;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IACD,IAAI,YAAY,CAAC,KAAK,EAAE,MAAM,EAE7B;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,MAAM,CAGlB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAEtB;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,MAAM,CAGvB;IACD,IAAI,UAAU,CAAC,KAAK,EAAE,MAAM,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;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;;OAGG;IACG,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAYxC;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAK5B;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,IAAI,IAAI,IAAI;CAwDZ;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,gBAAgB,EAAE,aAAa,CAAC;KAChC;CACD"}
@@ -9,6 +9,10 @@ export class ThemeDropdown extends HTMLElement {
9
9
  * The list of observed attributes.
10
10
  */
11
11
  static observedAttributes = ["alignment", "apptheme", "label"];
12
+ /**
13
+ * The abort controller used to remove the event listeners.
14
+ */
15
+ #abortController = new AbortController;
12
16
  /**
13
17
  * The dropdown menu.
14
18
  */
@@ -23,7 +27,7 @@ export class ThemeDropdown extends HTMLElement {
23
27
  constructor() {
24
28
  super();
25
29
  for (const button of this.querySelectorAll(".dropdown-menu button"))
26
- button.addEventListener("click", this.#setAppTheme.bind(this));
30
+ button.addEventListener("click", this.#setAppTheme);
27
31
  }
28
32
  /**
29
33
  * Registers the component.
@@ -51,6 +55,24 @@ export class ThemeDropdown extends HTMLElement {
51
55
  set appTheme(value) {
52
56
  this.setAttribute("apptheme", value);
53
57
  }
58
+ /**
59
+ * Value indicating whether to store the application theme in a cookie.
60
+ */
61
+ get cookie() {
62
+ return this.hasAttribute("cookie");
63
+ }
64
+ set cookie(value) {
65
+ this.toggleAttribute("cookie", value);
66
+ }
67
+ /**
68
+ * The URI for which the associated cookie is valid.
69
+ */
70
+ get cookieDomain() {
71
+ return (this.getAttribute("cookiedomain") ?? "").trim();
72
+ }
73
+ set cookieDomain(value) {
74
+ this.setAttribute("cookiedomain", value);
75
+ }
54
76
  /**
55
77
  * The label of the dropdown menu.
56
78
  */
@@ -100,20 +122,26 @@ export class ThemeDropdown extends HTMLElement {
100
122
  }
101
123
  /**
102
124
  * Method invoked when this component is connected.
125
+ * @returns Completes when this component has been connected.
103
126
  */
104
- connectedCallback() {
105
- const appTheme = localStorage.getItem(this.storageKey);
106
- if (appTheme)
107
- this.appTheme = appTheme;
127
+ async connectedCallback() {
108
128
  this.#dropdown = new Dropdown(this.querySelector(".dropdown-toggle"));
109
- this.#mediaQuery.addEventListener("change", this.#applyToDocument);
129
+ this.#mediaQuery.addEventListener("change", this.#applyToDocument, { signal: this.#abortController.signal });
130
+ const cookie = this.cookie ? await cookieStore.get(this.storageKey) : null;
131
+ if (cookie)
132
+ this.appTheme = cookie.value;
133
+ else {
134
+ const storage = localStorage.getItem(this.storageKey);
135
+ if (storage)
136
+ this.appTheme = storage;
137
+ }
110
138
  }
111
139
  /**
112
140
  * Method invoked when this component is disconnected.
113
141
  */
114
142
  disconnectedCallback() {
143
+ this.#abortController.abort();
115
144
  this.#dropdown.dispose();
116
- this.#mediaQuery.removeEventListener("change", this.#applyToDocument);
117
145
  }
118
146
  /**
119
147
  * Opens the dropdown menu.
@@ -125,7 +153,15 @@ export class ThemeDropdown extends HTMLElement {
125
153
  * Saves the current application theme into the local storage.
126
154
  */
127
155
  save() {
128
- localStorage.setItem(this.storageKey, this.appTheme);
156
+ const { appTheme, cookieDomain } = this;
157
+ localStorage.setItem(this.storageKey, appTheme);
158
+ if (this.cookie)
159
+ void cookieStore.set({
160
+ domain: cookieDomain ? cookieDomain : null,
161
+ expires: Date.now() + (365 * 24 * 60 * 60 * 1_000),
162
+ name: this.storageKey,
163
+ value: appTheme
164
+ });
129
165
  }
130
166
  /**
131
167
  * Applies the application theme to the document.
@@ -139,11 +175,11 @@ export class ThemeDropdown extends HTMLElement {
139
175
  * Changes the current application theme.
140
176
  * @param event The dispatched event.
141
177
  */
142
- #setAppTheme(event) {
178
+ #setAppTheme = event => {
143
179
  event.preventDefault();
144
180
  this.appTheme = event.currentTarget.value;
145
181
  this.save();
146
- }
182
+ };
147
183
  /**
148
184
  * Updates the alignment of the dropdown menu.
149
185
  * @param value The new value.
@@ -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,eAAe,CAAC;AAEtD;;GAEG;AACH,qBAAa,KAAM,SAAQ,WAAW;;IAErC;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,kBAAkB,WAA0E;IAkC5G;;OAEG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED;;OAEG;IACH,IAAI,IAAI,CAAC,KAAK,EAAE,gBAAgB,EAE/B;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,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,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,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED;;;;;OAKG;IACH,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,GAAG,IAAI;IAa/F;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,iBAAiB,IAAI,IAAI;IASzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAS5B;;OAEG;IACH,IAAI,IAAI,IAAI;CAoGZ;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,WAA0E;IAuC5G;;OAEG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IACD,IAAI,QAAQ,CAAC,KAAK,EAAE,OAAO,EAE1B;IAED;;OAEG;IACH,IAAI,IAAI,CAAC,KAAK,EAAE,gBAAgB,EAE/B;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,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,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,IAAI,IAAI,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,EAEtB;IAED;;;;;OAKG;IACH,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,GAAG,IAAI;IAa/F;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,iBAAiB,IAAI,IAAI;IASzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAM5B;;OAEG;IACH,IAAI,IAAI,IAAI;CAoGZ;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,cAAc,EAAE,KAAK,CAAC;KACtB;CACD"}
@@ -12,6 +12,10 @@ export class Toast extends HTMLElement {
12
12
  * The time units.
13
13
  */
14
14
  static #timeUnits = ["second", "minute", "hour"];
15
+ /**
16
+ * The abort controller used to remove the event listeners.
17
+ */
18
+ #abortController = new AbortController;
15
19
  /**
16
20
  * The formatter used to format the relative time.
17
21
  */
@@ -169,8 +173,8 @@ export class Toast extends HTMLElement {
169
173
  */
170
174
  connectedCallback() {
171
175
  const root = this.firstElementChild;
172
- root.addEventListener("hide.bs.toast", this.#stopTimer);
173
- root.addEventListener("show.bs.toast", this.#startTimer);
176
+ root.addEventListener("hide.bs.toast", this.#stopTimer, { signal: this.#abortController.signal });
177
+ root.addEventListener("show.bs.toast", this.#startTimer, { signal: this.#abortController.signal });
174
178
  this.#toast = new BootstrapToast(root);
175
179
  if (this.open)
176
180
  this.show();
@@ -179,10 +183,8 @@ export class Toast extends HTMLElement {
179
183
  * Method invoked when this component is disconnected.
180
184
  */
181
185
  disconnectedCallback() {
182
- const root = this.firstElementChild;
183
- root.removeEventListener("hide.bs.toast", () => this.#stopTimer);
184
- root.removeEventListener("show.bs.toast", () => this.#startTimer);
185
186
  this.#stopTimer();
187
+ this.#abortController.abort();
186
188
  this.#toast.dispose();
187
189
  }
188
190
  /**
@@ -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;AACtC,OAAO,EAAC,QAAQ,EAAQ,MAAM,gBAAgB,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,MAAM;IAEtB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,IAAI,EAAE,gBAAgB,CAAC;IAEvB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;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,OAAO,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;IAOlD;;OAEG;;IAcH;;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,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,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,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAI1E;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;CA4BzB;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,mBAAmB,EAAE,OAAO,CAAC;KAC7B;CACD"}
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;AACtC,OAAO,EAAC,QAAQ,EAAQ,MAAM,gBAAgB,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,MAAM;IAEtB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,IAAI,EAAE,gBAAgB,CAAC;IAEvB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;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,OAAO,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;IAOlD;;OAEG;;IAaH;;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,OAAO,CAElB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,OAAO,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,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAI1E;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;CA4BzB;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,mBAAmB,EAAE,OAAO,CAAC;KAC7B;CACD"}
package/package.json CHANGED
@@ -7,23 +7,23 @@
7
7
  "name": "@cedx/base",
8
8
  "repository": "cedx/base",
9
9
  "type": "module",
10
- "version": "0.29.0",
10
+ "version": "0.31.0",
11
11
  "devDependencies": {
12
12
  "@playwright/browser-chromium": "^1.55.0",
13
13
  "@types/bootstrap": "^5.2.10",
14
14
  "@types/chai": "^5.2.2",
15
15
  "@types/mocha": "^10.0.10",
16
- "@types/node": "^24.3.0",
16
+ "@types/node": "^24.5.2",
17
17
  "@types/serve-handler": "^6.1.4",
18
18
  "chai": "^6.0.1",
19
- "esbuild": "^0.25.9",
20
- "globals": "^16.3.0",
19
+ "esbuild": "^0.25.10",
20
+ "globals": "^16.4.0",
21
21
  "mocha": "^11.7.2",
22
22
  "playwright": "^1.55.0",
23
23
  "serve-handler": "^6.1.6",
24
- "typedoc": "^0.28.12",
24
+ "typedoc": "^0.28.13",
25
25
  "typescript": "^5.9.2",
26
- "typescript-eslint": "^8.42.0"
26
+ "typescript-eslint": "^8.44.0"
27
27
  },
28
28
  "engines": {
29
29
  "node": ">=24.0.0"
@@ -11,10 +11,11 @@ export function capitalize(value: string, locale: Intl.Locale|string = navigator
11
11
  /**
12
12
  * Replaces all new lines in the specified value by HTML line breaks.
13
13
  * @param value The string to format.
14
+ * @param options Value indicating whether to use XHTML compatible line breaks.
14
15
  * @returns The formatted string.
15
16
  */
16
- export function newLineToBr(value: string): string {
17
- return value.split(/\r?\n/g).join("<br>");
17
+ export function newLineToBr(value: string, options: {xhtml?: boolean} = {}): string {
18
+ return value.split(/\r?\n/g).join(options.xhtml ? "<br />" : "<br>");
18
19
  }
19
20
 
20
21
  /**
@@ -11,7 +11,7 @@ export const TimeSpan = Object.freeze({
11
11
  /**
12
12
  * The number of minutes in 1 day.
13
13
  */
14
- MinutesPerDay: 1_440,
14
+ MinutesPerDay: 24 * 60,
15
15
 
16
16
  /**
17
17
  * The number of minutes in 1 hour.
@@ -21,12 +21,12 @@ export const TimeSpan = Object.freeze({
21
21
  /**
22
22
  * The number of seconds in 1 day.
23
23
  */
24
- SecondsPerDay: 86_400,
24
+ SecondsPerDay: 24 * 60 * 60,
25
25
 
26
26
  /**
27
27
  * The number of seconds in 1 hour.
28
28
  */
29
- SecondsPerHour: 3_600,
29
+ SecondsPerHour: 60 * 60,
30
30
 
31
31
  /**
32
32
  * The number of seconds in 1 minute.
@@ -36,17 +36,17 @@ export const TimeSpan = Object.freeze({
36
36
  /**
37
37
  * The number of milliseconds in 1 day.
38
38
  */
39
- MillisecondsPerDay: 86_400_000,
39
+ MillisecondsPerDay: 24 * 60 * 60 * 1_000,
40
40
 
41
41
  /**
42
42
  * The number of milliseconds in 1 hour.
43
43
  */
44
- MillisecondsPerHour: 3_600_000,
44
+ MillisecondsPerHour: 60 * 60 * 1_000,
45
45
 
46
46
  /**
47
47
  * The number of milliseconds in 1 minute.
48
48
  */
49
- MillisecondsPerMinute: 60_000,
49
+ MillisecondsPerMinute: 60 * 1_000,
50
50
 
51
51
  /**
52
52
  * The number of milliseconds in 1 second.
@@ -8,7 +8,7 @@ export class BackButton extends HTMLElement {
8
8
  */
9
9
  constructor() {
10
10
  super();
11
- this.addEventListener("click", this.goBack.bind(this), {capture: true});
11
+ this.addEventListener("click", this.goBack, {capture: true});
12
12
  }
13
13
 
14
14
  /**
@@ -33,10 +33,10 @@ export class BackButton extends HTMLElement {
33
33
  * Moves back in the session history.
34
34
  * @param event The dispatched event.
35
35
  */
36
- goBack(event?: Event): void {
36
+ readonly goBack: (event?: Event) => void = event => {
37
37
  event?.stopPropagation();
38
38
  history.go(-this.steps);
39
- }
39
+ };
40
40
  }
41
41
 
42
42
  /**
@@ -82,8 +82,8 @@ export class DialogBox extends HTMLElement {
82
82
  constructor() {
83
83
  super();
84
84
  this.firstElementChild!.addEventListener("hide.bs.modal", () => this.#resolve(this.#result));
85
- this.querySelector(".btn-close")!.addEventListener("click", this.#close.bind(this));
86
- for (const button of this.querySelectorAll(".modal-footer button")) button.addEventListener("click", this.#close.bind(this));
85
+ this.querySelector(".btn-close")!.addEventListener("click", this.#close);
86
+ for (const button of this.querySelectorAll(".modal-footer button")) button.addEventListener("click", this.#close);
87
87
  }
88
88
 
89
89
  /**
@@ -267,7 +267,7 @@ export class DialogBox extends HTMLElement {
267
267
  show(message: IDialogMessage|null = null): Promise<string> {
268
268
  if (message) {
269
269
  const footer = message.footer ?? document.createDocumentFragment();
270
- for (const button of footer.querySelectorAll("button")) button.addEventListener("click", this.#close.bind(this));
270
+ for (const button of footer.querySelectorAll("button")) button.addEventListener("click", this.#close);
271
271
  this.body = message.body;
272
272
  this.caption = message.caption;
273
273
  this.footer = footer;
@@ -284,10 +284,10 @@ export class DialogBox extends HTMLElement {
284
284
  * Closes this dialog box.
285
285
  * @param event The dispatched event.
286
286
  */
287
- #close(event: Event): void {
287
+ readonly #close: (event: Event) => void = event => {
288
288
  event.preventDefault();
289
289
  this.close((event.currentTarget as HTMLButtonElement).value);
290
- }
290
+ };
291
291
 
292
292
  /**
293
293
  * Updates the title displayed in the header.
@@ -3,6 +3,11 @@
3
3
  */
4
4
  export class FullScreenToggler extends HTMLElement {
5
5
 
6
+ /**
7
+ * The abort controller used to remove the event listeners.
8
+ */
9
+ readonly #abortController = new AbortController;
10
+
6
11
  /**
7
12
  * The target element.
8
13
  */
@@ -18,7 +23,7 @@ export class FullScreenToggler extends HTMLElement {
18
23
  */
19
24
  constructor() {
20
25
  super();
21
- this.addEventListener("click", this.toggleFullScreen.bind(this), {capture: true}); // eslint-disable-line @typescript-eslint/no-misused-promises
26
+ this.addEventListener("click", this.toggleFullScreen, {capture: true}); // eslint-disable-line @typescript-eslint/no-misused-promises
22
27
  }
23
28
 
24
29
  /**
@@ -53,17 +58,16 @@ export class FullScreenToggler extends HTMLElement {
53
58
  * Method invoked when this component is connected.
54
59
  */
55
60
  connectedCallback(): void {
56
- document.addEventListener("visibilitychange", this.#onVisibilityChanged);
61
+ document.addEventListener("visibilitychange", this.#onVisibilityChanged, {signal: this.#abortController.signal});
57
62
  this.#element = document.querySelector(this.target) ?? document.body;
58
- this.#element.addEventListener("fullscreenchange", this.#onFullScreenChanged);
63
+ this.#element.addEventListener("fullscreenchange", this.#onFullScreenChanged, {signal: this.#abortController.signal});
59
64
  }
60
65
 
61
66
  /**
62
67
  * Method invoked when this component is disconnected.
63
68
  */
64
69
  disconnectedCallback(): void {
65
- document.removeEventListener("visibilitychange", this.#onVisibilityChanged);
66
- this.#element.removeEventListener("fullscreenchange", this.#onFullScreenChanged);
70
+ this.#abortController.abort();
67
71
  }
68
72
 
69
73
  /**
@@ -71,11 +75,11 @@ export class FullScreenToggler extends HTMLElement {
71
75
  * @param event The dispatched event.
72
76
  * @returns Completes when the full-screen mode has been toggled.
73
77
  */
74
- async toggleFullScreen(event?: Event): Promise<void> {
78
+ readonly toggleFullScreen: (event?: Event) => Promise<void> = async event => {
75
79
  event?.stopPropagation();
76
80
  if (document.fullscreenElement) await document.exitFullscreen();
77
81
  else await this.#element.requestFullscreen();
78
- }
82
+ };
79
83
 
80
84
  /**
81
85
  * Acquires a new wake lock.
@@ -5,6 +5,11 @@ import {KeyboardModifiers} from "../KeyboardModifiers.js";
5
5
  */
6
6
  export class KeyboardAccelerator extends HTMLElement {
7
7
 
8
+ /**
9
+ * The abort controller used to remove the event listeners.
10
+ */
11
+ readonly #abortController = new AbortController;
12
+
8
13
  /**
9
14
  * The mapping between the modifier names and their values.
10
15
  */
@@ -46,14 +51,14 @@ export class KeyboardAccelerator extends HTMLElement {
46
51
  * Method invoked when this component is connected.
47
52
  */
48
53
  connectedCallback(): void {
49
- addEventListener("keyup", this.#activateChildContent, {capture: true});
54
+ addEventListener("keyup", this.#activateChildContent, {capture: true, signal: this.#abortController.signal});
50
55
  }
51
56
 
52
57
  /**
53
58
  * Method invoked when this component is disconnected.
54
59
  */
55
60
  disconnectedCallback(): void {
56
- removeEventListener("keyup", this.#activateChildContent, {capture: true});
61
+ this.#abortController.abort();
57
62
  }
58
63
 
59
64
  /**
@@ -30,6 +30,13 @@ export class LoadingIndicator extends HTMLElement {
30
30
  this.toggleAttribute("fade", value);
31
31
  }
32
32
 
33
+ /**
34
+ * Value indicating whether this component is shown.
35
+ */
36
+ get isShown(): boolean {
37
+ return this.classList.contains("show");
38
+ }
39
+
33
40
  /**
34
41
  * Value indicating whether to initially show this component.
35
42
  */
@@ -3,6 +3,11 @@
3
3
  */
4
4
  export class MenuActivator extends HTMLElement {
5
5
 
6
+ /**
7
+ * The abort controller used to remove the event listeners.
8
+ */
9
+ readonly #abortController = new AbortController;
10
+
6
11
  /**
7
12
  * Registers the component.
8
13
  */
@@ -15,16 +20,15 @@ export class MenuActivator extends HTMLElement {
15
20
  */
16
21
  connectedCallback(): void {
17
22
  this.#update();
18
- addEventListener("popstate", this.#update);
19
- document.body.addEventListener("htmx:afterRequest", this.#update);
23
+ addEventListener("popstate", this.#update, {signal: this.#abortController.signal});
24
+ document.body.addEventListener("htmx:afterRequest", this.#update, {signal: this.#abortController.signal});
20
25
  }
21
26
 
22
27
  /**
23
28
  * Method invoked when this component is disconnected.
24
29
  */
25
30
  disconnectedCallback(): void {
26
- removeEventListener("popstate", this.#update);
27
- document.body.removeEventListener("htmx:afterRequest", this.#update);
31
+ this.#abortController.abort();
28
32
  }
29
33
 
30
34
  /**
@@ -8,6 +8,11 @@ export class OfflineIndicator extends HTMLElement {
8
8
  */
9
9
  static readonly observedAttributes = ["fade"];
10
10
 
11
+ /**
12
+ * The abort controller used to remove the event listeners.
13
+ */
14
+ readonly #abortController = new AbortController;
15
+
11
16
  /**
12
17
  * Registers the component.
13
18
  */
@@ -25,6 +30,13 @@ export class OfflineIndicator extends HTMLElement {
25
30
  this.toggleAttribute("fade", value);
26
31
  }
27
32
 
33
+ /**
34
+ * Value indicating whether this component is shown.
35
+ */
36
+ get isShown(): boolean {
37
+ return this.classList.contains("show");
38
+ }
39
+
28
40
  /**
29
41
  * Value indicating whether to initially show this component.
30
42
  */
@@ -52,7 +64,7 @@ export class OfflineIndicator extends HTMLElement {
52
64
  * Method invoked when this component is connected.
53
65
  */
54
66
  connectedCallback(): void {
55
- for (const event of ["online", "offline"]) addEventListener(event, this.#updateVisibility);
67
+ for (const event of ["online", "offline"]) addEventListener(event, this.#updateVisibility, {signal: this.#abortController.signal});
56
68
  if (this.open) this.show();
57
69
  else this.#updateVisibility();
58
70
  }
@@ -61,7 +73,7 @@ export class OfflineIndicator extends HTMLElement {
61
73
  * Method invoked when this component is disconnected.
62
74
  */
63
75
  disconnectedCallback(): void {
64
- for (const event of ["online", "offline"]) removeEventListener(event, this.#updateVisibility);
76
+ this.#abortController.abort();
65
77
  }
66
78
 
67
79
  /**
@@ -11,6 +11,7 @@ export class TabActivator extends HTMLElement {
11
11
  */
12
12
  constructor() {
13
13
  super();
14
+
14
15
  const {tabs} = this;
15
16
  for (let index = 0; index < tabs.length; index++)
16
17
  tabs.item(index).addEventListener("show.bs.tab", () => this.activeTabIndex = index);
@@ -12,6 +12,11 @@ export class ThemeDropdown extends HTMLElement {
12
12
  */
13
13
  static readonly observedAttributes = ["alignment", "apptheme", "label"];
14
14
 
15
+ /**
16
+ * The abort controller used to remove the event listeners.
17
+ */
18
+ readonly #abortController = new AbortController;
19
+
15
20
  /**
16
21
  * The dropdown menu.
17
22
  */
@@ -27,8 +32,7 @@ export class ThemeDropdown extends HTMLElement {
27
32
  */
28
33
  constructor() {
29
34
  super();
30
- for (const button of this.querySelectorAll(".dropdown-menu button"))
31
- button.addEventListener("click", this.#setAppTheme.bind(this));
35
+ for (const button of this.querySelectorAll(".dropdown-menu button")) button.addEventListener("click", this.#setAppTheme);
32
36
  }
33
37
 
34
38
  /**
@@ -60,6 +64,26 @@ export class ThemeDropdown extends HTMLElement {
60
64
  this.setAttribute("apptheme", value);
61
65
  }
62
66
 
67
+ /**
68
+ * Value indicating whether to store the application theme in a cookie.
69
+ */
70
+ get cookie(): boolean {
71
+ return this.hasAttribute("cookie");
72
+ }
73
+ set cookie(value: boolean) {
74
+ this.toggleAttribute("cookie", value);
75
+ }
76
+
77
+ /**
78
+ * The URI for which the associated cookie is valid.
79
+ */
80
+ get cookieDomain(): string {
81
+ return (this.getAttribute("cookiedomain") ?? "").trim();
82
+ }
83
+ set cookieDomain(value: string) {
84
+ this.setAttribute("cookiedomain", value);
85
+ }
86
+
63
87
  /**
64
88
  * The label of the dropdown menu.
65
89
  */
@@ -106,21 +130,26 @@ export class ThemeDropdown extends HTMLElement {
106
130
 
107
131
  /**
108
132
  * Method invoked when this component is connected.
133
+ * @returns Completes when this component has been connected.
109
134
  */
110
- connectedCallback(): void {
111
- const appTheme = localStorage.getItem(this.storageKey) as AppTheme|null;
112
- if (appTheme) this.appTheme = appTheme;
113
-
135
+ async connectedCallback(): Promise<void> {
114
136
  this.#dropdown = new Dropdown(this.querySelector(".dropdown-toggle")!);
115
- this.#mediaQuery.addEventListener("change", this.#applyToDocument);
137
+ this.#mediaQuery.addEventListener("change", this.#applyToDocument, {signal: this.#abortController.signal});
138
+
139
+ const cookie = this.cookie ? await cookieStore.get(this.storageKey) : null;
140
+ if (cookie) this.appTheme = cookie.value as AppTheme;
141
+ else {
142
+ const storage = localStorage.getItem(this.storageKey);
143
+ if (storage) this.appTheme = storage as AppTheme;
144
+ }
116
145
  }
117
146
 
118
147
  /**
119
148
  * Method invoked when this component is disconnected.
120
149
  */
121
150
  disconnectedCallback(): void {
151
+ this.#abortController.abort();
122
152
  this.#dropdown.dispose();
123
- this.#mediaQuery.removeEventListener("change", this.#applyToDocument);
124
153
  }
125
154
 
126
155
  /**
@@ -134,7 +163,15 @@ export class ThemeDropdown extends HTMLElement {
134
163
  * Saves the current application theme into the local storage.
135
164
  */
136
165
  save(): void {
137
- localStorage.setItem(this.storageKey, this.appTheme);
166
+ const {appTheme, cookieDomain} = this;
167
+ localStorage.setItem(this.storageKey, appTheme);
168
+
169
+ if (this.cookie) void cookieStore.set({
170
+ domain: cookieDomain ? cookieDomain : null,
171
+ expires: Date.now() + (365 * 24 * 60 * 60 * 1_000),
172
+ name: this.storageKey,
173
+ value: appTheme
174
+ });
138
175
  }
139
176
 
140
177
  /**
@@ -150,11 +187,11 @@ export class ThemeDropdown extends HTMLElement {
150
187
  * Changes the current application theme.
151
188
  * @param event The dispatched event.
152
189
  */
153
- #setAppTheme(event: Event): void {
190
+ readonly #setAppTheme: (event: Event) => void = event => {
154
191
  event.preventDefault();
155
192
  this.appTheme = (event.currentTarget as HTMLButtonElement).value as AppTheme;
156
193
  this.save();
157
- }
194
+ };
158
195
 
159
196
  /**
160
197
  * Updates the alignment of the dropdown menu.
@@ -16,6 +16,11 @@ export class Toast extends HTMLElement {
16
16
  */
17
17
  static readonly #timeUnits: Intl.RelativeTimeFormatUnit[] = ["second", "minute", "hour"];
18
18
 
19
+ /**
20
+ * The abort controller used to remove the event listeners.
21
+ */
22
+ readonly #abortController = new AbortController;
23
+
19
24
  /**
20
25
  * The formatter used to format the relative time.
21
26
  */
@@ -173,8 +178,8 @@ export class Toast extends HTMLElement {
173
178
  */
174
179
  connectedCallback(): void {
175
180
  const root = this.firstElementChild!;
176
- root.addEventListener("hide.bs.toast", this.#stopTimer);
177
- root.addEventListener("show.bs.toast", this.#startTimer);
181
+ root.addEventListener("hide.bs.toast", this.#stopTimer, {signal: this.#abortController.signal});
182
+ root.addEventListener("show.bs.toast", this.#startTimer, {signal: this.#abortController.signal});
178
183
 
179
184
  this.#toast = new BootstrapToast(root);
180
185
  if (this.open) this.show();
@@ -184,11 +189,8 @@ export class Toast extends HTMLElement {
184
189
  * Method invoked when this component is disconnected.
185
190
  */
186
191
  disconnectedCallback(): void {
187
- const root = this.firstElementChild!;
188
- root.removeEventListener("hide.bs.toast", () => this.#stopTimer);
189
- root.removeEventListener("show.bs.toast", () => this.#startTimer);
190
-
191
192
  this.#stopTimer();
193
+ this.#abortController.abort();
192
194
  this.#toast.dispose();
193
195
  }
194
196
 
@@ -67,8 +67,7 @@ export class Toaster extends HTMLElement {
67
67
  */
68
68
  constructor() {
69
69
  super();
70
- for (const toast of this.querySelectorAll("toaster-item"))
71
- toast.addEventListener("hidden.bs.toast", () => toast.remove());
70
+ for (const toast of this.querySelectorAll("toaster-item")) toast.addEventListener("hidden.bs.toast", () => toast.remove());
72
71
  }
73
72
 
74
73
  /**