@cedx/base 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ReadMe.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Belin.io Base
2
- ![.NET](https://badgen.net/badge/.net/%3E%3D9.0/green) ![Version](https://badgen.net/badge/project/v0.4.1/blue) ![Licence](https://badgen.net/badge/licence/MIT/blue)
2
+ ![.NET](https://badgen.net/badge/.net/%3E%3D9.0/green) ![Version](https://badgen.net/badge/project/v0.5.0/blue) ![Licence](https://badgen.net/badge/licence/MIT/blue)
3
3
 
4
4
  Base library by [Cédric Belin](https://belin.io), full stack developer,
5
5
  implemented in [C#](https://learn.microsoft.com/en-us/dotnet/csharp) and [TypeScript](https://www.typescriptlang.org).
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Specifies the order of a sorted property.
3
+ */
4
+ export declare const SortOrder: Readonly<{
5
+ /**
6
+ * The sort is ascending.
7
+ */
8
+ Ascending: "ASC";
9
+ /**
10
+ * The sort is descending.
11
+ */
12
+ Descending: "DESC";
13
+ }>;
14
+ /**
15
+ * Specifies the order of a sorted property.
16
+ */
17
+ export type SortOrder = typeof SortOrder[keyof typeof SortOrder];
18
+ /**
19
+ * Holds the name of a property and the order to sort by.
20
+ */
21
+ export type SortProperty = [string, SortOrder];
22
+ /**
23
+ * Represents information relevant to the sorting of data items.
24
+ */
25
+ export declare class Sort implements Iterable<SortProperty> {
26
+ #private;
27
+ /**
28
+ * Creates new sort.
29
+ * @param properties The list of properties to be sorted.
30
+ */
31
+ constructor(properties?: SortProperty[]);
32
+ /**
33
+ * The number of properties.
34
+ */
35
+ get length(): number;
36
+ /**
37
+ * Creates a new sort from the specified property and order.
38
+ * @param property The property name.
39
+ * @param order The sort order.
40
+ * @returns The sort corresponding to the property and order.
41
+ */
42
+ static of(property: string, order?: SortOrder): Sort;
43
+ /**
44
+ * Creates a new sort from the specified string.
45
+ * @param value A string representing a sort.
46
+ * @returns The sort corresponding to the specified string.
47
+ */
48
+ static parse(value: string): Sort;
49
+ /**
50
+ * Returns a new iterator that allows iterating the entries of this sort.
51
+ * @returns An iterator over the sorted properties.
52
+ */
53
+ [Symbol.iterator](): Generator<SortProperty, void, void>;
54
+ /**
55
+ * Appends the specified property to this sort.
56
+ * @param property The property name.
57
+ * @param order The sort order.
58
+ * @returns This instance.
59
+ */
60
+ append(property: string, order: SortOrder): this;
61
+ /**
62
+ * Gets the sorted property at the specified index.
63
+ * @param index The position in this sort.
64
+ * @returns The sorted property at the specified index, or `null` if it doesn't exist.
65
+ */
66
+ at(index: number): SortProperty | null;
67
+ /**
68
+ * Compares the specified objects, according to the current sort properties.
69
+ * @param x The first object to compare.
70
+ * @param y The second object to compare.
71
+ * @returns A value indicating the relationship between the two objects.
72
+ */
73
+ compare(x: object, y: object): number;
74
+ /**
75
+ * Removes the specified property from this sort.
76
+ * @param property The property name.
77
+ */
78
+ delete(property: string): void;
79
+ /**
80
+ * Gets the order associated with the specified property.
81
+ * @param property The property name.
82
+ * @returns The order associated with the specified property, or `null` if the property doesn't exist.
83
+ */
84
+ get(property: string): SortOrder | null;
85
+ /**
86
+ * Gets the icon corresponding to the specified property.
87
+ * @param property The property name.
88
+ * @returns The icon corresponding to the specified property.
89
+ */
90
+ getIcon(property: string): string;
91
+ /**
92
+ * Returns a value indicating whether the specified property exists in this sort.
93
+ * @param property The property name.
94
+ * @returns `true` if the specified property exists in this sort, otherwise `false`.
95
+ */
96
+ has(property: string): boolean;
97
+ /**
98
+ * Gets the index of the specified property in the underlying list.
99
+ * @param property The property name.
100
+ * @returns The index of the specified property, or `-1` if the property is not found.
101
+ */
102
+ indexOf(property: string): number;
103
+ /**
104
+ * Prepends the specified property to this sort.
105
+ * @param property The property name.
106
+ * @param order The sort order.
107
+ * @returns This instance.
108
+ */
109
+ prepend(property: string, order: SortOrder): this;
110
+ /**
111
+ * Returns a value indicating whether the current sort satisfies the specified conditions.
112
+ * @param conditions The conditions to satisfy.
113
+ * @returns `true` if the current sort satisfies the specified conditions, otherwise `false`.
114
+ */
115
+ satisfies(conditions?: Partial<{
116
+ min: number;
117
+ max: number;
118
+ properties: string[];
119
+ }>): boolean;
120
+ /**
121
+ * Sets the order of the specified property.
122
+ * @param property The property name.
123
+ * @param order The sort order.
124
+ * @returns This instance.
125
+ */
126
+ set(property: string, order: SortOrder): this;
127
+ /**
128
+ * Returns a JSON representation of this object.
129
+ * @returns The JSON representation of this object.
130
+ */
131
+ toJSON(): string;
132
+ /**
133
+ * Converts this sort to an SQL clause.
134
+ * @param escape A function used to escape the SQL identifiers.
135
+ * @returns The SQL clause corresponding to this object.
136
+ */
137
+ toSql(escape?: (identifier: string) => string): string;
138
+ /**
139
+ * Returns a string representation of this object.
140
+ * @returns The string representation of this object.
141
+ */
142
+ toString(): string;
143
+ }
144
+ //# sourceMappingURL=Sort.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sort.d.ts","sourceRoot":"","sources":["../../src/Client/Data/Sort.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,SAAS;IAErB;;OAEG;;IAGH;;OAEG;;EAEF,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEjE;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAE/C;;GAEG;AACH,qBAAa,IAAK,YAAW,QAAQ,CAAC,YAAY,CAAC;;IAOlD;;;OAGG;gBACS,UAAU,GAAE,YAAY,EAAO;IAI3C;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,GAAE,SAA+B,GAAG,IAAI;IAIzE;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAOjC;;;OAGG;IACF,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,CAAC;IAIzD;;;;;OAKG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI;IAMhD;;;;OAIG;IACH,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAC,IAAI;IAIpC;;;;;OAKG;IACH,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM;IAWrC;;;OAGG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAI9B;;;;OAIG;IACH,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAC,IAAI;IAKrC;;;;OAIG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAQjC;;;;OAIG;IACH,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAI9B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAKjC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI;IAMjD;;;;OAIG;IACH,SAAS,CAAC,UAAU,GAAE,OAAO,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,EAAE,CAAA;KAAC,CAAM,GAAG,OAAO;IAW9F;;;;;OAKG;IACH,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI;IAS7C;;;OAGG;IACH,MAAM,IAAI,MAAM;IAIhB;;;;OAIG;IACH,KAAK,CAAC,MAAM,GAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAiB,GAAG,MAAM;IAIhE;;;OAGG;IACH,QAAQ,IAAI,MAAM;CAGlB"}
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Specifies the order of a sorted property.
3
+ */
4
+ export const SortOrder = Object.freeze({
5
+ /**
6
+ * The sort is ascending.
7
+ */
8
+ Ascending: "ASC",
9
+ /**
10
+ * The sort is descending.
11
+ */
12
+ Descending: "DESC"
13
+ });
14
+ /**
15
+ * Represents information relevant to the sorting of data items.
16
+ */
17
+ export class Sort {
18
+ /**
19
+ * The list of sorted properties.
20
+ */
21
+ #properties;
22
+ /**
23
+ * Creates new sort.
24
+ * @param properties The list of properties to be sorted.
25
+ */
26
+ constructor(properties = []) {
27
+ this.#properties = properties;
28
+ }
29
+ /**
30
+ * The number of properties.
31
+ */
32
+ get length() {
33
+ return this.#properties.length;
34
+ }
35
+ /**
36
+ * Creates a new sort from the specified property and order.
37
+ * @param property The property name.
38
+ * @param order The sort order.
39
+ * @returns The sort corresponding to the property and order.
40
+ */
41
+ static of(property, order = SortOrder.Ascending) {
42
+ return new this([[property, order]]);
43
+ }
44
+ /**
45
+ * Creates a new sort from the specified string.
46
+ * @param value A string representing a sort.
47
+ * @returns The sort corresponding to the specified string.
48
+ */
49
+ static parse(value) {
50
+ return new this((value ? value.split(",") : []).map(item => {
51
+ const order = item.startsWith("-") ? SortOrder.Descending : SortOrder.Ascending;
52
+ return [order == SortOrder.Ascending ? item : item.slice(1), order];
53
+ }));
54
+ }
55
+ /**
56
+ * Returns a new iterator that allows iterating the entries of this sort.
57
+ * @returns An iterator over the sorted properties.
58
+ */
59
+ *[Symbol.iterator]() {
60
+ for (const entry of this.#properties)
61
+ yield entry;
62
+ }
63
+ /**
64
+ * Appends the specified property to this sort.
65
+ * @param property The property name.
66
+ * @param order The sort order.
67
+ * @returns This instance.
68
+ */
69
+ append(property, order) {
70
+ this.delete(property);
71
+ this.#properties.push([property, order]);
72
+ return this;
73
+ }
74
+ /**
75
+ * Gets the sorted property at the specified index.
76
+ * @param index The position in this sort.
77
+ * @returns The sorted property at the specified index, or `null` if it doesn't exist.
78
+ */
79
+ at(index) {
80
+ return this.#properties.at(index) ?? null;
81
+ }
82
+ /**
83
+ * Compares the specified objects, according to the current sort properties.
84
+ * @param x The first object to compare.
85
+ * @param y The second object to compare.
86
+ * @returns A value indicating the relationship between the two objects.
87
+ */
88
+ compare(x, y) {
89
+ for (const [property, order] of this.#properties) {
90
+ const xAttr = Reflect.get(x, property); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
91
+ const yAttr = Reflect.get(y, property); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
92
+ const value = xAttr > yAttr ? 1 : (xAttr < yAttr ? -1 : 0);
93
+ if (value)
94
+ return order == SortOrder.Ascending ? value : -value;
95
+ }
96
+ return 0;
97
+ }
98
+ /**
99
+ * Removes the specified property from this sort.
100
+ * @param property The property name.
101
+ */
102
+ delete(property) {
103
+ this.#properties = this.#properties.filter(([key]) => key != property);
104
+ }
105
+ /**
106
+ * Gets the order associated with the specified property.
107
+ * @param property The property name.
108
+ * @returns The order associated with the specified property, or `null` if the property doesn't exist.
109
+ */
110
+ get(property) {
111
+ for (const [key, order] of this.#properties)
112
+ if (key == property)
113
+ return order;
114
+ return null;
115
+ }
116
+ /**
117
+ * Gets the icon corresponding to the specified property.
118
+ * @param property The property name.
119
+ * @returns The icon corresponding to the specified property.
120
+ */
121
+ getIcon(property) {
122
+ switch (this.get(property)) {
123
+ case SortOrder.Ascending: return "arrow_upward";
124
+ case SortOrder.Descending: return "arrow_downward";
125
+ default: return "swap_vert";
126
+ }
127
+ }
128
+ /**
129
+ * Returns a value indicating whether the specified property exists in this sort.
130
+ * @param property The property name.
131
+ * @returns `true` if the specified property exists in this sort, otherwise `false`.
132
+ */
133
+ has(property) {
134
+ return this.#properties.some(([key]) => key == property);
135
+ }
136
+ /**
137
+ * Gets the index of the specified property in the underlying list.
138
+ * @param property The property name.
139
+ * @returns The index of the specified property, or `-1` if the property is not found.
140
+ */
141
+ indexOf(property) {
142
+ for (const [index, [key]] of this.#properties.entries())
143
+ if (key == property)
144
+ return index;
145
+ return -1;
146
+ }
147
+ /**
148
+ * Prepends the specified property to this sort.
149
+ * @param property The property name.
150
+ * @param order The sort order.
151
+ * @returns This instance.
152
+ */
153
+ prepend(property, order) {
154
+ this.delete(property);
155
+ this.#properties.unshift([property, order]);
156
+ return this;
157
+ }
158
+ /**
159
+ * Returns a value indicating whether the current sort satisfies the specified conditions.
160
+ * @param conditions The conditions to satisfy.
161
+ * @returns `true` if the current sort satisfies the specified conditions, otherwise `false`.
162
+ */
163
+ satisfies(conditions = {}) {
164
+ const min = conditions.min ?? -1;
165
+ if (min >= 0)
166
+ return this.length >= min;
167
+ const max = conditions.max ?? -1;
168
+ if (max >= 0)
169
+ return this.length <= max;
170
+ const properties = conditions.properties ?? [];
171
+ return properties.length ? this.#properties.every(([key]) => properties.includes(key)) : true;
172
+ }
173
+ /**
174
+ * Sets the order of the specified property.
175
+ * @param property The property name.
176
+ * @param order The sort order.
177
+ * @returns This instance.
178
+ */
179
+ set(property, order) {
180
+ for (const [index, [key]] of this.#properties.entries())
181
+ if (key == property) {
182
+ this.#properties[index] = [key, order];
183
+ return this;
184
+ }
185
+ return this.append(property, order);
186
+ }
187
+ /**
188
+ * Returns a JSON representation of this object.
189
+ * @returns The JSON representation of this object.
190
+ */
191
+ toJSON() {
192
+ return this.toString();
193
+ }
194
+ /**
195
+ * Converts this sort to an SQL clause.
196
+ * @param escape A function used to escape the SQL identifiers.
197
+ * @returns The SQL clause corresponding to this object.
198
+ */
199
+ toSql(escape = id => id) {
200
+ return this.#properties.map(([property, order]) => `${escape(property)} ${order}`).join(", ");
201
+ }
202
+ /**
203
+ * Returns a string representation of this object.
204
+ * @returns The string representation of this object.
205
+ */
206
+ toString() {
207
+ return this.#properties.map(([property, order]) => `${order == SortOrder.Descending ? "-" : ""}${property}`).join();
208
+ }
209
+ }
@@ -27,7 +27,7 @@ export class Container {
27
27
  get(id) {
28
28
  if (!this.#services.has(id))
29
29
  if (this.#factories.has(id))
30
- this.set(id, this.#factories.get(id)());
30
+ this.set(id, this.#factories.get(id).call(this));
31
31
  else if (typeof id == "function")
32
32
  this.set(id, Reflect.construct(id, []));
33
33
  else
@@ -28,5 +28,5 @@ export type Context = typeof Context[keyof typeof Context];
28
28
  * @param context The context.
29
29
  * @returns The icon corresponding to the specified context.
30
30
  */
31
- export declare function contextIcon(context: Context): string;
31
+ export declare function getIcon(context: Context): string;
32
32
  //# sourceMappingURL=Context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Context.d.ts","sourceRoot":"","sources":["../../src/Client/Html/Context.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,OAAO;IAEnB;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;EAEF,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,MAAM,OAAO,OAAO,CAAC,CAAC;AAE3D;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAOpD"}
1
+ {"version":3,"file":"Context.d.ts","sourceRoot":"","sources":["../../src/Client/Html/Context.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,OAAO;IAEnB;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;IAGH;;OAEG;;EAEF,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,MAAM,OAAO,OAAO,CAAC,CAAC;AAE3D;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAOhD"}
@@ -24,7 +24,7 @@ export const Context = Object.freeze({
24
24
  * @param context The context.
25
25
  * @returns The icon corresponding to the specified context.
26
26
  */
27
- export function contextIcon(context) {
27
+ export function getIcon(context) {
28
28
  switch (context) {
29
29
  case Context.Danger: return "error";
30
30
  case Context.Success: return "check_circle";
@@ -0,0 +1,25 @@
1
+ /**
2
+ * An action bar located under the navigation bar.
3
+ */
4
+ export declare class ActionBar extends HTMLElement {
5
+ /**
6
+ * Method invoked when this component is connected.
7
+ */
8
+ connectedCallback(): void;
9
+ /**
10
+ * Method invoked when this component is disconnected.
11
+ */
12
+ disconnectedCallback(): void;
13
+ }
14
+ /**
15
+ * Declaration merging.
16
+ */
17
+ declare global {
18
+ /**
19
+ * The map of HTML tag names.
20
+ */
21
+ interface HTMLElementTagNameMap {
22
+ "action-bar": ActionBar;
23
+ }
24
+ }
25
+ //# sourceMappingURL=ActionBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActionBar.d.ts","sourceRoot":"","sources":["../../src/Client/UI/ActionBar.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,SAAU,SAAQ,WAAW;IASzC;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAMzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;CAG5B;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,YAAY,EAAE,SAAS,CAAC;KACxB;CACD"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * An action bar located under the navigation bar.
3
+ */
4
+ export class ActionBar extends HTMLElement {
5
+ /**
6
+ * Registers the component.
7
+ */
8
+ static {
9
+ customElements.define("action-bar", this);
10
+ }
11
+ /**
12
+ * Method invoked when this component is connected.
13
+ */
14
+ connectedCallback() {
15
+ const navbarHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--navbar-height"));
16
+ const mainOffset = this.offsetHeight + (Number.isNaN(navbarHeight) ? 0 : navbarHeight);
17
+ document.documentElement.style.setProperty("--main-offset", `${mainOffset}px`);
18
+ }
19
+ /**
20
+ * Method invoked when this component is disconnected.
21
+ */
22
+ disconnectedCallback() {
23
+ document.documentElement.style.removeProperty("--main-offset");
24
+ }
25
+ }
@@ -12,7 +12,7 @@ export class MenuActivator extends HTMLElement {
12
12
  * Method invoked when this component is connected.
13
13
  */
14
14
  connectedCallback() {
15
- for (const anchor of this.firstElementChild.getElementsByTagName("a"))
15
+ for (const anchor of this.querySelectorAll("a"))
16
16
  if (anchor.href != location.href)
17
17
  anchor.classList.remove("active");
18
18
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeDropdown.d.ts","sourceRoot":"","sources":["../../src/Client/UI/ThemeDropdown.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,qBAAa,aAAc,SAAQ,WAAW;;IAsB7C;;OAEG;;IAeH;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAKzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAI5B;;OAEG;IACH,WAAW,IAAI,IAAI;CA2BnB;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/ThemeDropdown.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,qBAAa,aAAc,SAAQ,WAAW;;IAiB7C;;OAEG;;IAeH;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAKzB;;OAEG;IACH,oBAAoB,IAAI,IAAI;IAI5B;;OAEG;IACH,WAAW,IAAI,IAAI;CAuBnB;AAED;;GAEG;AACH,OAAO,CAAC,MAAM,CAAC;IAEd;;OAEG;IACH,UAAU,qBAAqB;QAC9B,gBAAgB,EAAE,aAAa,CAAC;KAChC;CACD"}
@@ -7,10 +7,6 @@ export class ThemeDropdown extends HTMLElement {
7
7
  * The media query used to check the application theme.
8
8
  */
9
9
  #mediaQuery = matchMedia("(prefers-color-scheme: dark)");
10
- /**
11
- * The root element.
12
- */
13
- #root = this.firstElementChild;
14
10
  /**
15
11
  * The key of the storage entry providing the saved theme mode.
16
12
  */
@@ -26,7 +22,7 @@ export class ThemeDropdown extends HTMLElement {
26
22
  super();
27
23
  const theme = localStorage.getItem(this.#storageKey);
28
24
  this.#theme = Object.values(AppTheme).includes(theme) ? theme : AppTheme.System;
29
- for (const button of this.#root.querySelectorAll("button"))
25
+ for (const button of this.querySelectorAll("button"))
30
26
  button.addEventListener("click", this.#setTheme.bind(this));
31
27
  }
32
28
  /**
@@ -60,11 +56,8 @@ export class ThemeDropdown extends HTMLElement {
60
56
  #applyTheme() {
61
57
  const theme = this.#theme == AppTheme.System ? (this.#mediaQuery.matches ? AppTheme.Dark : AppTheme.Light) : this.#theme;
62
58
  document.documentElement.dataset.bsTheme = theme.toLowerCase();
63
- this.#root.querySelector(".dropdown-toggle > .icon").textContent = getIcon(this.#theme);
64
- const checkIcon = this.#root.querySelector(".dropdown-item > .icon");
65
- checkIcon.remove();
66
- const activeButton = this.#root.querySelector(`button[data-theme="${this.#theme}"]`);
67
- activeButton.appendChild(checkIcon);
59
+ this.querySelector(".dropdown-toggle > .icon").textContent = getIcon(this.#theme);
60
+ this.querySelector(`button[data-theme="${this.#theme}"]`).appendChild(this.querySelector(".dropdown-item > .icon"));
68
61
  }
69
62
  /**
70
63
  * Changes the current theme.
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.4.1",
10
+ "version": "0.5.0",
11
11
  "devDependencies": {
12
12
  "@playwright/browser-chromium": "^1.54.2",
13
13
  "@types/bootstrap": "^5.2.10",
@@ -50,8 +50,7 @@
50
50
  "library"
51
51
  ],
52
52
  "peerDependencies": {
53
- "bootstrap": ">=5.3.0",
54
- "tslib": ">=2.8.0"
53
+ "bootstrap": ">=5.3.0"
55
54
  },
56
55
  "publishConfig": {
57
56
  "access": "public"
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Represents information relevant to the pagination of data items.
3
+ */
4
+ export class Pagination {
5
+
6
+ /**
7
+ * The current page number.
8
+ */
9
+ currentPage: number;
10
+
11
+ /**
12
+ * The number of items per page.
13
+ */
14
+ itemsPerPage: number;
15
+
16
+ /**
17
+ * The total number of items.
18
+ */
19
+ totalItemCount: number;
20
+
21
+ /**
22
+ * Creates a new pagination.
23
+ * @param options An object providing values to initialize this instance.
24
+ */
25
+ constructor(options: PaginationOptions = {}) {
26
+ this.currentPage = Math.max(1, options.currentPage ?? 1);
27
+ this.itemsPerPage = Math.max(1, Math.min(1000, options.itemsPerPage ?? 25));
28
+ this.totalItemCount = Math.max(0, options.totalItemCount ?? 0);
29
+ }
30
+
31
+ /**
32
+ * The last page number.
33
+ */
34
+ get lastPage(): number {
35
+ return Math.ceil(this.totalItemCount / this.itemsPerPage);
36
+ }
37
+
38
+ /**
39
+ * The data limit.
40
+ */
41
+ get limit(): number {
42
+ return this.itemsPerPage;
43
+ }
44
+
45
+ /**
46
+ * The data offset.
47
+ */
48
+ get offset(): number {
49
+ return (this.currentPage - 1) * this.itemsPerPage;
50
+ }
51
+
52
+ /**
53
+ * The search parameters corresponding to this object.
54
+ */
55
+ get searchParams(): URLSearchParams {
56
+ return new URLSearchParams({page: this.currentPage.toString(), perPage: this.itemsPerPage.toString()});
57
+ }
58
+
59
+ /**
60
+ * Creates a new pagination from the HTTP headers of the specified response.
61
+ * @param response A server response.
62
+ * @returns The pagination corresponding to the HTTP headers of the specified response.
63
+ */
64
+ static fromResponse(response: Response): Pagination {
65
+ return new this({
66
+ currentPage: Number(response.headers.get("X-Pagination-Current-Page") ?? "1"),
67
+ itemsPerPage: Number(response.headers.get("X-Pagination-Per-Page") ?? "25"),
68
+ totalItemCount: Number(response.headers.get("X-Pagination-Total-Count") ?? "0")
69
+ });
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Defines the options of a {@link Pagination} instance.
75
+ */
76
+ export type PaginationOptions = Partial<Pick<Pagination, "currentPage"|"itemsPerPage"|"totalItemCount">>;
77
+
78
+ /**
79
+ * A list with information relevant to the pagination of its items.
80
+ */
81
+ export class PaginatedList<T> implements Iterable<T, void, void> {
82
+
83
+ /**
84
+ * The list items.
85
+ */
86
+ items: T[];
87
+
88
+ /**
89
+ * The information relevant to the pagination of the list items.
90
+ */
91
+ pagination: Pagination;
92
+
93
+ /**
94
+ * Creates a new paginated list.
95
+ * @param options An object providing values to initialize this instance.
96
+ */
97
+ constructor(options: PaginatedListOptions<T> = {}) {
98
+ this.items = options.items ?? [];
99
+ this.pagination = options.pagination ?? new Pagination;
100
+ }
101
+
102
+ /**
103
+ * The number of items in this list.
104
+ */
105
+ get length(): number {
106
+ return this.items.length;
107
+ }
108
+
109
+ /**
110
+ * Creates an empty paginated list.
111
+ * @param itemsPerPage The number of items per page.
112
+ * @returns An empty paginated list with the specified number of items per page.
113
+ */
114
+ static empty<T>(itemsPerPage: number): PaginatedList<T> {
115
+ return new this({pagination: new Pagination({itemsPerPage})});
116
+ }
117
+
118
+ /**
119
+ * Returns a new iterator that allows iterating the items of this list.
120
+ * @returns An iterator over the items of this list.
121
+ */
122
+ *[Symbol.iterator](): Generator<T, void, void> {
123
+ for (const item of this.items) yield item;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Defines the options of a {@link PaginatedList} instance.
129
+ */
130
+ export type PaginatedListOptions<T> = Partial<Pick<PaginatedList<T>, "items"|"pagination">>;
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Specifies the order of a sorted property.
3
+ */
4
+ export const SortOrder = Object.freeze({
5
+
6
+ /**
7
+ * The sort is ascending.
8
+ */
9
+ Ascending: "ASC",
10
+
11
+ /**
12
+ * The sort is descending.
13
+ */
14
+ Descending: "DESC"
15
+ });
16
+
17
+ /**
18
+ * Specifies the order of a sorted property.
19
+ */
20
+ export type SortOrder = typeof SortOrder[keyof typeof SortOrder];
21
+
22
+ /**
23
+ * Holds the name of a property and the order to sort by.
24
+ */
25
+ export type SortProperty = [string, SortOrder];
26
+
27
+ /**
28
+ * Represents information relevant to the sorting of data items.
29
+ */
30
+ export class Sort implements Iterable<SortProperty> {
31
+
32
+ /**
33
+ * The list of sorted properties.
34
+ */
35
+ #properties: SortProperty[];
36
+
37
+ /**
38
+ * Creates new sort.
39
+ * @param properties The list of properties to be sorted.
40
+ */
41
+ constructor(properties: SortProperty[] = []) {
42
+ this.#properties = properties;
43
+ }
44
+
45
+ /**
46
+ * The number of properties.
47
+ */
48
+ get length(): number {
49
+ return this.#properties.length;
50
+ }
51
+
52
+ /**
53
+ * Creates a new sort from the specified property and order.
54
+ * @param property The property name.
55
+ * @param order The sort order.
56
+ * @returns The sort corresponding to the property and order.
57
+ */
58
+ static of(property: string, order: SortOrder = SortOrder.Ascending): Sort {
59
+ return new this([[property, order]]);
60
+ }
61
+
62
+ /**
63
+ * Creates a new sort from the specified string.
64
+ * @param value A string representing a sort.
65
+ * @returns The sort corresponding to the specified string.
66
+ */
67
+ static parse(value: string): Sort {
68
+ return new this((value ? value.split(",") : []).map(item => {
69
+ const order = item.startsWith("-") ? SortOrder.Descending : SortOrder.Ascending;
70
+ return [order == SortOrder.Ascending ? item : item.slice(1), order];
71
+ }));
72
+ }
73
+
74
+ /**
75
+ * Returns a new iterator that allows iterating the entries of this sort.
76
+ * @returns An iterator over the sorted properties.
77
+ */
78
+ *[Symbol.iterator](): Generator<SortProperty, void, void> {
79
+ for (const entry of this.#properties) yield entry;
80
+ }
81
+
82
+ /**
83
+ * Appends the specified property to this sort.
84
+ * @param property The property name.
85
+ * @param order The sort order.
86
+ * @returns This instance.
87
+ */
88
+ append(property: string, order: SortOrder): this {
89
+ this.delete(property);
90
+ this.#properties.push([property, order]);
91
+ return this;
92
+ }
93
+
94
+ /**
95
+ * Gets the sorted property at the specified index.
96
+ * @param index The position in this sort.
97
+ * @returns The sorted property at the specified index, or `null` if it doesn't exist.
98
+ */
99
+ at(index: number): SortProperty|null {
100
+ return this.#properties.at(index) ?? null;
101
+ }
102
+
103
+ /**
104
+ * Compares the specified objects, according to the current sort properties.
105
+ * @param x The first object to compare.
106
+ * @param y The second object to compare.
107
+ * @returns A value indicating the relationship between the two objects.
108
+ */
109
+ compare(x: object, y: object): number {
110
+ for (const [property, order] of this.#properties) {
111
+ const xAttr = Reflect.get(x, property); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
112
+ const yAttr = Reflect.get(y, property); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
113
+ const value = xAttr > yAttr ? 1 : (xAttr < yAttr ? -1 : 0);
114
+ if (value) return order == SortOrder.Ascending ? value : -value;
115
+ }
116
+
117
+ return 0;
118
+ }
119
+
120
+ /**
121
+ * Removes the specified property from this sort.
122
+ * @param property The property name.
123
+ */
124
+ delete(property: string): void {
125
+ this.#properties = this.#properties.filter(([key]) => key != property);
126
+ }
127
+
128
+ /**
129
+ * Gets the order associated with the specified property.
130
+ * @param property The property name.
131
+ * @returns The order associated with the specified property, or `null` if the property doesn't exist.
132
+ */
133
+ get(property: string): SortOrder|null {
134
+ for (const [key, order] of this.#properties) if (key == property) return order;
135
+ return null;
136
+ }
137
+
138
+ /**
139
+ * Gets the icon corresponding to the specified property.
140
+ * @param property The property name.
141
+ * @returns The icon corresponding to the specified property.
142
+ */
143
+ getIcon(property: string): string {
144
+ switch (this.get(property)) {
145
+ case SortOrder.Ascending: return "arrow_upward";
146
+ case SortOrder.Descending: return "arrow_downward";
147
+ default: return "swap_vert";
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Returns a value indicating whether the specified property exists in this sort.
153
+ * @param property The property name.
154
+ * @returns `true` if the specified property exists in this sort, otherwise `false`.
155
+ */
156
+ has(property: string): boolean {
157
+ return this.#properties.some(([key]) => key == property);
158
+ }
159
+
160
+ /**
161
+ * Gets the index of the specified property in the underlying list.
162
+ * @param property The property name.
163
+ * @returns The index of the specified property, or `-1` if the property is not found.
164
+ */
165
+ indexOf(property: string): number {
166
+ for (const [index, [key]] of this.#properties.entries()) if (key == property) return index;
167
+ return -1;
168
+ }
169
+
170
+ /**
171
+ * Prepends the specified property to this sort.
172
+ * @param property The property name.
173
+ * @param order The sort order.
174
+ * @returns This instance.
175
+ */
176
+ prepend(property: string, order: SortOrder): this {
177
+ this.delete(property);
178
+ this.#properties.unshift([property, order]);
179
+ return this;
180
+ }
181
+
182
+ /**
183
+ * Returns a value indicating whether the current sort satisfies the specified conditions.
184
+ * @param conditions The conditions to satisfy.
185
+ * @returns `true` if the current sort satisfies the specified conditions, otherwise `false`.
186
+ */
187
+ satisfies(conditions: Partial<{min: number, max: number, properties: string[]}> = {}): boolean {
188
+ const min = conditions.min ?? -1;
189
+ if (min >= 0) return this.length >= min;
190
+
191
+ const max = conditions.max ?? -1;
192
+ if (max >= 0) return this.length <= max;
193
+
194
+ const properties = conditions.properties ?? [];
195
+ return properties.length ? this.#properties.every(([key]) => properties.includes(key)) : true;
196
+ }
197
+
198
+ /**
199
+ * Sets the order of the specified property.
200
+ * @param property The property name.
201
+ * @param order The sort order.
202
+ * @returns This instance.
203
+ */
204
+ set(property: string, order: SortOrder): this {
205
+ for (const [index, [key]] of this.#properties.entries()) if (key == property) {
206
+ this.#properties[index] = [key, order];
207
+ return this;
208
+ }
209
+
210
+ return this.append(property, order);
211
+ }
212
+
213
+ /**
214
+ * Returns a JSON representation of this object.
215
+ * @returns The JSON representation of this object.
216
+ */
217
+ toJSON(): string {
218
+ return this.toString();
219
+ }
220
+
221
+ /**
222
+ * Converts this sort to an SQL clause.
223
+ * @param escape A function used to escape the SQL identifiers.
224
+ * @returns The SQL clause corresponding to this object.
225
+ */
226
+ toSql(escape: (identifier: string) => string = id => id): string {
227
+ return this.#properties.map(([property, order]) => `${escape(property)} ${order}`).join(", ");
228
+ }
229
+
230
+ /**
231
+ * Returns a string representation of this object.
232
+ * @returns The string representation of this object.
233
+ */
234
+ toString(): string {
235
+ return this.#properties.map(([property, order]) => `${order == SortOrder.Descending ? "-" : ""}${property}`).join();
236
+ }
237
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "include": ["*.ts"],
4
+ "compilerOptions": {
5
+ "composite": true,
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "noEmit": false,
9
+ "outDir": "../../../lib/Data",
10
+ "rootDir": ".",
11
+ "tsBuildInfoFile": "../../../var/Data.tsbuildinfo"
12
+ }
13
+ }
@@ -30,7 +30,7 @@ export class Container {
30
30
  */
31
31
  get<T>(id: ContainerToken): T { // eslint-disable-line @typescript-eslint/no-unnecessary-type-parameters
32
32
  if (!this.#services.has(id))
33
- if (this.#factories.has(id)) this.set(id, this.#factories.get(id)!());
33
+ if (this.#factories.has(id)) this.set(id, this.#factories.get(id)!.call(this));
34
34
  else if (typeof id == "function") this.set(id, Reflect.construct(id, []));
35
35
  else throw Error("There is no factory registered with the specified identifier.");
36
36
 
@@ -34,7 +34,7 @@ export type Context = typeof Context[keyof typeof Context];
34
34
  * @param context The context.
35
35
  * @returns The icon corresponding to the specified context.
36
36
  */
37
- export function contextIcon(context: Context): string {
37
+ export function getIcon(context: Context): string {
38
38
  switch (context) {
39
39
  case Context.Danger: return "error";
40
40
  case Context.Success: return "check_circle";
@@ -0,0 +1,41 @@
1
+ /**
2
+ * An action bar located under the navigation bar.
3
+ */
4
+ export class ActionBar extends HTMLElement {
5
+
6
+ /**
7
+ * Registers the component.
8
+ */
9
+ static {
10
+ customElements.define("action-bar", this);
11
+ }
12
+
13
+ /**
14
+ * Method invoked when this component is connected.
15
+ */
16
+ connectedCallback(): void {
17
+ const navbarHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue("--navbar-height"));
18
+ const mainOffset = this.offsetHeight + (Number.isNaN(navbarHeight) ? 0 : navbarHeight);
19
+ document.documentElement.style.setProperty("--main-offset", `${mainOffset}px`);
20
+ }
21
+
22
+ /**
23
+ * Method invoked when this component is disconnected.
24
+ */
25
+ disconnectedCallback(): void {
26
+ document.documentElement.style.removeProperty("--main-offset");
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Declaration merging.
32
+ */
33
+ declare global {
34
+
35
+ /**
36
+ * The map of HTML tag names.
37
+ */
38
+ interface HTMLElementTagNameMap {
39
+ "action-bar": ActionBar;
40
+ }
41
+ }
@@ -14,7 +14,7 @@ export class MenuActivator extends HTMLElement {
14
14
  * Method invoked when this component is connected.
15
15
  */
16
16
  connectedCallback(): void {
17
- for (const anchor of this.firstElementChild!.getElementsByTagName("a"))
17
+ for (const anchor of this.querySelectorAll("a"))
18
18
  if (anchor.href != location.href) anchor.classList.remove("active");
19
19
  else {
20
20
  anchor.classList.add("active");
@@ -10,11 +10,6 @@ export class ThemeDropdown extends HTMLElement {
10
10
  */
11
11
  readonly #mediaQuery = matchMedia("(prefers-color-scheme: dark)");
12
12
 
13
- /**
14
- * The root element.
15
- */
16
- readonly #root = this.firstElementChild!;
17
-
18
13
  /**
19
14
  * The key of the storage entry providing the saved theme mode.
20
15
  */
@@ -32,7 +27,7 @@ export class ThemeDropdown extends HTMLElement {
32
27
  super();
33
28
  const theme = localStorage.getItem(this.#storageKey) as AppTheme;
34
29
  this.#theme = Object.values(AppTheme).includes(theme) ? theme : AppTheme.System;
35
- for (const button of this.#root.querySelectorAll("button")) button.addEventListener("click", this.#setTheme.bind(this));
30
+ for (const button of this.querySelectorAll("button")) button.addEventListener("click", this.#setTheme.bind(this));
36
31
  }
37
32
 
38
33
  /**
@@ -70,12 +65,8 @@ export class ThemeDropdown extends HTMLElement {
70
65
  #applyTheme(): void {
71
66
  const theme = this.#theme == AppTheme.System ? (this.#mediaQuery.matches ? AppTheme.Dark : AppTheme.Light) : this.#theme;
72
67
  document.documentElement.dataset.bsTheme = theme.toLowerCase();
73
- this.#root.querySelector(".dropdown-toggle > .icon")!.textContent = getIcon(this.#theme);
74
-
75
- const checkIcon = this.#root.querySelector(".dropdown-item > .icon")!;
76
- checkIcon.remove();
77
- const activeButton = this.#root.querySelector(`button[data-theme="${this.#theme}"]`)!
78
- activeButton.appendChild(checkIcon);
68
+ this.querySelector(".dropdown-toggle > .icon")!.textContent = getIcon(this.#theme);
69
+ this.querySelector(`button[data-theme="${this.#theme}"]`)!.appendChild(this.querySelector(".dropdown-item > .icon")!);
79
70
  }
80
71
 
81
72
  /**
@@ -5,12 +5,10 @@
5
5
  "composite": true,
6
6
  "declaration": true,
7
7
  "declarationMap": true,
8
- "importHelpers": true,
9
8
  "noEmit": false,
10
9
  "outDir": "../../../lib/UI",
11
10
  "rootDir": ".",
12
- "tsBuildInfoFile": "../../../var/UI.tsbuildinfo",
13
- "useDefineForClassFields": false
11
+ "tsBuildInfoFile": "../../../var/UI.tsbuildinfo"
14
12
  },
15
13
  "references": [
16
14
  {"path": "../Abstractions/tsconfig.json"},
@@ -3,6 +3,7 @@
3
3
  "references": [
4
4
  {"path": "Abstractions/tsconfig.json"},
5
5
  {"path": "Base/tsconfig.json"},
6
+ {"path": "Data/tsconfig.json"},
6
7
  {"path": "DependencyInjection/tsconfig.json"},
7
8
  {"path": "Html/tsconfig.json"},
8
9
  {"path": "Http/tsconfig.json"},