@cedx/base 0.30.0 → 0.32.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 (53) hide show
  1. package/ReadMe.md +1 -1
  2. package/lib/Date.d.ts.map +1 -1
  3. package/lib/DateRange.d.ts +106 -0
  4. package/lib/DateRange.d.ts.map +1 -0
  5. package/lib/DateRange.js +136 -0
  6. package/lib/File.d.ts.map +1 -1
  7. package/lib/Number.d.ts.map +1 -1
  8. package/lib/String.d.ts +7 -4
  9. package/lib/String.d.ts.map +1 -1
  10. package/lib/String.js +7 -6
  11. package/lib/TimeSpan.d.ts.map +1 -1
  12. package/lib/UI/Components/BackButton.d.ts +1 -1
  13. package/lib/UI/Components/BackButton.d.ts.map +1 -1
  14. package/lib/UI/Components/BackButton.js +3 -3
  15. package/lib/UI/Components/DialogBox.js +5 -5
  16. package/lib/UI/Components/FullScreenToggler.d.ts +1 -1
  17. package/lib/UI/Components/FullScreenToggler.d.ts.map +1 -1
  18. package/lib/UI/Components/FullScreenToggler.js +10 -7
  19. package/lib/UI/Components/KeyboardAccelerator.d.ts.map +1 -1
  20. package/lib/UI/Components/KeyboardAccelerator.js +6 -2
  21. package/lib/UI/Components/LoadingIndicator.d.ts +4 -0
  22. package/lib/UI/Components/LoadingIndicator.d.ts.map +1 -1
  23. package/lib/UI/Components/LoadingIndicator.js +6 -0
  24. package/lib/UI/Components/MenuActivator.d.ts.map +1 -1
  25. package/lib/UI/Components/MenuActivator.js +7 -4
  26. package/lib/UI/Components/OfflineIndicator.d.ts +4 -0
  27. package/lib/UI/Components/OfflineIndicator.d.ts.map +1 -1
  28. package/lib/UI/Components/OfflineIndicator.js +12 -3
  29. package/lib/UI/Components/TabActivator.d.ts.map +1 -1
  30. package/lib/UI/Components/ThemeDropdown.d.ts.map +1 -1
  31. package/lib/UI/Components/ThemeDropdown.js +9 -5
  32. package/lib/UI/Components/Toast.d.ts.map +1 -1
  33. package/lib/UI/Components/Toast.js +7 -5
  34. package/lib/UI/Components/Toaster.d.ts.map +1 -1
  35. package/package.json +6 -6
  36. package/src/Client/Base/DateRange.ts +173 -0
  37. package/src/Client/{String.ts → Base/String.ts} +7 -6
  38. package/src/Client/Base/tsconfig.json +2 -2
  39. package/src/Client/UI/Components/BackButton.ts +3 -3
  40. package/src/Client/UI/Components/DialogBox.ts +5 -5
  41. package/src/Client/UI/Components/FullScreenToggler.ts +11 -7
  42. package/src/Client/UI/Components/KeyboardAccelerator.ts +7 -2
  43. package/src/Client/UI/Components/LoadingIndicator.ts +7 -0
  44. package/src/Client/UI/Components/MenuActivator.ts +8 -4
  45. package/src/Client/UI/Components/OfflineIndicator.ts +14 -2
  46. package/src/Client/UI/Components/TabActivator.ts +1 -0
  47. package/src/Client/UI/Components/ThemeDropdown.ts +10 -6
  48. package/src/Client/UI/Components/Toast.ts +8 -6
  49. package/src/Client/UI/Components/Toaster.ts +1 -2
  50. /package/src/Client/{Date.ts → Base/Date.ts} +0 -0
  51. /package/src/Client/{File.ts → Base/File.ts} +0 -0
  52. /package/src/Client/{Number.ts → Base/Number.ts} +0 -0
  53. /package/src/Client/{TimeSpan.ts → Base/TimeSpan.ts} +0 -0
