@cedx/base 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ReadMe.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Cédric Belin's Base
2
- ![.NET](https://badgen.net/badge/.net/%3E%3D9.0/green) ![Version](https://badgen.net/badge/project/v0.26.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.28.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/Date.d.ts CHANGED
@@ -52,6 +52,11 @@ export declare function isHoliday(date: Date): boolean;
52
52
  * @returns `true` if the specified date is a working day, otherwise `false`.
53
53
  */
54
54
  export declare function isWorkingDay(date: Date): boolean;
55
+ /**
56
+ * Gets the current date.
57
+ * @returns The current date.
58
+ */
59
+ export declare function today(): Date;
55
60
  /**
56
61
  * Converts the specified date to an ISO 8601 week.
57
62
  * @param date The date to convert.
package/lib/Date.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Date.d.ts","sourceRoot":"","sources":["../src/Client/Date.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAE3C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAc1C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,CAqB9C;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE7C;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAIhD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAG9C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAG7C;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAGhD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE5C;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAM,GAAG,MAAM,CAM7H"}
1
+ {"version":3,"file":"Date.d.ts","sourceRoot":"","sources":["../src/Client/Date.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAE3C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAc1C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,CAqB9C;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE7C;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAIhD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAG9C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAG7C;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAGhD;AAED;;;GAGG;AACH,wBAAgB,KAAK,IAAI,IAAI,CAI5B;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE5C;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAM,GAAG,MAAM,CAM7H"}
package/lib/Date.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Duration } from "./Duration.js";
1
+ import { TimeSpan } from "./TimeSpan.js";
2
2
  /**
3
3
  * Returns the date at midnight corresponding to the specified date.
4
4
  * @param date The source date.
@@ -52,9 +52,9 @@ export function getHolidays(date) {
52
52
  holidays.push(new Date(year, 11, 25)); // Christmas.
53
53
  // Holidays depending on Easter.
54
54
  const easter = getEaster(date);
55
- holidays.push(new Date(easter.getTime() + Duration.Day)); // Easter monday.
56
- holidays.push(new Date(easter.getTime() + (39 * Duration.Day))); // Ascension thursday.
57
- holidays.push(new Date(easter.getTime() + (50 * Duration.Day))); // Pentecost monday.
55
+ holidays.push(new Date(easter.getTime() + TimeSpan.MillisecondsPerDay)); // Easter monday.
56
+ holidays.push(new Date(easter.getTime() + (39 * TimeSpan.MillisecondsPerDay))); // Ascension thursday.
57
+ holidays.push(new Date(easter.getTime() + (50 * TimeSpan.MillisecondsPerDay))); // Pentecost monday.
58
58
  return holidays;
59
59
  }
60
60
  /**
@@ -73,7 +73,7 @@ export function getQuarter(date) {
73
73
  export function getWeekOfYear(date) {
74
74
  const thursday = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 3 - ((date.getDay() + 6) % 7));
75
75
  const firstWeek = new Date(thursday.getFullYear(), 0, 4);
76
- return 1 + Math.round((((thursday.getTime() - firstWeek.getTime()) / Duration.Day) - 3 + ((firstWeek.getDay() + 6) % 7)) / 7);
76
+ return 1 + Math.round((((thursday.getTime() - firstWeek.getTime()) / TimeSpan.MillisecondsPerDay) - 3 + ((firstWeek.getDay() + 6) % 7)) / 7);
77
77
  }
78
78
  /**
79
79
  * Returns a value indicating whether the specified date is in a leap year.
@@ -102,6 +102,15 @@ export function isWorkingDay(date) {
102
102
  const dayOfWeek = date.getDay();
103
103
  return dayOfWeek >= 1 && dayOfWeek <= 5;
104
104
  }
105
+ /**
106
+ * Gets the current date.
107
+ * @returns The current date.
108
+ */
109
+ export function today() {
110
+ const date = new Date;
111
+ date.setHours(0, 0, 0, 0);
112
+ return date;
113
+ }
105
114
  /**
106
115
  * Converts the specified date to an ISO 8601 week.
107
116
  * @param date The date to convert.
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Provides some common time intervals in milliseconds.
3
+ */
4
+ export declare const TimeSpan: Readonly<{
5
+ /**
6
+ * The number of hours in 1 day.
7
+ */
8
+ HoursPerDay: 24;
9
+ /**
10
+ * The number of minutes in 1 day.
11
+ */
12
+ MinutesPerDay: 1440;
13
+ /**
14
+ * The number of minutes in 1 hour.
15
+ */
16
+ MinutesPerHour: 60;
17
+ /**
18
+ * The number of seconds in 1 day.
19
+ */
20
+ SecondsPerDay: 86400;
21
+ /**
22
+ * The number of seconds in 1 hour.
23
+ */
24
+ SecondsPerHour: 3600;
25
+ /**
26
+ * The number of seconds in 1 minute.
27
+ */
28
+ SecondsPerMinute: 60;
29
+ /**
30
+ * The number of milliseconds in 1 day.
31
+ */
32
+ MillisecondsPerDay: 86400000;
33
+ /**
34
+ * The number of milliseconds in 1 hour.
35
+ */
36
+ MillisecondsPerHour: 3600000;
37
+ /**
38
+ * The number of milliseconds in 1 minute.
39
+ */
40
+ MillisecondsPerMinute: 60000;
41
+ /**
42
+ * The number of milliseconds in 1 second.
43
+ */
44
+ MillisecondsPerSecond: 1000;
45
+ }>;
46
+ //# sourceMappingURL=TimeSpan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimeSpan.d.ts","sourceRoot":"","sources":["../src/Client/TimeSpan.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,QAAQ;IAEpB;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;EAEF,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Provides some common time intervals in milliseconds.
3
+ */
4
+ export const TimeSpan = Object.freeze({
5
+ /**
6
+ * The number of hours in 1 day.
7
+ */
8
+ HoursPerDay: 24,
9
+ /**
10
+ * The number of minutes in 1 day.
11
+ */
12
+ MinutesPerDay: 1_440,
13
+ /**
14
+ * The number of minutes in 1 hour.
15
+ */
16
+ MinutesPerHour: 60,
17
+ /**
18
+ * The number of seconds in 1 day.
19
+ */
20
+ SecondsPerDay: 86_400,
21
+ /**
22
+ * The number of seconds in 1 hour.
23
+ */
24
+ SecondsPerHour: 3_600,
25
+ /**
26
+ * The number of seconds in 1 minute.
27
+ */
28
+ SecondsPerMinute: 60,
29
+ /**
30
+ * The number of milliseconds in 1 day.
31
+ */
32
+ MillisecondsPerDay: 86_400_000,
33
+ /**
34
+ * The number of milliseconds in 1 hour.
35
+ */
36
+ MillisecondsPerHour: 3_600_000,
37
+ /**
38
+ * The number of milliseconds in 1 minute.
39
+ */
40
+ MillisecondsPerMinute: 60_000,
41
+ /**
42
+ * The number of milliseconds in 1 second.
43
+ */
44
+ MillisecondsPerSecond: 1_000
45
+ });
@@ -28,7 +28,7 @@ export declare class FullScreenToggler extends HTMLElement {
28
28
  /**
29
29
  * Toggles the full-screen mode of the associated element.
30
30
  * @param event The dispatched event.
31
- * @returns Resolves when the full-screen mode has been toggled.
31
+ * @returns Completes when the full-screen mode has been toggled.
32
32
  */
33
33
  toggleFullScreen(event?: Event): Promise<void>;
34
34
  }
@@ -60,7 +60,7 @@ export class FullScreenToggler extends HTMLElement {
60
60
  /**
61
61
  * Toggles the full-screen mode of the associated element.
62
62
  * @param event The dispatched event.
63
- * @returns Resolves when the full-screen mode has been toggled.
63
+ * @returns Completes when the full-screen mode has been toggled.
64
64
  */
65
65
  async toggleFullScreen(event) {
66
66
  event?.stopPropagation();
@@ -71,7 +71,7 @@ export class FullScreenToggler extends HTMLElement {
71
71
  }
72
72
  /**
73
73
  * Acquires a new wake lock.
74
- * @returns Resolves when the wake lock has been acquired.
74
+ * @returns Completes when the wake lock has been acquired.
75
75
  */
76
76
  async #acquireWakeLock() {
77
77
  if (this.#sentinel && !this.#sentinel.released)
@@ -99,7 +99,7 @@ export class FullScreenToggler extends HTMLElement {
99
99
  };
100
100
  /**
101
101
  * Releases the acquired wake lock.
102
- * @returns Resolves when the wake lock has been released.
102
+ * @returns Completes when the wake lock has been released.
103
103
  */
104
104
  async #releaseWakeLock() {
105
105
  if (!this.#sentinel || this.#sentinel.released)
@@ -60,7 +60,7 @@ export class Toaster extends HTMLElement {
60
60
  */
61
61
  get delay() {
62
62
  const value = Number(this.getAttribute("delay"));
63
- return Math.max(1, Number.isNaN(value) ? 5_000 : value);
63
+ return Math.max(0, Number.isNaN(value) ? 5_000 : value);
64
64
  }
65
65
  set delay(value) {
66
66
  this.setAttribute("delay", value.toString());
@@ -0,0 +1,53 @@
1
+ /**
2
+ * A component that moves back in the session history when clicked.
3
+ */
4
+ export declare class TypeAhead extends HTMLElement {
5
+ #private;
6
+ /**
7
+ * The list of observed attributes.
8
+ */
9
+ static readonly observedAttributes: string[];
10
+ /**
11
+ * The delay in milliseconds to wait before triggering autocomplete suggestions.
12
+ */
13
+ get delay(): number;
14
+ set delay(value: number);
15
+ /**
16
+ * The function invoked when the query has been changed.
17
+ */
18
+ set handler(callback: (query: string) => Promise<string[]>);
19
+ /**
20
+ * The data list identifier.
21
+ */
22
+ get list(): string;
23
+ set list(value: string);
24
+ /**
25
+ * The minimum character length needed before triggering autocomplete suggestions.
26
+ */
27
+ get minLength(): number;
28
+ set minLength(value: number);
29
+ /**
30
+ * The query to look up.
31
+ */
32
+ get query(): string;
33
+ set query(value: string);
34
+ /**
35
+ * Method invoked when an attribute has been changed.
36
+ * @param attribute The attribute name.
37
+ * @param oldValue The previous attribute value.
38
+ * @param newValue The new attribute value.
39
+ */
40
+ attributeChangedCallback(attribute: string, oldValue: string | null, newValue: string | null): void;
41
+ }
42
+ /**
43
+ * Declaration merging.
44
+ */
45
+ declare global {
46
+ /**
47
+ * The map of HTML tag names.
48
+ */
49
+ interface HTMLElementTagNameMap {
50
+ "type-ahead": TypeAhead;
51
+ }
52
+ }
53
+ //# sourceMappingURL=TypeAhead.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TypeAhead.d.ts","sourceRoot":"","sources":["../../../src/Client/UI/Components/TypeAhead.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,SAAU,SAAQ,WAAW;;IAEzC;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,kBAAkB,WAAqB;IAmBvD;;OAEG;IACH,IAAI,KAAK,IAAI,MAAM,CAGlB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAEtB;IAED;;OAEG;IACH,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,EAKzD;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAGtB;IACD,IAAI,SAAS,CAAC,KAAK,EAAE,MAAM,EAE1B;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,MAAM,CAElB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,EAEtB;IAED;;;;;OAKG;IACH,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAC,IAAI,GAAG,IAAI;CAsD/F;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,YAAY,EAAE,SAAS,CAAC;KACxB;CACD"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * A component that moves back in the session history when clicked.
3
+ */
4
+ export class TypeAhead extends HTMLElement {
5
+ /**
6
+ * The list of observed attributes.
7
+ */
8
+ static observedAttributes = ["list", "query"];
9
+ /**
10
+ * The debounced handler used to look up the query.
11
+ */
12
+ #debounced = () => { };
13
+ /**
14
+ * The previous query.
15
+ */
16
+ #previousQuery = "";
17
+ /**
18
+ * Registers the component.
19
+ */
20
+ static {
21
+ customElements.define("type-ahead", this);
22
+ }
23
+ /**
24
+ * The delay in milliseconds to wait before triggering autocomplete suggestions.
25
+ */
26
+ get delay() {
27
+ const value = Number(this.getAttribute("delay"));
28
+ return Math.max(0, Number.isNaN(value) ? 300 : value);
29
+ }
30
+ set delay(value) {
31
+ this.setAttribute("delay", value.toString());
32
+ }
33
+ /**
34
+ * The function invoked when the query has been changed.
35
+ */
36
+ set handler(callback) {
37
+ this.#debounced = this.#debounce(async (query) => {
38
+ try {
39
+ this.#updateItems(await callback(query));
40
+ }
41
+ catch {
42
+ this.#updateItems([]);
43
+ }
44
+ }, this.delay);
45
+ }
46
+ /**
47
+ * The data list identifier.
48
+ */
49
+ get list() {
50
+ return (this.getAttribute("list") ?? "").trim();
51
+ }
52
+ set list(value) {
53
+ this.setAttribute("list", value);
54
+ }
55
+ /**
56
+ * The minimum character length needed before triggering autocomplete suggestions.
57
+ */
58
+ get minLength() {
59
+ const value = Number(this.getAttribute("minlength"));
60
+ return Math.max(1, Number.isNaN(value) ? 1 : value);
61
+ }
62
+ set minLength(value) {
63
+ this.setAttribute("minlength", value.toString());
64
+ }
65
+ /**
66
+ * The query to look up.
67
+ */
68
+ get query() {
69
+ return (this.getAttribute("query") ?? "").trim();
70
+ }
71
+ set query(value) {
72
+ this.setAttribute("query", value);
73
+ }
74
+ /**
75
+ * Method invoked when an attribute has been changed.
76
+ * @param attribute The attribute name.
77
+ * @param oldValue The previous attribute value.
78
+ * @param newValue The new attribute value.
79
+ */
80
+ attributeChangedCallback(attribute, oldValue, newValue) {
81
+ if (newValue != oldValue)
82
+ switch (attribute) {
83
+ case "list":
84
+ this.#updateList(newValue ?? "");
85
+ break;
86
+ case "query":
87
+ this.#updateQuery(newValue ?? "");
88
+ break;
89
+ // No default
90
+ }
91
+ }
92
+ /**
93
+ * Postpones the invocation of the specified callback until after a given delay has elapsed since the last time it was invoked.
94
+ * @param callback The function to debounce.
95
+ * @param delay The delay to wait, in milliseconds.
96
+ * @returns The debounced function.
97
+ */
98
+ #debounce(callback, delay) {
99
+ let timer = 0;
100
+ return value => {
101
+ if (timer)
102
+ clearTimeout(timer);
103
+ timer = window.setTimeout(callback, delay, value);
104
+ };
105
+ }
106
+ /**
107
+ * Updates the identifier of the underlying data list.
108
+ * @param value The new value.
109
+ */
110
+ #updateList(value) {
111
+ this.firstElementChild.id = value.trim();
112
+ }
113
+ /**
114
+ * Updates the query to look up.
115
+ * @param value The new value.
116
+ */
117
+ #updateQuery(value) {
118
+ const query = value.trim();
119
+ if (query != this.#previousQuery) {
120
+ this.#previousQuery = query;
121
+ if (query.length < this.minLength)
122
+ this.#updateItems([]);
123
+ else
124
+ this.#debounced(query);
125
+ }
126
+ }
127
+ /**
128
+ * Updates the items of the underlying data list.
129
+ * @param values The new values.
130
+ */
131
+ #updateItems(values) {
132
+ this.firstElementChild.replaceChildren(...values.map(value => {
133
+ const option = document.createElement("option");
134
+ option.value = value;
135
+ return option;
136
+ }));
137
+ }
138
+ }
@@ -27,8 +27,4 @@ export declare const DialogResult: Readonly<{
27
27
  */
28
28
  Ignore: "Ignore";
29
29
  }>;
30
- /**
31
- * Specifies the return value of a dialog box.
32
- */
33
- export type DialogResult = typeof DialogResult[keyof typeof DialogResult];
34
30
  //# sourceMappingURL=DialogResult.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DialogResult.d.ts","sourceRoot":"","sources":["../../src/Client/UI/DialogResult.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,YAAY;IAExB;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;EAEF,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC"}
1
+ {"version":3,"file":"DialogResult.d.ts","sourceRoot":"","sources":["../../src/Client/UI/DialogResult.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,YAAY;IAExB;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;EAEF,CAAC"}
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "name": "@cedx/base",
8
8
  "repository": "cedx/base",
9
9
  "type": "module",
10
- "version": "0.26.0",
10
+ "version": "0.28.0",
11
11
  "devDependencies": {
12
12
  "@playwright/browser-chromium": "^1.55.0",
13
13
  "@types/bootstrap": "^5.2.10",
@@ -18,12 +18,12 @@
18
18
  "chai": "^6.0.1",
19
19
  "esbuild": "^0.25.9",
20
20
  "globals": "^16.3.0",
21
- "mocha": "^11.7.1",
21
+ "mocha": "^11.7.2",
22
22
  "playwright": "^1.55.0",
23
23
  "serve-handler": "^6.1.6",
24
24
  "typedoc": "^0.28.12",
25
25
  "typescript": "^5.9.2",
26
- "typescript-eslint": "^8.41.0"
26
+ "typescript-eslint": "^8.42.0"
27
27
  },
28
28
  "engines": {
29
29
  "node": ">=24.0.0"
@@ -1,4 +1,4 @@
1
- import {Duration} from "./Duration.js";
1
+ import {TimeSpan} from "./TimeSpan.js";
2
2
 
3
3
  /**
4
4
  * Returns the date at midnight corresponding to the specified date.
@@ -60,9 +60,9 @@ export function getHolidays(date: Date): Date[] {
60
60
 
61
61
  // Holidays depending on Easter.
62
62
  const easter = getEaster(date);
63
- holidays.push(new Date(easter.getTime() + Duration.Day)); // Easter monday.
64
- holidays.push(new Date(easter.getTime() + (39 * Duration.Day))); // Ascension thursday.
65
- holidays.push(new Date(easter.getTime() + (50 * Duration.Day))); // Pentecost monday.
63
+ holidays.push(new Date(easter.getTime() + TimeSpan.MillisecondsPerDay)); // Easter monday.
64
+ holidays.push(new Date(easter.getTime() + (39 * TimeSpan.MillisecondsPerDay))); // Ascension thursday.
65
+ holidays.push(new Date(easter.getTime() + (50 * TimeSpan.MillisecondsPerDay))); // Pentecost monday.
66
66
 
67
67
  return holidays;
68
68
  }
@@ -84,7 +84,7 @@ export function getQuarter(date: Date): number {
84
84
  export function getWeekOfYear(date: Date): number {
85
85
  const thursday = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 3 - ((date.getDay() + 6) % 7));
86
86
  const firstWeek = new Date(thursday.getFullYear(), 0, 4);
87
- return 1 + Math.round((((thursday.getTime() - firstWeek.getTime()) / Duration.Day) - 3 + ((firstWeek.getDay() + 6) % 7)) / 7);
87
+ return 1 + Math.round((((thursday.getTime() - firstWeek.getTime()) / TimeSpan.MillisecondsPerDay) - 3 + ((firstWeek.getDay() + 6) % 7)) / 7);
88
88
  }
89
89
 
90
90
  /**
@@ -117,6 +117,16 @@ export function isWorkingDay(date: Date): boolean {
117
117
  return dayOfWeek >= 1 && dayOfWeek <= 5;
118
118
  }
119
119
 
120
+ /**
121
+ * Gets the current date.
122
+ * @returns The current date.
123
+ */
124
+ export function today(): Date {
125
+ const date = new Date;
126
+ date.setHours(0, 0, 0, 0);
127
+ return date;
128
+ }
129
+
120
130
  /**
121
131
  * Converts the specified date to an ISO 8601 week.
122
132
  * @param date The date to convert.
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Provides some common time intervals in milliseconds.
3
+ */
4
+ export const TimeSpan = Object.freeze({
5
+
6
+ /**
7
+ * The number of hours in 1 day.
8
+ */
9
+ HoursPerDay: 24,
10
+
11
+ /**
12
+ * The number of minutes in 1 day.
13
+ */
14
+ MinutesPerDay: 1_440,
15
+
16
+ /**
17
+ * The number of minutes in 1 hour.
18
+ */
19
+ MinutesPerHour: 60,
20
+
21
+ /**
22
+ * The number of seconds in 1 day.
23
+ */
24
+ SecondsPerDay: 86_400,
25
+
26
+ /**
27
+ * The number of seconds in 1 hour.
28
+ */
29
+ SecondsPerHour: 3_600,
30
+
31
+ /**
32
+ * The number of seconds in 1 minute.
33
+ */
34
+ SecondsPerMinute: 60,
35
+
36
+ /**
37
+ * The number of milliseconds in 1 day.
38
+ */
39
+ MillisecondsPerDay: 86_400_000,
40
+
41
+ /**
42
+ * The number of milliseconds in 1 hour.
43
+ */
44
+ MillisecondsPerHour: 3_600_000,
45
+
46
+ /**
47
+ * The number of milliseconds in 1 minute.
48
+ */
49
+ MillisecondsPerMinute: 60_000,
50
+
51
+ /**
52
+ * The number of milliseconds in 1 second.
53
+ */
54
+ MillisecondsPerSecond: 1_000
55
+ });
@@ -69,7 +69,7 @@ export class FullScreenToggler extends HTMLElement {
69
69
  /**
70
70
  * Toggles the full-screen mode of the associated element.
71
71
  * @param event The dispatched event.
72
- * @returns Resolves when the full-screen mode has been toggled.
72
+ * @returns Completes when the full-screen mode has been toggled.
73
73
  */
74
74
  async toggleFullScreen(event?: Event): Promise<void> {
75
75
  event?.stopPropagation();
@@ -79,7 +79,7 @@ export class FullScreenToggler extends HTMLElement {
79
79
 
80
80
  /**
81
81
  * Acquires a new wake lock.
82
- * @returns Resolves when the wake lock has been acquired.
82
+ * @returns Completes when the wake lock has been acquired.
83
83
  */
84
84
  async #acquireWakeLock(): Promise<void> {
85
85
  if (this.#sentinel && !this.#sentinel.released) return;
@@ -105,7 +105,7 @@ export class FullScreenToggler extends HTMLElement {
105
105
 
106
106
  /**
107
107
  * Releases the acquired wake lock.
108
- * @returns Resolves when the wake lock has been released.
108
+ * @returns Completes when the wake lock has been released.
109
109
  */
110
110
  async #releaseWakeLock(): Promise<void> {
111
111
  if (!this.#sentinel || this.#sentinel.released) return;
@@ -115,7 +115,7 @@ export class Toaster extends HTMLElement {
115
115
  */
116
116
  get delay(): number {
117
117
  const value = Number(this.getAttribute("delay"));
118
- return Math.max(1, Number.isNaN(value) ? 5_000 : value);
118
+ return Math.max(0, Number.isNaN(value) ? 5_000 : value);
119
119
  }
120
120
  set delay(value: number) {
121
121
  this.setAttribute("delay", value.toString());
@@ -0,0 +1,153 @@
1
+ /**
2
+ * A component that moves back in the session history when clicked.
3
+ */
4
+ export class TypeAhead extends HTMLElement {
5
+
6
+ /**
7
+ * The list of observed attributes.
8
+ */
9
+ static readonly observedAttributes = ["list", "query"];
10
+
11
+ /**
12
+ * The debounced handler used to look up the query.
13
+ */
14
+ #debounced: (value: string) => void = () => { /* Noop */ };
15
+
16
+ /**
17
+ * The previous query.
18
+ */
19
+ #previousQuery = "";
20
+
21
+ /**
22
+ * Registers the component.
23
+ */
24
+ static {
25
+ customElements.define("type-ahead", this);
26
+ }
27
+
28
+ /**
29
+ * The delay in milliseconds to wait before triggering autocomplete suggestions.
30
+ */
31
+ get delay(): number {
32
+ const value = Number(this.getAttribute("delay"));
33
+ return Math.max(0, Number.isNaN(value) ? 300 : value);
34
+ }
35
+ set delay(value: number) {
36
+ this.setAttribute("delay", value.toString());
37
+ }
38
+
39
+ /**
40
+ * The function invoked when the query has been changed.
41
+ */
42
+ set handler(callback: (query: string) => Promise<string[]>) { // eslint-disable-line accessor-pairs
43
+ this.#debounced = this.#debounce(async query => { // eslint-disable-line @typescript-eslint/no-misused-promises
44
+ try { this.#updateItems(await callback(query)); }
45
+ catch { this.#updateItems([]); }
46
+ }, this.delay);
47
+ }
48
+
49
+ /**
50
+ * The data list identifier.
51
+ */
52
+ get list(): string {
53
+ return (this.getAttribute("list") ?? "").trim();
54
+ }
55
+ set list(value: string) {
56
+ this.setAttribute("list", value);
57
+ }
58
+
59
+ /**
60
+ * The minimum character length needed before triggering autocomplete suggestions.
61
+ */
62
+ get minLength(): number {
63
+ const value = Number(this.getAttribute("minlength"));
64
+ return Math.max(1, Number.isNaN(value) ? 1 : value);
65
+ }
66
+ set minLength(value: number) {
67
+ this.setAttribute("minlength", value.toString());
68
+ }
69
+
70
+ /**
71
+ * The query to look up.
72
+ */
73
+ get query(): string {
74
+ return (this.getAttribute("query") ?? "").trim();
75
+ }
76
+ set query(value: string) {
77
+ this.setAttribute("query", value);
78
+ }
79
+
80
+ /**
81
+ * Method invoked when an attribute has been changed.
82
+ * @param attribute The attribute name.
83
+ * @param oldValue The previous attribute value.
84
+ * @param newValue The new attribute value.
85
+ */
86
+ attributeChangedCallback(attribute: string, oldValue: string|null, newValue: string|null): void {
87
+ if (newValue != oldValue) switch (attribute) {
88
+ case "list": this.#updateList(newValue ?? ""); break;
89
+ case "query": this.#updateQuery(newValue ?? ""); break;
90
+ // No default
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Postpones the invocation of the specified callback until after a given delay has elapsed since the last time it was invoked.
96
+ * @param callback The function to debounce.
97
+ * @param delay The delay to wait, in milliseconds.
98
+ * @returns The debounced function.
99
+ */
100
+ #debounce(callback: (value: string) => void, delay: number): (value: string) => void {
101
+ let timer = 0;
102
+ return value => {
103
+ if (timer) clearTimeout(timer);
104
+ timer = window.setTimeout(callback, delay, value);
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Updates the identifier of the underlying data list.
110
+ * @param value The new value.
111
+ */
112
+ #updateList(value: string): void {
113
+ this.firstElementChild!.id = value.trim();
114
+ }
115
+
116
+ /**
117
+ * Updates the query to look up.
118
+ * @param value The new value.
119
+ */
120
+ #updateQuery(value: string): void {
121
+ const query = value.trim();
122
+ if (query != this.#previousQuery) {
123
+ this.#previousQuery = query;
124
+ if (query.length < this.minLength) this.#updateItems([]);
125
+ else this.#debounced(query);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Updates the items of the underlying data list.
131
+ * @param values The new values.
132
+ */
133
+ #updateItems(values: string[]): void {
134
+ this.firstElementChild!.replaceChildren(...values.map(value => {
135
+ const option = document.createElement("option");
136
+ option.value = value;
137
+ return option;
138
+ }));
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Declaration merging.
144
+ */
145
+ declare global {
146
+
147
+ /**
148
+ * The map of HTML tag names.
149
+ */
150
+ interface HTMLElementTagNameMap {
151
+ "type-ahead": TypeAhead;
152
+ }
153
+ }
@@ -33,8 +33,3 @@ export const DialogResult = Object.freeze({
33
33
  */
34
34
  Ignore: "Ignore"
35
35
  });
36
-
37
- /**
38
- * Specifies the return value of a dialog box.
39
- */
40
- export type DialogResult = typeof DialogResult[keyof typeof DialogResult];
package/lib/Duration.d.ts DELETED
@@ -1,26 +0,0 @@
1
- /**
2
- * Provides some common durations in milliseconds.
3
- */
4
- export declare const Duration: Readonly<{
5
- /**
6
- * One second.
7
- */
8
- Second: 1000;
9
- /**
10
- * One minute.
11
- */
12
- Minute: 60000;
13
- /**
14
- * One hour.
15
- */
16
- Hour: 3600000;
17
- /**
18
- * One day.
19
- */
20
- Day: 86400000;
21
- }>;
22
- /**
23
- * Provides some common durations in milliseconds.
24
- */
25
- export type Duration = typeof Duration[keyof typeof Duration];
26
- //# sourceMappingURL=Duration.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Duration.d.ts","sourceRoot":"","sources":["../src/Client/Duration.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,QAAQ;IAEpB;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;EAEF,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,MAAM,OAAO,QAAQ,CAAC,CAAC"}
package/lib/Duration.js DELETED
@@ -1,21 +0,0 @@
1
- /**
2
- * Provides some common durations in milliseconds.
3
- */
4
- export const Duration = Object.freeze({
5
- /**
6
- * One second.
7
- */
8
- Second: 1_000,
9
- /**
10
- * One minute.
11
- */
12
- Minute: 60_000,
13
- /**
14
- * One hour.
15
- */
16
- Hour: 3_600_000,
17
- /**
18
- * One day.
19
- */
20
- Day: 86_400_000
21
- });
@@ -1,30 +0,0 @@
1
- /**
2
- * Provides some common durations in milliseconds.
3
- */
4
- export const Duration = Object.freeze({
5
-
6
- /**
7
- * One second.
8
- */
9
- Second: 1_000,
10
-
11
- /**
12
- * One minute.
13
- */
14
- Minute: 60_000,
15
-
16
- /**
17
- * One hour.
18
- */
19
- Hour: 3_600_000,
20
-
21
- /**
22
- * One day.
23
- */
24
- Day: 86_400_000
25
- });
26
-
27
- /**
28
- * Provides some common durations in milliseconds.
29
- */
30
- export type Duration = typeof Duration[keyof typeof Duration];