@@ -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"}
@@ -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,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"}
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.
@@ -122,7 +126,7 @@ export class ThemeDropdown extends HTMLElement {
122
126
  */
123
127
  async connectedCallback() {
124
128
  this.#dropdown = new Dropdown(this.querySelector(".dropdown-toggle"));
125
- this.#mediaQuery.addEventListener("change", this.#applyToDocument);
129
+ this.#mediaQuery.addEventListener("change", this.#applyToDocument, { signal: this.#abortController.signal });
126
130
  const cookie = this.cookie ? await cookieStore.get(this.storageKey) : null;
127
131
  if (cookie)
128
132
  this.appTheme = cookie.value;
@@ -136,8 +140,8 @@ export class ThemeDropdown extends HTMLElement {
136
140
  * Method invoked when this component is disconnected.
137
141
  */
138
142
  disconnectedCallback() {
143
+ this.#abortController.abort();
139
144
  this.#dropdown.dispose();
140
- this.#mediaQuery.removeEventListener("change", this.#applyToDocument);
141
145
  }
142
146
  /**
143
147
  * Opens the dropdown menu.
@@ -171,11 +175,11 @@ export class ThemeDropdown extends HTMLElement {
171
175
  * Changes the current application theme.
172
176
  * @param event The dispatched event.
173
177
  */
174
- #setAppTheme(event) {
178
+ #setAppTheme = event => {
175
179
  event.preventDefault();
176
180
  this.appTheme = event.currentTarget.value;
177
181
  this.save();
178
- }
182
+ };
179
183
  /**
180
184
  * Updates the alignment of the dropdown menu.
181
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.30.0",
10
+ "version": "0.32.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.1",
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"
@@ -0,0 +1,173 @@
1
+ import {atMidnight, daysInMonth, getQuarter, getWeekOfYear} from "./Date.js";
2
+ import {TimeSpan} from "./TimeSpan.js";
3
+
4
+ /**
5
+ * Defines the type of a date range.
6
+ */
7
+ export const DateRangeType = Object.freeze({
8
+
9
+ /**
10
+ * A custom date range.
11
+ */
12
+ Custom: "Custom",
13
+
14
+ /**
15
+ * A whole day.
16
+ */
17
+ Day: "Day",
18
+
19
+ /**
20
+ * A whole week.
21
+ */
22
+ Week: "Week",
23
+
24
+ /**
25
+ * A whole month.
26
+ */
27
+ Month: "Month",
28
+
29
+ /**
30
+ * A whole quarter.
31
+ */
32
+ Quarter: "Quarter",
33
+
34
+ /**
35
+ * A whole year.
36
+ */
37
+ Year: "Year"
38
+ });
39
+
40
+ /**
41
+ * Defines the type of a date range.
42
+ */
43
+ export type DateRangeType = typeof DateRangeType[keyof typeof DateRangeType];
44
+
45
+ /**
46
+ * Defines a date range.
47
+ */
48
+ export class DateRange {
49
+
50
+ /**
51
+ * The end date.
52
+ */
53
+ readonly end: Date;
54
+
55
+ /**
56
+ * The start date.
57
+ */
58
+ readonly start: Date;
59
+
60
+ /**
61
+ * The range type.
62
+ */
63
+ readonly type: DateRangeType;
64
+
65
+ /**
66
+ * Creates a new date range.
67
+ * @param start The start date.
68
+ * @param end The end date.
69
+ * @param type The range type.
70
+ */
71
+ constructor(start: Date, end: Date, type: DateRangeType = DateRangeType.Custom) {
72
+ this.start = start;
73
+ this.end = end;
74
+ this.type = type;
75
+ }
76
+
77
+ /**
78
+ * Creates a new date range from the specified JSON object.
79
+ * @param json A JSON object representing a date range.
80
+ * @returns The instance corresponding to the specified JSON object.
81
+ */
82
+ static fromJson(json: Record<string, any>): DateRange {
83
+ return new this(
84
+ new Date(typeof json.start == "string" ? json.start : Date.now()),
85
+ new Date(typeof json.end == "string" ? json.end : Date.now()),
86
+ Object.values(DateRangeType).includes(json.type as DateRangeType) ? json.type as DateRangeType : DateRangeType.Custom
87
+ );
88
+ }
89
+
90
+ /**
91
+ * Creates a date range corresponding to the day including the specified date.
92
+ * @param date The date.
93
+ * @returns The date range corresponding to the day including the specified date.
94
+ */
95
+ static day(date: Date): DateRange {
96
+ return new this(atMidnight(date), new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999), DateRangeType.Day);
97
+ }
98
+
99
+ /**
100
+ * Creates a date range corresponding to the month including the specified date.
101
+ * @param date The date.
102
+ * @returns The date range corresponding to the month including the specified date.
103
+ */
104
+ static month(date: Date): DateRange {
105
+ return new this(
106
+ new Date(date.getFullYear(), date.getMonth(), 1),
107
+ new Date(date.getFullYear(), date.getMonth(), daysInMonth(date), 23, 59, 59, 999),
108
+ DateRangeType.Month
109
+ );
110
+ }
111
+
112
+ /**
113
+ * Creates a date range corresponding to the quarter including the specified date.
114
+ * @param date The date.
115
+ * @returns The date range corresponding to the quarter including the specified date.
116
+ */
117
+ static quarter(date: Date): DateRange {
118
+ const firstMonth = (getQuarter(date) * 3) - 3;
119
+ const lastMonth = firstMonth + 2;
120
+ return new this(
121
+ new Date(date.getFullYear(), firstMonth, 1),
122
+ new Date(date.getFullYear(), lastMonth, daysInMonth(new Date(date.getFullYear(), lastMonth, 1)), 23, 59, 59, 999),
123
+ DateRangeType.Quarter
124
+ );
125
+ }
126
+
127
+ /**
128
+ * Creates a date range corresponding to the week including the specified date.
129
+ * @param date The date.
130
+ * @returns The date range corresponding to the week including the specified date.
131
+ */
132
+ static week(date: Date): DateRange {
133
+ const delta = -((date.getDay() + 6) % 7);
134
+ const sunday = new Date(date.getTime() + ((delta + 6) * TimeSpan.MillisecondsPerDay));
135
+ sunday.setHours(23, 59, 59, 999);
136
+ return new this(atMidnight(new Date(date.getTime() + (delta * TimeSpan.MillisecondsPerDay))), sunday, DateRangeType.Week);
137
+ }
138
+
139
+ /**
140
+ * Creates a date range corresponding to the year including the specified date.
141
+ * @param date The date.
142
+ * @returns The date range corresponding to the year including the specified date.
143
+ */
144
+ static year(date: Date): DateRange {
145
+ return new this(new Date(date.getFullYear(), 0, 1), new Date(date.getFullYear(), 11, 31, 23, 59, 59, 999), DateRangeType.Year);
146
+ }
147
+
148
+ /**
149
+ * Returns a value indicating whether this date range is equal to the specified date range.
150
+ * @param other The other date range to compare.
151
+ * @returns `true` if this date range is equal to the specified date range, otherwise `false`.
152
+ */
153
+ equals(other: DateRange): boolean {
154
+ return this.start.getTime() == other.start.getTime() && this.end.getTime() == other.end.getTime();
155
+ }
156
+
157
+ /**
158
+ * The label corresponding to this date range.
159
+ * @param culture The current culture.
160
+ * @returns The label corresponding to this date range.
161
+ */
162
+ getLabel(culture: Intl.Locale|string = navigator.language): string {
163
+ const {start, end, type} = this;
164
+ switch (type) {
165
+ case DateRangeType.Day: return start.toLocaleString(culture, {dateStyle: "medium"});
166
+ case DateRangeType.Week: return `S${getWeekOfYear(start)} ${start.getFullYear()}`;
167
+ case DateRangeType.Month: return start.toLocaleString(culture, {month: "long", year: "numeric"});
168
+ case DateRangeType.Quarter: return `T${getQuarter(start)} ${start.getFullYear()}`;
169
+ case DateRangeType.Year: return start.getFullYear().toString();
170
+ default: return end.toLocaleString(culture, {dateStyle: "medium"});
171
+ }
172
+ }
173
+ }
@@ -1,20 +1,21 @@
1
1
  /**
2
- * Converts the first character to uppercase.
2
+ * Converts the first character of the specified string to uppercase.
3
3
  * @param value The string to process.
4
- * @param locale The current locale.
4
+ * @param culture The current culture.
5
5
  * @returns The processed string.
6
6
  */
7
- export function capitalize(value: string, locale: Intl.Locale|string = navigator.language): string {
8
- return value.charAt(0).toLocaleUpperCase(locale) + value.slice(1);
7
+ export function capitalize(value: string, culture: Intl.Locale|string = navigator.language): string {
8
+ return value.length == 0 ? "" : value.charAt(0).toLocaleUpperCase(culture) + value.slice(1);
9
9
  }
10
10
 
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
  /**
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "extends": "../../../tsconfig.json",
3
- "include": ["../*.ts"],
3
+ "include": ["**/*.ts"],
4
4
  "compilerOptions": {
5
5
  "composite": true,
6
6
  "declaration": true,
7
7
  "declarationMap": true,
8
8
  "noEmit": false,
9
9
  "outDir": "../../../lib",
10
- "rootDir": "..",
10
+ "rootDir": ".",
11
11
  "tsBuildInfoFile": "../../../var/Base.tsbuildinfo"
12
12
  }
13
13
  }
@@ -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
  /**
@@ -130,7 +134,7 @@ export class ThemeDropdown extends HTMLElement {
130
134
  */
131
135
  async connectedCallback(): Promise<void> {
132
136
  this.#dropdown = new Dropdown(this.querySelector(".dropdown-toggle")!);
133
- this.#mediaQuery.addEventListener("change", this.#applyToDocument);
137
+ this.#mediaQuery.addEventListener("change", this.#applyToDocument, {signal: this.#abortController.signal});
134
138
 
135
139
  const cookie = this.cookie ? await cookieStore.get(this.storageKey) : null;
136
140
  if (cookie) this.appTheme = cookie.value as AppTheme;
@@ -144,8 +148,8 @@ export class ThemeDropdown extends HTMLElement {
144
148
  * Method invoked when this component is disconnected.
145
149
  */
146
150
  disconnectedCallback(): void {
151
+ this.#abortController.abort();
147
152
  this.#dropdown.dispose();
148
- this.#mediaQuery.removeEventListener("change", this.#applyToDocument);
149
153
  }
150
154
 
151
155
  /**
@@ -183,11 +187,11 @@ export class ThemeDropdown extends HTMLElement {
183
187
  * Changes the current application theme.
184
188
  * @param event The dispatched event.
185
189
  */
186
- #setAppTheme(event: Event): void {
190
+ readonly #setAppTheme: (event: Event) => void = event => {
187
191
  event.preventDefault();
188
192
  this.appTheme = (event.currentTarget as HTMLButtonElement).value as AppTheme;
189
193
  this.save();
190
- }
194
+ };
191
195
 
192
196
  /**
193
197
  * Updates the alignment of the dropdown menu.