@arsedizioni/ars-utils 21.2.207 → 21.2.209
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/fesm2022/arsedizioni-ars-utils-clipper.ui.mjs +1 -1
- package/fesm2022/arsedizioni-ars-utils-clipper.ui.mjs.map +1 -1
- package/fesm2022/arsedizioni-ars-utils-core.mjs +872 -513
- package/fesm2022/arsedizioni-ars-utils-core.mjs.map +1 -1
- package/package.json +1 -1
- package/types/arsedizioni-ars-utils-clipper.ui.d.ts +1 -1
- package/types/arsedizioni-ars-utils-core.d.ts +724 -238
- package/types/arsedizioni-ars-utils-ui.application.d.ts +1 -1
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, ElementRef, Directive, input, HostListener, output, forwardRef, Pipe, EventEmitter,
|
|
2
|
+
import { inject, ElementRef, afterNextRender, Directive, input, DestroyRef, HostListener, output, forwardRef, Pipe, EventEmitter, signal, computed, Injectable, RendererFactory2, NgModule } from '@angular/core';
|
|
3
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
+
import { Subject, BehaviorSubject } from 'rxjs';
|
|
5
|
+
import { debounceTime } from 'rxjs/operators';
|
|
3
6
|
import { HttpParams } from '@angular/common/http';
|
|
4
7
|
import { parseISO, parse, format, endOfDay, getYear, getMonth, getDate, getDay, getDaysInMonth, addYears, addMonths, addDays, formatISO, isDate, isValid } from 'date-fns';
|
|
5
8
|
import { it } from 'date-fns/locale';
|
|
6
9
|
import { TZDate } from '@date-fns/tz';
|
|
7
|
-
import { debounceTime } from 'rxjs/operators';
|
|
8
|
-
import { Subject, BehaviorSubject } from 'rxjs';
|
|
9
10
|
import { NG_VALIDATORS } from '@angular/forms';
|
|
10
11
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
11
12
|
import { SelectionModel } from '@angular/cdk/collections';
|
|
12
13
|
import { DateAdapter, MAT_DATE_LOCALE, MAT_DATE_FORMATS } from '@angular/material/core';
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Directive that moves browser focus to the host element after the first render cycle.
|
|
17
|
+
* Apply `autoFocus` to any focusable element to set focus automatically on initialisation.
|
|
18
|
+
*/
|
|
14
19
|
class AutoFocusDirective {
|
|
15
20
|
constructor() {
|
|
16
21
|
this.elementRef = inject(ElementRef);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this.elementRef.nativeElement.focus();
|
|
21
|
-
}, 500);
|
|
22
|
+
afterNextRender(() => {
|
|
23
|
+
this.elementRef.nativeElement?.focus();
|
|
24
|
+
});
|
|
22
25
|
}
|
|
23
26
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AutoFocusDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
24
27
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: AutoFocusDirective, isStandalone: true, selector: "[autoFocus]", ngImport: i0 }); }
|
|
@@ -26,10 +29,10 @@ class AutoFocusDirective {
|
|
|
26
29
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AutoFocusDirective, decorators: [{
|
|
27
30
|
type: Directive,
|
|
28
31
|
args: [{
|
|
29
|
-
selector:
|
|
32
|
+
selector: '[autoFocus]',
|
|
30
33
|
standalone: true
|
|
31
34
|
}]
|
|
32
|
-
}] });
|
|
35
|
+
}], ctorParameters: () => [] });
|
|
33
36
|
|
|
34
37
|
class FileInfo {
|
|
35
38
|
isValid() {
|
|
@@ -178,27 +181,25 @@ class SystemUtils {
|
|
|
178
181
|
return nodes;
|
|
179
182
|
}
|
|
180
183
|
/**
|
|
181
|
-
*
|
|
182
|
-
* @param key
|
|
183
|
-
* @param order
|
|
184
|
-
* @returns
|
|
184
|
+
* Comparator factory for sorting arrays of objects by a given property key.
|
|
185
|
+
* @param key - Name of the property to sort by.
|
|
186
|
+
* @param order - Sort direction: `'asc'` (default) or `'desc'`.
|
|
187
|
+
* @returns A comparator function that returns `0`, `1`, or `-1`.
|
|
185
188
|
*/
|
|
186
|
-
static arraySortCompare(key, order =
|
|
187
|
-
|
|
188
|
-
if (!
|
|
189
|
-
// property doesn't exist on either object
|
|
189
|
+
static arraySortCompare(key, order = 'asc') {
|
|
190
|
+
return function innerSort(a, b) {
|
|
191
|
+
if (!Object.prototype.hasOwnProperty.call(a, key) || !Object.prototype.hasOwnProperty.call(b, key)) {
|
|
190
192
|
return 0;
|
|
191
193
|
}
|
|
192
|
-
const varA = typeof a[key] ===
|
|
193
|
-
const varB = typeof b[key] ===
|
|
194
|
+
const varA = typeof a[key] === 'string' ? a[key].toUpperCase() : a[key];
|
|
195
|
+
const varB = typeof b[key] === 'string' ? b[key].toUpperCase() : b[key];
|
|
194
196
|
let comparison = 0;
|
|
195
197
|
if (varA > varB)
|
|
196
198
|
comparison = 1;
|
|
197
199
|
else if (varA < varB)
|
|
198
200
|
comparison = -1;
|
|
199
|
-
return order ===
|
|
201
|
+
return order === 'desc' ? comparison * -1 : comparison;
|
|
200
202
|
};
|
|
201
|
-
return result;
|
|
202
203
|
}
|
|
203
204
|
/**
|
|
204
205
|
* Format weight
|
|
@@ -311,21 +312,22 @@ class SystemUtils {
|
|
|
311
312
|
return items[0];
|
|
312
313
|
}
|
|
313
314
|
/**
|
|
314
|
-
*
|
|
315
|
-
* @param s
|
|
316
|
-
* @returns
|
|
315
|
+
* Wraps bare URLs in the given string with `<a>` anchor tags.
|
|
316
|
+
* @param s - The plain-text or HTML string to process.
|
|
317
|
+
* @returns The string with URLs replaced by clickable links, or `''` when `s` is falsy.
|
|
317
318
|
*/
|
|
318
319
|
static replaceAsHtml(s) {
|
|
319
320
|
if (!s)
|
|
320
321
|
return '';
|
|
321
322
|
const regex = /https?:\/\/.*[^\s]/ig;
|
|
322
323
|
const m = regex.exec(s);
|
|
323
|
-
if (m
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
324
|
+
if (!m || m.length === 0)
|
|
325
|
+
return s;
|
|
326
|
+
let result = s;
|
|
327
|
+
for (const v of m) {
|
|
328
|
+
result = result.replace(v, `<a href="${v}" target="_blank">${v}</a>`);
|
|
327
329
|
}
|
|
328
|
-
return
|
|
330
|
+
return result;
|
|
329
331
|
}
|
|
330
332
|
/**
|
|
331
333
|
* Convert markdown to html
|
|
@@ -595,16 +597,18 @@ class SystemUtils {
|
|
|
595
597
|
return JSON.parse(JSON.stringify(obj));
|
|
596
598
|
}
|
|
597
599
|
/**
|
|
598
|
-
*
|
|
599
|
-
* @param obj
|
|
600
|
-
* @
|
|
600
|
+
* Creates a deep clone of an object by recursively copying all own properties.
|
|
601
|
+
* @param obj - The source object to clone.
|
|
602
|
+
* @param dest - Optional pre-allocated destination object; a new instance is created when omitted.
|
|
603
|
+
* @returns A deep copy of `obj`.
|
|
601
604
|
*/
|
|
605
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
602
606
|
static deepClone(obj, dest) {
|
|
603
|
-
|
|
604
|
-
const
|
|
605
|
-
for (const attribute of
|
|
607
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
608
|
+
const cloneObj = dest ?? obj.constructor();
|
|
609
|
+
for (const attribute of Object.keys(obj)) {
|
|
606
610
|
const property = obj[attribute];
|
|
607
|
-
if (typeof property ===
|
|
611
|
+
if (typeof property === 'object' && property !== null) {
|
|
608
612
|
cloneObj[attribute] = this.deepClone(property);
|
|
609
613
|
}
|
|
610
614
|
else {
|
|
@@ -614,22 +618,20 @@ class SystemUtils {
|
|
|
614
618
|
return cloneObj;
|
|
615
619
|
}
|
|
616
620
|
/**
|
|
617
|
-
*
|
|
618
|
-
* @param value
|
|
619
|
-
* @returns : true if is a valid UUID
|
|
621
|
+
* Returns `true` when `value` is a syntactically valid UUID string.
|
|
622
|
+
* @param value - The string to validate.
|
|
620
623
|
*/
|
|
621
624
|
static parseUUID(value) {
|
|
622
625
|
const regex = /^({|()?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}(}|))?$/;
|
|
623
|
-
return value
|
|
626
|
+
return !!value && value.length > 0 && regex.test(value);
|
|
624
627
|
}
|
|
625
628
|
/**
|
|
626
|
-
*
|
|
627
|
-
* @param value
|
|
628
|
-
* @returns : true if valid and not empty
|
|
629
|
+
* Returns `true` when `value` is a valid, non-empty (non-zero) UUID.
|
|
630
|
+
* @param value - The string to validate.
|
|
629
631
|
*/
|
|
630
632
|
static parseUUIDNotEmpty(value) {
|
|
631
633
|
const regex = /^({|()?0{8}-(0{4}-){3}0{12}(}|))?$/;
|
|
632
|
-
return this.parseUUID(value) && !regex.test(value);
|
|
634
|
+
return this.parseUUID(value) && !regex.test(value ?? '');
|
|
633
635
|
}
|
|
634
636
|
/**
|
|
635
637
|
* Return an empty UUID
|
|
@@ -717,7 +719,7 @@ class SystemUtils {
|
|
|
717
719
|
if (d && d.getTime() && d.getFullYear() > 1750)
|
|
718
720
|
return d;
|
|
719
721
|
// Parse values manually
|
|
720
|
-
|
|
722
|
+
const parts = this.getDateParts(value);
|
|
721
723
|
if (!parts)
|
|
722
724
|
return undefined;
|
|
723
725
|
if (parts[0] < 100)
|
|
@@ -746,13 +748,13 @@ class SystemUtils {
|
|
|
746
748
|
static formatDate(value, fmt = DateFormat.Short, locale = it) {
|
|
747
749
|
// No value at all
|
|
748
750
|
if (!value)
|
|
749
|
-
return
|
|
751
|
+
return '';
|
|
750
752
|
// A string
|
|
751
753
|
if (typeof value === 'string' || value instanceof String)
|
|
752
754
|
value = this.parseDate(value);
|
|
753
|
-
// Not a date
|
|
755
|
+
// Not a date
|
|
754
756
|
if (!(value instanceof Date && value.getTime()))
|
|
755
|
-
return
|
|
757
|
+
return '';
|
|
756
758
|
// Format
|
|
757
759
|
switch (fmt) {
|
|
758
760
|
case DateFormat.Short: return format(value, "dd/MM/yyyy", { locale: locale });
|
|
@@ -824,63 +826,62 @@ class SystemUtils {
|
|
|
824
826
|
}
|
|
825
827
|
}
|
|
826
828
|
/**
|
|
827
|
-
*
|
|
828
|
-
* @param value
|
|
829
|
-
* @param decimals
|
|
830
|
-
* @param locale
|
|
831
|
-
* @returns
|
|
829
|
+
* Formats a number using `Intl.NumberFormat`.
|
|
830
|
+
* @param value - The number to format.
|
|
831
|
+
* @param decimals - Maximum decimal places (default: `2`).
|
|
832
|
+
* @param locale - BCP 47 locale tag (default: `'it-IT'`).
|
|
833
|
+
* @returns The formatted number string.
|
|
832
834
|
*/
|
|
833
835
|
static formatNumber(value, decimals = 2, locale = 'it-IT') {
|
|
834
|
-
return Intl.NumberFormat(locale, { style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: decimals }).format(value
|
|
836
|
+
return Intl.NumberFormat(locale, { style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: decimals }).format(value);
|
|
835
837
|
}
|
|
836
838
|
/**
|
|
837
|
-
*
|
|
838
|
-
* @param value
|
|
839
|
-
* @param currency
|
|
840
|
-
* @param decimals
|
|
841
|
-
* @param locale
|
|
842
|
-
* @returns
|
|
839
|
+
* Formats a number as a currency string using `Intl.NumberFormat`.
|
|
840
|
+
* @param value - The number to format.
|
|
841
|
+
* @param currency - ISO 4217 currency code (default: `'EUR'`).
|
|
842
|
+
* @param decimals - Maximum decimal places (default: `2`).
|
|
843
|
+
* @param locale - BCP 47 locale tag (default: `'it-IT'`).
|
|
844
|
+
* @returns The formatted currency string.
|
|
843
845
|
*/
|
|
844
846
|
static formatCurrency(value, currency = 'EUR', decimals = 2, locale = 'it-IT') {
|
|
845
|
-
return Intl.NumberFormat(locale, { style: 'currency', currency
|
|
847
|
+
return Intl.NumberFormat(locale, { style: 'currency', currency, minimumFractionDigits: 0, maximumFractionDigits: decimals }).format(value);
|
|
846
848
|
}
|
|
847
849
|
/**
|
|
848
|
-
*
|
|
849
|
-
* @param value
|
|
850
|
-
* @returns
|
|
850
|
+
* Percent-encodes a string for safe inclusion in a URL.
|
|
851
|
+
* @param value - The string to encode.
|
|
852
|
+
* @returns The encoded string, or `undefined` when `value` is empty.
|
|
851
853
|
*/
|
|
852
854
|
static urlEncode(value) {
|
|
853
|
-
return value
|
|
855
|
+
return value.length > 0
|
|
854
856
|
? encodeURIComponent(value)
|
|
855
857
|
: undefined;
|
|
856
858
|
}
|
|
857
859
|
/**
|
|
858
|
-
*
|
|
859
|
-
* @param value
|
|
860
|
-
* @returns
|
|
860
|
+
* Decodes a percent-encoded URL string, treating `+` as a space.
|
|
861
|
+
* @param value - The encoded string to decode.
|
|
862
|
+
* @returns The decoded string, or `undefined` when `value` is empty or absent.
|
|
861
863
|
*/
|
|
862
864
|
static urlDecode(value) {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
? decodeURIComponent((value + "").replace(regex, "%20"))
|
|
865
|
+
return value && value.length > 0
|
|
866
|
+
? decodeURIComponent(value.replace(/\+/g, '%20'))
|
|
866
867
|
: undefined;
|
|
867
868
|
}
|
|
868
869
|
/**
|
|
869
|
-
*
|
|
870
|
-
* @param name
|
|
871
|
-
* @returns
|
|
870
|
+
* Reads a query string parameter value from the current page URL.
|
|
871
|
+
* @param name - The parameter name to look up.
|
|
872
|
+
* @returns The decoded parameter value, or `undefined` when absent or running server-side.
|
|
872
873
|
*/
|
|
873
874
|
static getQueryStringValueByName(name) {
|
|
874
875
|
if (!this.isBrowser())
|
|
875
876
|
return undefined;
|
|
876
877
|
const url = window.location.href;
|
|
877
|
-
if (url.includes(
|
|
878
|
-
const httpParams = new HttpParams({ fromString: url.split(
|
|
879
|
-
const
|
|
880
|
-
if (
|
|
881
|
-
return v;
|
|
882
|
-
else
|
|
878
|
+
if (url.includes('?')) {
|
|
879
|
+
const httpParams = new HttpParams({ fromString: url.split('?')[1] });
|
|
880
|
+
const raw = httpParams.get(name);
|
|
881
|
+
if (!raw)
|
|
883
882
|
return undefined;
|
|
883
|
+
const v = decodeURIComponent(raw);
|
|
884
|
+
return v && v !== 'null' ? v : undefined;
|
|
884
885
|
}
|
|
885
886
|
return undefined;
|
|
886
887
|
}
|
|
@@ -911,7 +912,7 @@ class SystemUtils {
|
|
|
911
912
|
* @returns the password strength info
|
|
912
913
|
*/
|
|
913
914
|
static calculatePasswordStrength(password) {
|
|
914
|
-
if (password
|
|
915
|
+
if (password && password.length > 0) {
|
|
915
916
|
let score = 0;
|
|
916
917
|
const suggestions = [];
|
|
917
918
|
// Length
|
|
@@ -1033,22 +1034,23 @@ class SystemUtils {
|
|
|
1033
1034
|
};
|
|
1034
1035
|
const children = n.children ?? n.subFolders ?? [];
|
|
1035
1036
|
if (children.length > 0) {
|
|
1036
|
-
node.children = this._toNodes(
|
|
1037
|
+
node.children = this._toNodes(children, node);
|
|
1037
1038
|
}
|
|
1038
1039
|
nodes.push(node);
|
|
1039
1040
|
});
|
|
1040
1041
|
return nodes;
|
|
1041
1042
|
}
|
|
1042
1043
|
/**
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1044
|
+
* Returns an array of individual power-of-2 flag values that are set in `value`.
|
|
1045
|
+
* @param value - The bitmask to decompose.
|
|
1046
|
+
* @param max - Upper-bound exponent: checks flags from `1` up to `1 << max` (default: `30`).
|
|
1047
|
+
* @returns Array of set flag values, or an empty array when `value` is `0`.
|
|
1048
|
+
*/
|
|
1047
1049
|
static getFlags(value, max = 30) {
|
|
1048
|
-
if (value) {
|
|
1049
|
-
|
|
1050
|
+
if (value !== 0) {
|
|
1051
|
+
const items = [];
|
|
1050
1052
|
let v = 1;
|
|
1051
|
-
|
|
1053
|
+
const m = 1 << max;
|
|
1052
1054
|
while (v < m) {
|
|
1053
1055
|
if ((value & v) === v)
|
|
1054
1056
|
items.push(v);
|
|
@@ -1082,23 +1084,33 @@ class SystemUtils {
|
|
|
1082
1084
|
}
|
|
1083
1085
|
}
|
|
1084
1086
|
|
|
1087
|
+
/**
|
|
1088
|
+
* Directive that listens to `keyup` events on a date input and debounces changes
|
|
1089
|
+
* into a {@link DateInterval} model, converting shorthand strings (e.g. "d/m") to dates.
|
|
1090
|
+
* Apply `[dateIntervalChange]="interval"` to the host `<input>` element.
|
|
1091
|
+
*/
|
|
1085
1092
|
class DateIntervalChangeDirective {
|
|
1086
1093
|
constructor() {
|
|
1094
|
+
/** The date interval model to update when the input value changes. */
|
|
1087
1095
|
this.dateIntervalChange = input(new DateInterval(null, null), ...(ngDevMode ? [{ debugName: "dateIntervalChange" }] : /* istanbul ignore next */ []));
|
|
1096
|
+
/** When `true`, the directive updates the interval's end date; otherwise the start date. */
|
|
1088
1097
|
this.end = input(false, ...(ngDevMode ? [{ debugName: "end" }] : /* istanbul ignore next */ []));
|
|
1089
1098
|
this.subject = new Subject();
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
.pipe(debounceTime(100))
|
|
1099
|
+
this.destroyRef = inject(DestroyRef);
|
|
1100
|
+
this.subject
|
|
1101
|
+
.pipe(debounceTime(100), takeUntilDestroyed(this.destroyRef))
|
|
1094
1102
|
.subscribe((e) => {
|
|
1095
|
-
const value = e.target
|
|
1096
|
-
|
|
1103
|
+
const value = e.target?.value;
|
|
1104
|
+
if (value !== undefined) {
|
|
1105
|
+
SystemUtils.changeDateInterval(value, this.dateIntervalChange(), this.end(), e.key === ' ');
|
|
1106
|
+
}
|
|
1097
1107
|
});
|
|
1098
1108
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1109
|
+
/**
|
|
1110
|
+
* Handles `keyup` events on the host element.
|
|
1111
|
+
* Prevents default browser behaviour for the space key and forwards the event to the debounce pipeline.
|
|
1112
|
+
* @param e - The keyboard event emitted by the host input.
|
|
1113
|
+
*/
|
|
1102
1114
|
onKeyup(e) {
|
|
1103
1115
|
if (e.key === ' ') {
|
|
1104
1116
|
e.preventDefault();
|
|
@@ -1115,26 +1127,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1115
1127
|
selector: '[dateIntervalChange]',
|
|
1116
1128
|
standalone: true,
|
|
1117
1129
|
}]
|
|
1118
|
-
}], propDecorators: { dateIntervalChange: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateIntervalChange", required: false }] }], end: [{ type: i0.Input, args: [{ isSignal: true, alias: "end", required: false }] }], onKeyup: [{
|
|
1130
|
+
}], ctorParameters: () => [], propDecorators: { dateIntervalChange: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateIntervalChange", required: false }] }], end: [{ type: i0.Input, args: [{ isSignal: true, alias: "end", required: false }] }], onKeyup: [{
|
|
1119
1131
|
type: HostListener,
|
|
1120
1132
|
args: ['keyup', ['$event']]
|
|
1121
1133
|
}] } });
|
|
1122
1134
|
|
|
1135
|
+
/**
|
|
1136
|
+
* Directive that copies a string payload to the clipboard when the host element is clicked.
|
|
1137
|
+
* Bind `[copyClipboard]="text"` to provide the content to copy and listen to `(copied)` for confirmation.
|
|
1138
|
+
*/
|
|
1123
1139
|
class CopyClipboardDirective {
|
|
1124
1140
|
constructor() {
|
|
1125
|
-
|
|
1141
|
+
/** The text to copy to the clipboard. Bound via the `copyClipboard` attribute. */
|
|
1142
|
+
this.payload = input(undefined, { ...(ngDevMode ? { debugName: "payload" } : /* istanbul ignore next */ {}), alias: 'copyClipboard' });
|
|
1143
|
+
/** Emits the copied text after a successful copy operation. */
|
|
1126
1144
|
this.copied = output({ alias: 'copied' });
|
|
1127
1145
|
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Handles click events on the host element and copies the payload to the clipboard.
|
|
1148
|
+
* Emits `copied` with the copied text on success.
|
|
1149
|
+
* @param e - The mouse click event.
|
|
1150
|
+
*/
|
|
1128
1151
|
onClick(e) {
|
|
1129
1152
|
e.preventDefault();
|
|
1130
|
-
|
|
1153
|
+
const payload = this.payload();
|
|
1154
|
+
if (!payload)
|
|
1131
1155
|
return;
|
|
1132
1156
|
if (SystemUtils.isBrowser()) {
|
|
1133
|
-
const listener = (
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
clipboard.setData('text/html', payload.toString());
|
|
1137
|
-
e.preventDefault();
|
|
1157
|
+
const listener = (clipEvent) => {
|
|
1158
|
+
clipEvent.clipboardData?.setData('text/html', payload);
|
|
1159
|
+
clipEvent.preventDefault();
|
|
1138
1160
|
this.copied.emit(payload);
|
|
1139
1161
|
};
|
|
1140
1162
|
document.addEventListener('copy', listener, false);
|
|
@@ -1156,12 +1178,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1156
1178
|
args: ['click', ['$event']]
|
|
1157
1179
|
}] } });
|
|
1158
1180
|
|
|
1181
|
+
/**
|
|
1182
|
+
* Directive that delegates validation to an externally provided validator function.
|
|
1183
|
+
* Bind `[validator]="myFn"` where `myFn` is `(c: AbstractControl) => ValidationErrors | null`.
|
|
1184
|
+
*/
|
|
1159
1185
|
class ValidatorDirective {
|
|
1160
1186
|
constructor() {
|
|
1161
|
-
|
|
1187
|
+
/** The custom validator function to apply. */
|
|
1188
|
+
this.validator = input(undefined, ...(ngDevMode ? [{ debugName: "validator" }] : /* istanbul ignore next */ []));
|
|
1162
1189
|
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Invokes the provided validator function against the given control.
|
|
1192
|
+
* Returns `null` (valid) when no function is bound.
|
|
1193
|
+
* @param control - The form control to validate.
|
|
1194
|
+
*/
|
|
1163
1195
|
validate(control) {
|
|
1164
|
-
|
|
1196
|
+
const fn = this.validator();
|
|
1197
|
+
return fn ? fn(control) : null;
|
|
1165
1198
|
}
|
|
1166
1199
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1167
1200
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: ValidatorDirective, isStandalone: true, selector: "[validator]", inputs: { validator: { classPropertyName: "validator", publicName: "validator", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: NG_VALIDATORS, useExisting: ValidatorDirective, multi: true }], ngImport: i0 }); }
|
|
@@ -1174,27 +1207,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1174
1207
|
standalone: true,
|
|
1175
1208
|
}]
|
|
1176
1209
|
}], propDecorators: { validator: [{ type: i0.Input, args: [{ isSignal: true, alias: "validator", required: false }] }] } });
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Directive that validates a control using the host object's `isValid()` method
|
|
1213
|
+
* or a boolean expression passed via `[validIf]`.
|
|
1214
|
+
*/
|
|
1177
1215
|
class ValidIfDirective {
|
|
1178
1216
|
constructor() {
|
|
1217
|
+
/** When `true`, the control is considered valid regardless of the bound value. */
|
|
1179
1218
|
this.validIf = input(false, ...(ngDevMode ? [{ debugName: "validIf" }] : /* istanbul ignore next */ []));
|
|
1180
1219
|
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Validates the control value against a boolean flag or the value's own `isValid()` method.
|
|
1222
|
+
* @param control - The form control to validate.
|
|
1223
|
+
*/
|
|
1181
1224
|
validate(control) {
|
|
1182
1225
|
let isValid = false;
|
|
1183
1226
|
const c = control.value ? control.value : null;
|
|
1184
|
-
if (!c)
|
|
1227
|
+
if (!c) {
|
|
1185
1228
|
isValid = this.validIf() === true;
|
|
1229
|
+
}
|
|
1186
1230
|
else {
|
|
1187
1231
|
try {
|
|
1188
1232
|
isValid = c.isValid();
|
|
1189
1233
|
}
|
|
1190
1234
|
catch { }
|
|
1191
1235
|
}
|
|
1192
|
-
|
|
1193
|
-
return null;
|
|
1194
|
-
}
|
|
1195
|
-
else {
|
|
1196
|
-
return { validIf: "Non valido." };
|
|
1197
|
-
}
|
|
1236
|
+
return isValid ? null : { validIf: "Non valido." };
|
|
1198
1237
|
}
|
|
1199
1238
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ValidIfDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1200
1239
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: ValidIfDirective, isStandalone: true, selector: "[validIf]", inputs: { validIf: { classPropertyName: "validIf", publicName: "validIf", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
|
|
@@ -1219,18 +1258,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1219
1258
|
standalone: true,
|
|
1220
1259
|
}]
|
|
1221
1260
|
}], propDecorators: { validIf: [{ type: i0.Input, args: [{ isSignal: true, alias: "validIf", required: false }] }] } });
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* Directive that validates that the host control's value equals the value of another control.
|
|
1264
|
+
* Bind `[equals]="otherControl"`.
|
|
1265
|
+
*/
|
|
1222
1266
|
class EqualsValidatorDirective {
|
|
1223
1267
|
constructor() {
|
|
1224
|
-
|
|
1268
|
+
/** The control whose value must match the host control's value. */
|
|
1269
|
+
this.equals = input(undefined, ...(ngDevMode ? [{ debugName: "equals" }] : /* istanbul ignore next */ []));
|
|
1225
1270
|
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Validates that the host control value equals the bound control's value.
|
|
1273
|
+
* Returns `null` (valid) when no control is bound.
|
|
1274
|
+
* @param control - The form control to validate.
|
|
1275
|
+
*/
|
|
1226
1276
|
validate(control) {
|
|
1227
|
-
const
|
|
1228
|
-
if (
|
|
1277
|
+
const eq = this.equals();
|
|
1278
|
+
if (!eq)
|
|
1229
1279
|
return null;
|
|
1230
|
-
}
|
|
1231
|
-
else {
|
|
1232
|
-
return { equals: "Non valido." };
|
|
1233
|
-
}
|
|
1280
|
+
return eq.value === control.value ? null : { equals: "Non valido." };
|
|
1234
1281
|
}
|
|
1235
1282
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EqualsValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1236
1283
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: EqualsValidatorDirective, isStandalone: true, selector: "[equals]", inputs: { equals: { classPropertyName: "equals", publicName: "equals", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
|
|
@@ -1255,24 +1302,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1255
1302
|
standalone: true,
|
|
1256
1303
|
}]
|
|
1257
1304
|
}], propDecorators: { equals: [{ type: i0.Input, args: [{ isSignal: true, alias: "equals", required: false }] }] } });
|
|
1305
|
+
|
|
1306
|
+
/**
|
|
1307
|
+
* Directive that validates that the host control's value is different from another control's value.
|
|
1308
|
+
* Bind `[notEqual]="otherControl"`.
|
|
1309
|
+
*/
|
|
1258
1310
|
class NotEqualValidatorDirective {
|
|
1259
1311
|
constructor() {
|
|
1260
|
-
|
|
1312
|
+
/** The control whose value must differ from the host control's value. */
|
|
1313
|
+
this.notEqual = input(undefined, ...(ngDevMode ? [{ debugName: "notEqual" }] : /* istanbul ignore next */ []));
|
|
1261
1314
|
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Validates that the host control value is not equal to the bound control's value.
|
|
1317
|
+
* Also clears the `notequal` error on the other control when the host becomes valid.
|
|
1318
|
+
* Returns `null` (valid) when no control is bound.
|
|
1319
|
+
* @param control - The form control to validate.
|
|
1320
|
+
*/
|
|
1262
1321
|
validate(control) {
|
|
1263
1322
|
const notEqual = this.notEqual();
|
|
1323
|
+
if (!notEqual)
|
|
1324
|
+
return null;
|
|
1264
1325
|
const isValid = (!notEqual.value && !control.value) || (notEqual.value !== control.value);
|
|
1265
|
-
const errors = isValid ? null : {
|
|
1266
|
-
// immediate show up
|
|
1267
|
-
const notEqualValue = this.notEqual();
|
|
1326
|
+
const errors = isValid ? null : { notequal: true };
|
|
1268
1327
|
if (errors) {
|
|
1269
1328
|
control.markAsTouched();
|
|
1270
1329
|
}
|
|
1271
|
-
else if (
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1330
|
+
else if (notEqual.hasError('notequal')) {
|
|
1331
|
+
notEqual.setErrors({ notequal: null });
|
|
1332
|
+
notEqual.updateValueAndValidity({ onlySelf: true, emitEvent: false });
|
|
1333
|
+
notEqual.markAsTouched();
|
|
1334
|
+
notEqual.markAsDirty();
|
|
1276
1335
|
}
|
|
1277
1336
|
return errors;
|
|
1278
1337
|
}
|
|
@@ -1299,27 +1358,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1299
1358
|
standalone: true,
|
|
1300
1359
|
}]
|
|
1301
1360
|
}], propDecorators: { notEqual: [{ type: i0.Input, args: [{ isSignal: true, alias: "notEqual", required: false }] }] } });
|
|
1361
|
+
|
|
1362
|
+
/**
|
|
1363
|
+
* Directive that validates a semicolon-separated list of email addresses.
|
|
1364
|
+
* Apply `emails` to a text input containing one or more addresses separated by `;`.
|
|
1365
|
+
*/
|
|
1302
1366
|
class EmailsValidatorDirective {
|
|
1367
|
+
/**
|
|
1368
|
+
* Validates each address in a semicolon-separated email list.
|
|
1369
|
+
* Returns `null` when the control is empty.
|
|
1370
|
+
* @param control - The form control to validate.
|
|
1371
|
+
*/
|
|
1303
1372
|
validate(control) {
|
|
1304
|
-
let isValid = true;
|
|
1305
1373
|
const input = control.value;
|
|
1306
1374
|
if (!input || input.length === 0)
|
|
1307
1375
|
return null;
|
|
1308
1376
|
const parts = input.replaceAll(/\r\n/g, '').split(';');
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
if (!SystemUtils.parseEmail(part)) {
|
|
1312
|
-
isValid = false;
|
|
1313
|
-
break;
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
if (!isValid) {
|
|
1318
|
-
return { emails: "Elenco non valido." };
|
|
1319
|
-
}
|
|
1320
|
-
else {
|
|
1321
|
-
return null;
|
|
1322
|
-
}
|
|
1377
|
+
const isValid = parts.every(part => part.length === 0 || !!SystemUtils.parseEmail(part));
|
|
1378
|
+
return isValid ? null : { emails: "Elenco non valido." };
|
|
1323
1379
|
}
|
|
1324
1380
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EmailsValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1325
1381
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: EmailsValidatorDirective, isStandalone: true, selector: "[emails]", providers: [
|
|
@@ -1344,18 +1400,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1344
1400
|
standalone: true,
|
|
1345
1401
|
}]
|
|
1346
1402
|
}] });
|
|
1403
|
+
|
|
1404
|
+
/**
|
|
1405
|
+
* Directive that validates a control value as a GUID / UUID string.
|
|
1406
|
+
* Apply `guid` to a text input that expects a valid UUID.
|
|
1407
|
+
*/
|
|
1347
1408
|
class GuidValidatorDirective {
|
|
1409
|
+
/**
|
|
1410
|
+
* Validates that the control value is a well-formed GUID / UUID.
|
|
1411
|
+
* Returns `null` when the control is empty.
|
|
1412
|
+
* @param control - The form control to validate.
|
|
1413
|
+
*/
|
|
1348
1414
|
validate(control) {
|
|
1349
1415
|
const input = control.value;
|
|
1350
1416
|
if (!input || input.length === 0)
|
|
1351
1417
|
return null;
|
|
1352
|
-
|
|
1353
|
-
if (!isValid) {
|
|
1354
|
-
return { guid: "Non valido." };
|
|
1355
|
-
}
|
|
1356
|
-
else {
|
|
1357
|
-
return null;
|
|
1358
|
-
}
|
|
1418
|
+
return SystemUtils.parseUUID(input) ? null : { guid: "Non valido." };
|
|
1359
1419
|
}
|
|
1360
1420
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: GuidValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1361
1421
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: GuidValidatorDirective, isStandalone: true, selector: "[guid]", providers: [
|
|
@@ -1380,19 +1440,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1380
1440
|
standalone: true,
|
|
1381
1441
|
}]
|
|
1382
1442
|
}] });
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* Directive that validates a control value as a parseable SQL-compatible date string.
|
|
1446
|
+
* Apply `sqlDate` to a text input that expects a date after year 1750.
|
|
1447
|
+
*/
|
|
1383
1448
|
class SqlDateValidatorDirective {
|
|
1449
|
+
/**
|
|
1450
|
+
* Validates that the control value can be parsed as a date after 1750.
|
|
1451
|
+
* Returns `null` when the control is empty.
|
|
1452
|
+
* @param control - The form control to validate.
|
|
1453
|
+
*/
|
|
1384
1454
|
validate(control) {
|
|
1385
1455
|
const input = control.value;
|
|
1386
1456
|
if (!input || input.length === 0)
|
|
1387
1457
|
return null;
|
|
1388
1458
|
const d = endOfDay(SystemUtils.parseDate(input));
|
|
1389
|
-
|
|
1390
|
-
if (!isValid) {
|
|
1391
|
-
return { sqlDate: "Non valido." };
|
|
1392
|
-
}
|
|
1393
|
-
else {
|
|
1394
|
-
return null;
|
|
1395
|
-
}
|
|
1459
|
+
return d.getFullYear() > 1750 ? null : { sqlDate: "Non valido." };
|
|
1396
1460
|
}
|
|
1397
1461
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SqlDateValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1398
1462
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: SqlDateValidatorDirective, isStandalone: true, selector: "[sqlDate]", providers: [
|
|
@@ -1417,20 +1481,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1417
1481
|
standalone: true,
|
|
1418
1482
|
}]
|
|
1419
1483
|
}] });
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* Directive that validates that a control value is not a future date.
|
|
1487
|
+
* Apply `notFuture` to a text input that expects a date on or before today.
|
|
1488
|
+
*/
|
|
1420
1489
|
class NotFutureValidatorDirective {
|
|
1490
|
+
/**
|
|
1491
|
+
* Validates that the control value represents a date that is not in the future.
|
|
1492
|
+
* Returns `null` when the control is empty.
|
|
1493
|
+
* @param control - The form control to validate.
|
|
1494
|
+
*/
|
|
1421
1495
|
validate(control) {
|
|
1422
1496
|
const input = control.value;
|
|
1423
1497
|
if (!input || input.length === 0)
|
|
1424
1498
|
return null;
|
|
1425
1499
|
const today = endOfDay(new Date());
|
|
1426
1500
|
const d = endOfDay(SystemUtils.parseDate(input));
|
|
1427
|
-
|
|
1428
|
-
if (!isValid) {
|
|
1429
|
-
return { notFuture: "Non valido." };
|
|
1430
|
-
}
|
|
1431
|
-
else {
|
|
1432
|
-
return null;
|
|
1433
|
-
}
|
|
1501
|
+
return d <= today ? null : { notFuture: "Non valido." };
|
|
1434
1502
|
}
|
|
1435
1503
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NotFutureValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1436
1504
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: NotFutureValidatorDirective, isStandalone: true, selector: "[notFuture]", providers: [
|
|
@@ -1455,16 +1523,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1455
1523
|
standalone: true,
|
|
1456
1524
|
}]
|
|
1457
1525
|
}] });
|
|
1526
|
+
|
|
1527
|
+
/**
|
|
1528
|
+
* Directive that validates a control value as a well-formed URL.
|
|
1529
|
+
* Apply `url` to a text input that expects a URL.
|
|
1530
|
+
*/
|
|
1458
1531
|
class UrlValidatorDirective {
|
|
1532
|
+
/**
|
|
1533
|
+
* Validates that the control value is a well-formed URL.
|
|
1534
|
+
* Returns `null` (valid) when the control is empty.
|
|
1535
|
+
* @param control - The form control to validate.
|
|
1536
|
+
*/
|
|
1459
1537
|
validate(control) {
|
|
1460
1538
|
const input = control.value;
|
|
1461
|
-
|
|
1462
|
-
if (!isValid) {
|
|
1463
|
-
return { url: "Non valido." };
|
|
1464
|
-
}
|
|
1465
|
-
else {
|
|
1539
|
+
if (!input || input.length === 0)
|
|
1466
1540
|
return null;
|
|
1467
|
-
}
|
|
1541
|
+
return SystemUtils.parseUrl(input) ? null : { url: "Non valido." };
|
|
1468
1542
|
}
|
|
1469
1543
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UrlValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1470
1544
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: UrlValidatorDirective, isStandalone: true, selector: "[url]", providers: [
|
|
@@ -1489,25 +1563,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1489
1563
|
standalone: true,
|
|
1490
1564
|
}]
|
|
1491
1565
|
}] });
|
|
1566
|
+
|
|
1567
|
+
/**
|
|
1568
|
+
* Directive that validates a file size against configurable minimum and maximum bounds.
|
|
1569
|
+
* Bind `[fileSize]` together with `[size]="fileSizeInMb"`, `[maxSizeMb]`, and `[minSizeMb]`.
|
|
1570
|
+
*/
|
|
1492
1571
|
class FileSizeValidatorDirective {
|
|
1493
1572
|
constructor() {
|
|
1573
|
+
/** Maximum allowed file size in megabytes. Defaults to 5. */
|
|
1494
1574
|
this.maxSizeMb = input(5, ...(ngDevMode ? [{ debugName: "maxSizeMb" }] : /* istanbul ignore next */ []));
|
|
1575
|
+
/** Minimum required file size in megabytes. Defaults to 0. */
|
|
1495
1576
|
this.minSizeMb = input(0, ...(ngDevMode ? [{ debugName: "minSizeMb" }] : /* istanbul ignore next */ []));
|
|
1496
|
-
|
|
1577
|
+
/** The actual file size in megabytes to validate against the bounds. */
|
|
1578
|
+
this.size = input(undefined, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
1497
1579
|
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Validates that the bound file size falls within the configured min/max range.
|
|
1582
|
+
* Returns `null` when no control value is present.
|
|
1583
|
+
* @param control - The form control to validate.
|
|
1584
|
+
*/
|
|
1498
1585
|
validate(control) {
|
|
1499
1586
|
const input = control.value;
|
|
1500
|
-
const size = this.size();
|
|
1501
|
-
const s = size ?? 0;
|
|
1502
1587
|
if (!input)
|
|
1503
1588
|
return null;
|
|
1589
|
+
const s = this.size() ?? 0;
|
|
1504
1590
|
const isValid = s <= this.maxSizeMb() && s >= this.minSizeMb();
|
|
1505
|
-
|
|
1506
|
-
return null;
|
|
1507
|
-
}
|
|
1508
|
-
else {
|
|
1509
|
-
return { fileSize: "Non valido." };
|
|
1510
|
-
}
|
|
1591
|
+
return isValid ? null : { fileSize: "Non valido." };
|
|
1511
1592
|
}
|
|
1512
1593
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FileSizeValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1513
1594
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: FileSizeValidatorDirective, isStandalone: true, selector: "[fileSize]", inputs: { maxSizeMb: { classPropertyName: "maxSizeMb", publicName: "maxSizeMb", isSignal: true, isRequired: false, transformFunction: null }, minSizeMb: { classPropertyName: "minSizeMb", publicName: "minSizeMb", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
|
|
@@ -1532,22 +1613,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1532
1613
|
standalone: true,
|
|
1533
1614
|
}]
|
|
1534
1615
|
}], propDecorators: { maxSizeMb: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSizeMb", required: false }] }], minSizeMb: [{ type: i0.Input, args: [{ isSignal: true, alias: "minSizeMb", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
|
|
1616
|
+
|
|
1617
|
+
/**
|
|
1618
|
+
* Directive that validates that a control value does not exceed a maximum word count.
|
|
1619
|
+
* Bind `[maxTerms]="10"` to allow at most 10 whitespace-separated terms.
|
|
1620
|
+
*/
|
|
1535
1621
|
class MaxTermsValidatorDirective {
|
|
1536
1622
|
constructor() {
|
|
1623
|
+
/** The maximum number of whitespace-separated terms allowed. */
|
|
1537
1624
|
this.maxTerms = input(0, ...(ngDevMode ? [{ debugName: "maxTerms" }] : /* istanbul ignore next */ []));
|
|
1538
1625
|
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Validates that the control value contains no more than the configured number of terms.
|
|
1628
|
+
* Returns `null` when the control is empty.
|
|
1629
|
+
* @param control - The form control to validate.
|
|
1630
|
+
*/
|
|
1539
1631
|
validate(control) {
|
|
1540
1632
|
const input = control.value;
|
|
1541
1633
|
if (!input)
|
|
1542
1634
|
return null;
|
|
1543
|
-
const terms = input
|
|
1544
|
-
|
|
1545
|
-
if (isValid) {
|
|
1546
|
-
return null;
|
|
1547
|
-
}
|
|
1548
|
-
else {
|
|
1549
|
-
return { maxTerms: "Non valido." };
|
|
1550
|
-
}
|
|
1635
|
+
const terms = input.match(/\S+/g)?.length ?? 0;
|
|
1636
|
+
return terms <= this.maxTerms() ? null : { maxTerms: "Non valido." };
|
|
1551
1637
|
}
|
|
1552
1638
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: MaxTermsValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1553
1639
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: MaxTermsValidatorDirective, isStandalone: true, selector: "[maxTerms]", inputs: { maxTerms: { classPropertyName: "maxTerms", publicName: "maxTerms", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
|
|
@@ -1572,16 +1658,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1572
1658
|
standalone: true,
|
|
1573
1659
|
}]
|
|
1574
1660
|
}], propDecorators: { maxTerms: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxTerms", required: false }] }] } });
|
|
1661
|
+
|
|
1662
|
+
/**
|
|
1663
|
+
* Directive that validates a control value as a sufficiently strong password.
|
|
1664
|
+
* Apply `password` to a password input.
|
|
1665
|
+
*/
|
|
1575
1666
|
class PasswordValidatorDirective {
|
|
1667
|
+
/**
|
|
1668
|
+
* Validates that the control value meets the minimum password-strength requirements.
|
|
1669
|
+
* @param control - The form control to validate.
|
|
1670
|
+
*/
|
|
1576
1671
|
validate(control) {
|
|
1577
1672
|
const input = control.value ?? '';
|
|
1578
1673
|
const strength = SystemUtils.calculatePasswordStrength(input);
|
|
1579
|
-
|
|
1580
|
-
return { password: "Non valido." };
|
|
1581
|
-
}
|
|
1582
|
-
else {
|
|
1583
|
-
return null;
|
|
1584
|
-
}
|
|
1674
|
+
return strength.isValid ? null : { password: "Non valido." };
|
|
1585
1675
|
}
|
|
1586
1676
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: PasswordValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1587
1677
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: PasswordValidatorDirective, isStandalone: true, selector: "[password]", providers: [
|
|
@@ -1606,14 +1696,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1606
1696
|
standalone: true,
|
|
1607
1697
|
}]
|
|
1608
1698
|
}] });
|
|
1699
|
+
|
|
1700
|
+
/**
|
|
1701
|
+
* Directive that validates a time string against optional allowed time slot ranges.
|
|
1702
|
+
* Bind `[time]` and optionally `[slots]="'08:00-12:00|14:00-18:00'"` (pipe-separated ranges).
|
|
1703
|
+
*/
|
|
1609
1704
|
class TimeValidatorDirective {
|
|
1610
1705
|
constructor() {
|
|
1611
|
-
|
|
1706
|
+
/** Optional pipe-separated list of allowed time ranges, e.g. `"08:00-12:00|14:00-18:00"`. */
|
|
1707
|
+
this.slots = input(undefined, ...(ngDevMode ? [{ debugName: "slots" }] : /* istanbul ignore next */ []));
|
|
1612
1708
|
}
|
|
1613
1709
|
/**
|
|
1614
|
-
*
|
|
1615
|
-
*
|
|
1616
|
-
* @
|
|
1710
|
+
* Parses a `"HH:MM"` time string into a comparable integer (e.g. `"09:30"` -> `930`).
|
|
1711
|
+
* Returns `-1` when the string is not a valid time.
|
|
1712
|
+
* @param value - The time string to parse.
|
|
1617
1713
|
*/
|
|
1618
1714
|
getTime(value) {
|
|
1619
1715
|
const p = value.split(':');
|
|
@@ -1623,37 +1719,33 @@ class TimeValidatorDirective {
|
|
|
1623
1719
|
if (hh < 0 || hh > 23)
|
|
1624
1720
|
return -1;
|
|
1625
1721
|
const mm = parseInt(p[1]);
|
|
1626
|
-
if (mm < 0 ||
|
|
1722
|
+
if (mm < 0 || mm > 59)
|
|
1627
1723
|
return -1;
|
|
1628
1724
|
return parseInt(p[0] + p[1]);
|
|
1629
1725
|
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Validates that the control value is a valid time string and, when slots are configured,
|
|
1728
|
+
* that it falls within at least one of the allowed ranges.
|
|
1729
|
+
* Returns `null` when the control is empty.
|
|
1730
|
+
* @param control - The form control to validate.
|
|
1731
|
+
*/
|
|
1630
1732
|
validate(control) {
|
|
1631
1733
|
const input = control.value;
|
|
1632
1734
|
if (!input || input.length === 0)
|
|
1633
1735
|
return null;
|
|
1634
|
-
let isValid = false;
|
|
1635
1736
|
const t = this.getTime(input);
|
|
1636
|
-
if (t
|
|
1637
|
-
const slotsValue = this.slots();
|
|
1638
|
-
if (slotsValue) {
|
|
1639
|
-
const slots = slotsValue.split('|');
|
|
1640
|
-
slots.forEach(s => {
|
|
1641
|
-
const t1 = this.getTime(s.substring(0, 5));
|
|
1642
|
-
const t2 = this.getTime(s.substring(6));
|
|
1643
|
-
if (t1 !== -1 && t2 !== -1 && t1 <= t && t2 >= t) {
|
|
1644
|
-
// Good
|
|
1645
|
-
isValid = true;
|
|
1646
|
-
return;
|
|
1647
|
-
}
|
|
1648
|
-
});
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
if (!isValid) {
|
|
1737
|
+
if (t === -1)
|
|
1652
1738
|
return { time: "Non valido." };
|
|
1739
|
+
const slotsValue = this.slots();
|
|
1740
|
+
if (slotsValue) {
|
|
1741
|
+
const isValid = slotsValue.split('|').some(s => {
|
|
1742
|
+
const t1 = this.getTime(s.substring(0, 5));
|
|
1743
|
+
const t2 = this.getTime(s.substring(6));
|
|
1744
|
+
return t1 !== -1 && t2 !== -1 && t1 <= t && t2 >= t;
|
|
1745
|
+
});
|
|
1746
|
+
return isValid ? null : { time: "Non valido." };
|
|
1653
1747
|
}
|
|
1654
|
-
|
|
1655
|
-
return null;
|
|
1656
|
-
}
|
|
1748
|
+
return null;
|
|
1657
1749
|
}
|
|
1658
1750
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: TimeValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1659
1751
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: TimeValidatorDirective, isStandalone: true, selector: "[time]", inputs: { slots: { classPropertyName: "slots", publicName: "slots", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
|
|
@@ -1678,18 +1770,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1678
1770
|
standalone: true,
|
|
1679
1771
|
}]
|
|
1680
1772
|
}], propDecorators: { slots: [{ type: i0.Input, args: [{ isSignal: true, alias: "slots", required: false }] }] } });
|
|
1773
|
+
|
|
1774
|
+
/**
|
|
1775
|
+
* Directive that validates that a string control value is not blank (whitespace-only).
|
|
1776
|
+
* Apply `notEmpty` to a text input where non-blank content is required.
|
|
1777
|
+
*/
|
|
1681
1778
|
class NotEmptyValidatorDirective {
|
|
1779
|
+
/**
|
|
1780
|
+
* Validates that the control value is a non-blank string.
|
|
1781
|
+
* Returns `null` when the control is empty or not a string.
|
|
1782
|
+
* @param control - The form control to validate.
|
|
1783
|
+
*/
|
|
1682
1784
|
validate(control) {
|
|
1683
1785
|
const input = control?.value;
|
|
1684
1786
|
if (!input || typeof input !== 'string' || input.length === 0)
|
|
1685
1787
|
return null;
|
|
1686
|
-
|
|
1687
|
-
if (!isValid) {
|
|
1688
|
-
return { notEmpty: true };
|
|
1689
|
-
}
|
|
1690
|
-
else {
|
|
1691
|
-
return null;
|
|
1692
|
-
}
|
|
1788
|
+
return input.trim().length > 0 ? null : { notEmpty: true };
|
|
1693
1789
|
}
|
|
1694
1790
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: NotEmptyValidatorDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1695
1791
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: NotEmptyValidatorDirective, isStandalone: true, selector: "[notEmpty]", providers: [
|
|
@@ -1715,29 +1811,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1715
1811
|
}]
|
|
1716
1812
|
}] });
|
|
1717
1813
|
|
|
1814
|
+
/**
|
|
1815
|
+
* General-purpose formatting pipe that converts a raw value to a locale-aware string
|
|
1816
|
+
* based on the specified format type.
|
|
1817
|
+
*
|
|
1818
|
+
* Supported types: `'date'` / `'D'`, `'currency'` / `'C'`, `'number'` / `'N'`,
|
|
1819
|
+
* `'number0'` / `'N0'`, `'percentage'` / `'P'`.
|
|
1820
|
+
*
|
|
1821
|
+
* Usage: `{{ value | format:'currency' }}`
|
|
1822
|
+
*/
|
|
1718
1823
|
class FormatPipe {
|
|
1719
|
-
|
|
1824
|
+
/**
|
|
1825
|
+
* Formats a value according to the specified type and optional pattern.
|
|
1826
|
+
* Returns `undefined` when the value is `null` or `undefined`, or when the type is unrecognised.
|
|
1827
|
+
* @param value - The raw value to format.
|
|
1828
|
+
* @param type - The format type identifier (default: `'date'`).
|
|
1829
|
+
* @param pattern - The date pattern used when `type` is `'date'` (default: `'dd/MM/yyyy'`).
|
|
1830
|
+
* @returns A formatted string, or `undefined` when the value cannot be formatted.
|
|
1831
|
+
*/
|
|
1832
|
+
transform(value, type = 'date', pattern = 'dd/MM/yyyy') {
|
|
1720
1833
|
if (value === undefined || value === null)
|
|
1721
1834
|
return undefined;
|
|
1722
1835
|
switch (type) {
|
|
1723
1836
|
case 'D':
|
|
1724
|
-
case 'date':
|
|
1837
|
+
case 'date': {
|
|
1725
1838
|
const d = SystemUtils.parseDate(value, it);
|
|
1726
1839
|
if (d)
|
|
1727
1840
|
return format(d, pattern, { locale: it });
|
|
1728
1841
|
break;
|
|
1842
|
+
}
|
|
1729
1843
|
case 'C':
|
|
1730
1844
|
case 'currency':
|
|
1731
|
-
return new Intl.NumberFormat('it-IT', { style: 'currency', currency: 'EUR' }).format(value
|
|
1845
|
+
return new Intl.NumberFormat('it-IT', { style: 'currency', currency: 'EUR' }).format(value);
|
|
1732
1846
|
case 'N':
|
|
1733
1847
|
case 'number':
|
|
1734
|
-
return new Intl.NumberFormat('it-IT', { style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 2 }).format(value
|
|
1848
|
+
return new Intl.NumberFormat('it-IT', { style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 2 }).format(value);
|
|
1735
1849
|
case 'N0':
|
|
1736
1850
|
case 'number0':
|
|
1737
|
-
return new Intl.NumberFormat('it-IT', { style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(value
|
|
1851
|
+
return new Intl.NumberFormat('it-IT', { style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(value);
|
|
1738
1852
|
case 'P':
|
|
1739
1853
|
case 'percentage':
|
|
1740
|
-
return new Intl.NumberFormat('it-IT', { style: 'percent', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(value
|
|
1854
|
+
return new Intl.NumberFormat('it-IT', { style: 'percent', minimumFractionDigits: 0, maximumFractionDigits: 0 }).format(value);
|
|
1741
1855
|
}
|
|
1742
1856
|
return undefined;
|
|
1743
1857
|
}
|
|
@@ -1752,17 +1866,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1752
1866
|
}]
|
|
1753
1867
|
}] });
|
|
1754
1868
|
|
|
1869
|
+
/**
|
|
1870
|
+
* Pipe that applies a global regex replacement on a string and returns the result
|
|
1871
|
+
* as sanitized HTML. When `regexValue` is `'\n'` and no `replaceValue` is given,
|
|
1872
|
+
* newlines are replaced with `<br>` tags.
|
|
1873
|
+
*
|
|
1874
|
+
* Usage: `{{ text | replace:'\n':'' }}`
|
|
1875
|
+
*/
|
|
1755
1876
|
class ReplacePipe {
|
|
1756
1877
|
constructor() {
|
|
1757
1878
|
this.sanitizer = inject(DomSanitizer);
|
|
1758
1879
|
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Replaces all occurrences of `regexValue` in `value` with `replaceValue`.
|
|
1882
|
+
* Returns `undefined` when `value` is empty or `undefined`.
|
|
1883
|
+
* @param value - The source string to process.
|
|
1884
|
+
* @param regexValue - The regex pattern string to match (applied with the global flag).
|
|
1885
|
+
* @param replaceValue - The replacement string. Defaults to `'<br>'` when `regexValue` is `'\n'` and this is falsy.
|
|
1886
|
+
* @returns A `SafeHtml` value with all matches replaced, or `undefined` when the input is empty.
|
|
1887
|
+
*/
|
|
1759
1888
|
transform(value, regexValue, replaceValue) {
|
|
1760
1889
|
if (!value)
|
|
1761
1890
|
return undefined;
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
}
|
|
1765
|
-
return this.sanitizer.bypassSecurityTrustHtml(value.replace(new RegExp(regexValue, 'g'), replaceValue));
|
|
1891
|
+
const replacement = (regexValue === '\n' && !replaceValue) ? '<br>' : replaceValue;
|
|
1892
|
+
return this.sanitizer.bypassSecurityTrustHtml(value.replace(new RegExp(regexValue, 'g'), replacement));
|
|
1766
1893
|
}
|
|
1767
1894
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ReplacePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
1768
1895
|
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: ReplacePipe, isStandalone: true, name: "replace" }); }
|
|
@@ -1775,10 +1902,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1775
1902
|
}]
|
|
1776
1903
|
}] });
|
|
1777
1904
|
|
|
1905
|
+
/**
|
|
1906
|
+
* Pipe that marks an HTML string as trusted so Angular does not escape it when
|
|
1907
|
+
* bound via `[innerHTML]`.
|
|
1908
|
+
*
|
|
1909
|
+
* Usage: `<div [innerHTML]="html | safeHtml"></div>`
|
|
1910
|
+
*/
|
|
1778
1911
|
class SafeHtmlPipe {
|
|
1779
1912
|
constructor() {
|
|
1780
1913
|
this.sanitizer = inject(DomSanitizer);
|
|
1781
1914
|
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Bypasses Angular's HTML sanitization and returns a `SafeHtml` instance.
|
|
1917
|
+
* @param value - The raw HTML string to trust. Treated as an empty string when `undefined`.
|
|
1918
|
+
* @returns A `SafeHtml` value that can be bound to `[innerHTML]` without escaping.
|
|
1919
|
+
*/
|
|
1782
1920
|
transform(value) {
|
|
1783
1921
|
return this.sanitizer.bypassSecurityTrustHtml(value ?? '');
|
|
1784
1922
|
}
|
|
@@ -1793,10 +1931,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1793
1931
|
}]
|
|
1794
1932
|
}] });
|
|
1795
1933
|
|
|
1934
|
+
/**
|
|
1935
|
+
* Pipe that marks a URL string as a trusted resource URL so Angular does not block it
|
|
1936
|
+
* when bound to attributes such as `[src]` or `[href]` on iframes, objects, or embeds.
|
|
1937
|
+
*
|
|
1938
|
+
* Usage: `<iframe [src]="url | safeUrl"></iframe>`
|
|
1939
|
+
*/
|
|
1796
1940
|
class SafeUrlPipe {
|
|
1797
1941
|
constructor() {
|
|
1798
1942
|
this.sanitizer = inject(DomSanitizer);
|
|
1799
1943
|
}
|
|
1944
|
+
/**
|
|
1945
|
+
* Bypasses Angular's resource-URL sanitization and returns a `SafeResourceUrl` instance.
|
|
1946
|
+
* @param value - The URL string to trust. Treated as an empty string when `undefined`.
|
|
1947
|
+
* @returns A `SafeResourceUrl` that can be bound to resource URL attributes without blocking.
|
|
1948
|
+
*/
|
|
1800
1949
|
transform(value) {
|
|
1801
1950
|
return this.sanitizer.bypassSecurityTrustResourceUrl(value ?? '');
|
|
1802
1951
|
}
|
|
@@ -1811,11 +1960,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1811
1960
|
}]
|
|
1812
1961
|
}] });
|
|
1813
1962
|
|
|
1963
|
+
/**
|
|
1964
|
+
* Impure pipe that filters an array using a caller-provided predicate function.
|
|
1965
|
+
* Because the pipe is impure it re-evaluates on every change-detection cycle,
|
|
1966
|
+
* which is necessary when the predicate's captured state changes.
|
|
1967
|
+
*
|
|
1968
|
+
* Usage: `*ngFor="let item of items | callback:myFilter"`
|
|
1969
|
+
*/
|
|
1814
1970
|
class SearchCallbackPipe {
|
|
1971
|
+
/**
|
|
1972
|
+
* Filters `items` by applying `callback` to each element.
|
|
1973
|
+
* Returns the original array unchanged when either argument is falsy.
|
|
1974
|
+
* @param items - The source array to filter. May be `undefined`.
|
|
1975
|
+
* @param callback - A predicate function that returns `true` for items to keep.
|
|
1976
|
+
* @returns A new filtered array, the original array when no callback is provided,
|
|
1977
|
+
* or `undefined` when `items` is `undefined`.
|
|
1978
|
+
*/
|
|
1815
1979
|
transform(items, callback) {
|
|
1816
|
-
if (!items || !callback)
|
|
1980
|
+
if (!items || !callback)
|
|
1817
1981
|
return items;
|
|
1818
|
-
}
|
|
1819
1982
|
return items.filter(item => callback(item));
|
|
1820
1983
|
}
|
|
1821
1984
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SearchCallbackPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
@@ -1830,35 +1993,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1830
1993
|
}]
|
|
1831
1994
|
}] });
|
|
1832
1995
|
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1996
|
+
/**
|
|
1997
|
+
* Impure pipe that filters an array of searchable items against a text query.
|
|
1998
|
+
*
|
|
1999
|
+
* Each item is matched either via its `searchBag.name` property (when present)
|
|
2000
|
+
* or by converting the item itself to a lowercase string. The optional `metadata`
|
|
2001
|
+
* argument is updated in-place with the total item count and the filtered count,
|
|
2002
|
+
* making it usable in the template alongside `*ngFor`.
|
|
2003
|
+
*
|
|
2004
|
+
* Usage:
|
|
2005
|
+
* ```html
|
|
2006
|
+
* <div *ngFor="let item of items | search:filterText:meta">...</div>
|
|
2007
|
+
* <div>Showing {{ meta.count }} of {{ meta.total }}</div>
|
|
2008
|
+
* ```
|
|
2009
|
+
*/
|
|
1844
2010
|
class SearchFilterPipe {
|
|
2011
|
+
/**
|
|
2012
|
+
* Filters `items` by performing a case-insensitive substring match against `value`.
|
|
2013
|
+
* When `items` or `value` is falsy the original array is returned unfiltered.
|
|
2014
|
+
* @param items - The source array to filter. May be `undefined`.
|
|
2015
|
+
* @param value - The search text to match against each item. May be `undefined`.
|
|
2016
|
+
* @param metadata - Optional object that is updated with `total` and `count` after filtering.
|
|
2017
|
+
* @returns The filtered array, the original array when no filter text is given,
|
|
2018
|
+
* or `undefined` when `items` is `undefined`.
|
|
2019
|
+
*/
|
|
1845
2020
|
transform(items, value, metadata) {
|
|
1846
2021
|
metadata ??= { total: 0, count: 0 };
|
|
1847
|
-
if (!items || !value)
|
|
2022
|
+
if (!items || !value)
|
|
1848
2023
|
return items;
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
if (item)
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
.indexOf(value.toLowerCase()) !== -1;
|
|
1856
|
-
else
|
|
1857
|
-
return item.toLowerCase().indexOf(value.toLowerCase()) !== -1;
|
|
1858
|
-
}
|
|
1859
|
-
return null;
|
|
2024
|
+
const query = value.toLowerCase();
|
|
2025
|
+
const result = items.filter(item => {
|
|
2026
|
+
if (!item)
|
|
2027
|
+
return false;
|
|
2028
|
+
const text = item.searchBag?.name ?? (typeof item === 'string' ? item : undefined);
|
|
2029
|
+
return text?.toLowerCase().includes(query) ?? false;
|
|
1860
2030
|
});
|
|
1861
|
-
// aggiorna i conteggi
|
|
1862
2031
|
metadata.total = items.length;
|
|
1863
2032
|
metadata.count = result.length;
|
|
1864
2033
|
return result;
|
|
@@ -1874,10 +2043,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1874
2043
|
}]
|
|
1875
2044
|
}] });
|
|
1876
2045
|
|
|
2046
|
+
/**
|
|
2047
|
+
* Pipe that converts plain-text newlines (`\r\n`, `\r`, `\n`) to HTML `<br>` tags
|
|
2048
|
+
* and marks the result as trusted HTML so Angular does not escape it.
|
|
2049
|
+
*
|
|
2050
|
+
* Usage: `{{ text | formatHtml }}`
|
|
2051
|
+
*/
|
|
1877
2052
|
class FormatHtmlPipe {
|
|
1878
2053
|
constructor() {
|
|
1879
2054
|
this.sanitizer = inject(DomSanitizer);
|
|
1880
2055
|
}
|
|
2056
|
+
/**
|
|
2057
|
+
* Transforms a plain-text string into sanitized HTML by replacing newline characters
|
|
2058
|
+
* with `<br>` tags.
|
|
2059
|
+
* @param value - The input string to transform. Treated as an empty string when `undefined`.
|
|
2060
|
+
* @returns A `SafeHtml` value that can be rendered with `[innerHTML]`.
|
|
2061
|
+
*/
|
|
1881
2062
|
transform(value) {
|
|
1882
2063
|
return this.sanitizer.bypassSecurityTrustHtml((value ?? '').replaceAll(/(?:\r\n|\r|\n)/g, '<br>'));
|
|
1883
2064
|
}
|
|
@@ -1910,131 +2091,148 @@ const UtilsMessages = {
|
|
|
1910
2091
|
UTILS_DIALOGS_SELECT_OPTIONS_CHANGED: '§utils-dialogs-select-options-changed'
|
|
1911
2092
|
};
|
|
1912
2093
|
|
|
2094
|
+
/**
|
|
2095
|
+
* Generic selection model that tracks a set of selected items identified by a lookup field.
|
|
2096
|
+
* Wraps Angular CDK's `SelectionModel` and adds lookup-based add/remove logic.
|
|
2097
|
+
*
|
|
2098
|
+
* @typeParam T - The item type held in the selection.
|
|
2099
|
+
* @typeParam V - The type of the lookup key used to identify items.
|
|
2100
|
+
*/
|
|
1913
2101
|
class SelectableModel {
|
|
2102
|
+
/**
|
|
2103
|
+
* Snapshot of all items currently tracked by this model (selected or previously toggled).
|
|
2104
|
+
* Backed by a signal — reads are always up to date.
|
|
2105
|
+
*/
|
|
1914
2106
|
get all() {
|
|
1915
|
-
return this._all;
|
|
2107
|
+
return this._all();
|
|
1916
2108
|
}
|
|
2109
|
+
/** The underlying CDK `SelectionModel` (provides `.selected`, `.isSelected`, etc.). */
|
|
1917
2110
|
get current() {
|
|
1918
2111
|
return this._current;
|
|
1919
2112
|
}
|
|
1920
|
-
|
|
2113
|
+
/**
|
|
2114
|
+
* @param allowMultiSelect - When `true` (default), multiple items can be selected simultaneously.
|
|
2115
|
+
* @param lookupFieldName - Name of the property used as the unique key when searching the internal list (default: `'id'`).
|
|
2116
|
+
*/
|
|
2117
|
+
constructor(allowMultiSelect = true, lookupFieldName = 'id') {
|
|
2118
|
+
/**
|
|
2119
|
+
* Emits whenever the selection changes.
|
|
2120
|
+
* Carries the affected item, or `undefined` when the whole selection is cleared.
|
|
2121
|
+
*/
|
|
1921
2122
|
this.changed = new EventEmitter();
|
|
1922
|
-
this._all = [];
|
|
2123
|
+
this._all = signal([], ...(ngDevMode ? [{ debugName: "_all" }] : /* istanbul ignore next */ []));
|
|
2124
|
+
/** Signal that is `true` when at least one item is selected. */
|
|
2125
|
+
this.hasValue = computed(() => this._all().length > 0, ...(ngDevMode ? [{ debugName: "hasValue" }] : /* istanbul ignore next */ []));
|
|
1923
2126
|
this._current = new SelectionModel(allowMultiSelect, []);
|
|
1924
|
-
this._all = [];
|
|
1925
2127
|
this._lookupFieldName = lookupFieldName;
|
|
1926
2128
|
}
|
|
1927
2129
|
/**
|
|
1928
|
-
*
|
|
1929
|
-
*
|
|
1930
|
-
* @param
|
|
2130
|
+
* Toggles the CDK selection state of an item that already exists in the tracked list.
|
|
2131
|
+
* Has no effect when `lookupValue` does not match any tracked item.
|
|
2132
|
+
* @param item - The item whose selection state should be toggled.
|
|
2133
|
+
* @param lookupValue - The key value used to locate the item in the internal list.
|
|
1931
2134
|
*/
|
|
1932
2135
|
updateCurrent(item, lookupValue) {
|
|
1933
|
-
const p = SystemUtils.arrayFindIndexByKey(this._all, this._lookupFieldName, lookupValue);
|
|
2136
|
+
const p = SystemUtils.arrayFindIndexByKey(this._all(), this._lookupFieldName, lookupValue);
|
|
1934
2137
|
if (p !== -1) {
|
|
1935
2138
|
this._current.toggle(item);
|
|
1936
2139
|
this.changed.emit(item);
|
|
1937
2140
|
}
|
|
1938
2141
|
}
|
|
1939
2142
|
/**
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
1942
|
-
* @param
|
|
2143
|
+
* Toggles an item in both the internal list and the CDK selection.
|
|
2144
|
+
* Adds the item when it is not yet tracked; removes it when it is.
|
|
2145
|
+
* @param item - The item to toggle.
|
|
2146
|
+
* @param lookupValue - The key value used to locate or register the item.
|
|
1943
2147
|
*/
|
|
1944
2148
|
toggle(item, lookupValue) {
|
|
1945
|
-
if (lookupValue)
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
this._current.toggle(item);
|
|
1954
|
-
this.changed.emit(item);
|
|
2149
|
+
if (lookupValue === undefined)
|
|
2150
|
+
return;
|
|
2151
|
+
const p = SystemUtils.arrayFindIndexByKey(this._all(), this._lookupFieldName, lookupValue);
|
|
2152
|
+
if (p === -1) {
|
|
2153
|
+
this._all.update(arr => [...arr, item]);
|
|
2154
|
+
}
|
|
2155
|
+
else {
|
|
2156
|
+
this._all.update(arr => arr.filter((_, i) => i !== p));
|
|
1955
2157
|
}
|
|
2158
|
+
this._current.toggle(item);
|
|
2159
|
+
this.changed.emit(item);
|
|
1956
2160
|
}
|
|
1957
2161
|
/**
|
|
1958
|
-
*
|
|
1959
|
-
* @param item
|
|
1960
|
-
* @param lookupValue
|
|
2162
|
+
* Adds an item to the internal list (when not already present) and marks it as selected.
|
|
2163
|
+
* @param item - The item to select.
|
|
2164
|
+
* @param lookupValue - The key value used to locate or register the item.
|
|
1961
2165
|
*/
|
|
1962
2166
|
select(item, lookupValue) {
|
|
1963
|
-
if (lookupValue)
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
this._current.select(item);
|
|
1969
|
-
this.changed.emit(item);
|
|
2167
|
+
if (lookupValue === undefined)
|
|
2168
|
+
return;
|
|
2169
|
+
const p = SystemUtils.arrayFindIndexByKey(this._all(), this._lookupFieldName, lookupValue);
|
|
2170
|
+
if (p === -1) {
|
|
2171
|
+
this._all.update(arr => [...arr, item]);
|
|
1970
2172
|
}
|
|
2173
|
+
this._current.select(item);
|
|
2174
|
+
this.changed.emit(item);
|
|
1971
2175
|
}
|
|
1972
2176
|
/**
|
|
1973
|
-
*
|
|
1974
|
-
*
|
|
1975
|
-
* @param
|
|
2177
|
+
* Removes an item from the internal list and deselects it in the CDK model.
|
|
2178
|
+
* Has no effect when the item is not currently tracked.
|
|
2179
|
+
* @param item - The item to deselect.
|
|
2180
|
+
* @param lookupValue - The key value used to locate the item in the internal list.
|
|
1976
2181
|
*/
|
|
1977
2182
|
deselect(item, lookupValue) {
|
|
1978
|
-
if (lookupValue)
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
2183
|
+
if (lookupValue === undefined)
|
|
2184
|
+
return;
|
|
2185
|
+
const p = SystemUtils.arrayFindIndexByKey(this._all(), this._lookupFieldName, lookupValue);
|
|
2186
|
+
if (p !== -1) {
|
|
2187
|
+
this._all.update(arr => arr.filter((_, i) => i !== p));
|
|
2188
|
+
this._current.deselect(item);
|
|
2189
|
+
this.changed.emit(item);
|
|
1985
2190
|
}
|
|
1986
2191
|
}
|
|
1987
2192
|
/**
|
|
1988
|
-
*
|
|
1989
|
-
* @param lookupValues
|
|
2193
|
+
* Deselects all items whose lookup key is contained in `lookupValues`.
|
|
2194
|
+
* @param lookupValues - Array of key values identifying the items to deselect.
|
|
1990
2195
|
*/
|
|
1991
2196
|
deselectByValues(lookupValues) {
|
|
1992
|
-
if (
|
|
2197
|
+
if (lookupValues.length === 0)
|
|
1993
2198
|
return;
|
|
1994
|
-
|
|
1995
|
-
const p = SystemUtils.arrayFindIndexByKey(this._all, this._lookupFieldName,
|
|
2199
|
+
for (const key of lookupValues) {
|
|
2200
|
+
const p = SystemUtils.arrayFindIndexByKey(this._all(), this._lookupFieldName, key);
|
|
1996
2201
|
if (p !== -1) {
|
|
1997
|
-
const item = this._all[p];
|
|
1998
|
-
this._all.
|
|
2202
|
+
const item = this._all()[p];
|
|
2203
|
+
this._all.update(arr => arr.filter((_, i) => i !== p));
|
|
1999
2204
|
this._current.deselect(item);
|
|
2000
2205
|
this.changed.emit(item);
|
|
2001
2206
|
}
|
|
2002
|
-
}
|
|
2207
|
+
}
|
|
2003
2208
|
}
|
|
2004
2209
|
/**
|
|
2005
|
-
*
|
|
2006
|
-
*
|
|
2210
|
+
* Runs `clearFunc` against every currently selected item, then clears the CDK selection.
|
|
2211
|
+
* The internal tracked list is **not** affected; only the CDK selection is cleared.
|
|
2212
|
+
* @param clearFunc - Callback invoked for each currently selected item before clearing.
|
|
2007
2213
|
*/
|
|
2008
2214
|
clearCurrent(clearFunc) {
|
|
2009
|
-
this._current.selected
|
|
2010
|
-
clearFunc(
|
|
2011
|
-
}
|
|
2215
|
+
for (const item of this._current.selected) {
|
|
2216
|
+
clearFunc(item);
|
|
2217
|
+
}
|
|
2012
2218
|
this._current.clear();
|
|
2013
|
-
this.changed.emit();
|
|
2219
|
+
this.changed.emit(undefined);
|
|
2014
2220
|
}
|
|
2015
2221
|
/**
|
|
2016
|
-
*
|
|
2222
|
+
* Clears both the internal tracked list and the CDK selection.
|
|
2017
2223
|
*/
|
|
2018
2224
|
clear() {
|
|
2019
|
-
this._all
|
|
2225
|
+
this._all.set([]);
|
|
2020
2226
|
this._current.clear();
|
|
2021
|
-
this.changed.emit();
|
|
2227
|
+
this.changed.emit(undefined);
|
|
2022
2228
|
}
|
|
2023
2229
|
/**
|
|
2024
|
-
*
|
|
2025
|
-
* @param lookupValue
|
|
2026
|
-
* @returns : true if the value is selected
|
|
2230
|
+
* Returns `true` when the item identified by `lookupValue` is present in the tracked list.
|
|
2231
|
+
* @param lookupValue - The key value to look up.
|
|
2027
2232
|
*/
|
|
2028
2233
|
isSelected(lookupValue) {
|
|
2029
2234
|
return (lookupValue !== undefined &&
|
|
2030
|
-
SystemUtils.arrayFindIndexByKey(this._all, this._lookupFieldName, lookupValue) !== -1);
|
|
2031
|
-
}
|
|
2032
|
-
/**
|
|
2033
|
-
* Checks if a selection exists
|
|
2034
|
-
* @returns : true if at least one selection exists
|
|
2035
|
-
*/
|
|
2036
|
-
hasValue() {
|
|
2037
|
-
return this._all.length > 0;
|
|
2235
|
+
SystemUtils.arrayFindIndexByKey(this._all(), this._lookupFieldName, lookupValue) !== -1);
|
|
2038
2236
|
}
|
|
2039
2237
|
}
|
|
2040
2238
|
|
|
@@ -2043,37 +2241,48 @@ class SelectableModel {
|
|
|
2043
2241
|
* https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel
|
|
2044
2242
|
*/
|
|
2045
2243
|
/**
|
|
2046
|
-
* Web
|
|
2047
|
-
*
|
|
2244
|
+
* Wrapper around the native Web `BroadcastChannel` API that adds typed messaging,
|
|
2245
|
+
* multiple named subscriptions, and a graceful disposal mechanism.
|
|
2246
|
+
*
|
|
2247
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel
|
|
2048
2248
|
*/
|
|
2049
2249
|
class BroadcastChannelManager {
|
|
2050
2250
|
/**
|
|
2051
|
-
*
|
|
2251
|
+
* The name of the underlying `BroadcastChannel`, or `undefined` when the API
|
|
2252
|
+
* is not supported by the current browser.
|
|
2052
2253
|
*/
|
|
2053
2254
|
get currentBus() {
|
|
2054
2255
|
return this.channel?.name;
|
|
2055
2256
|
}
|
|
2056
|
-
|
|
2257
|
+
/**
|
|
2258
|
+
* Opens a `BroadcastChannel` with the given name (when the API is available).
|
|
2259
|
+
* @param bus - The channel name to open (default: `'ARS-CHANNEL'`).
|
|
2260
|
+
*/
|
|
2261
|
+
constructor(bus = 'ARS-CHANNEL') {
|
|
2057
2262
|
this.subject = new Subject();
|
|
2058
2263
|
this.subscriptions = [];
|
|
2059
2264
|
if ('BroadcastChannel' in window) {
|
|
2060
2265
|
this.channel = new BroadcastChannel(bus);
|
|
2061
|
-
this.channel.onmessageerror = (
|
|
2062
|
-
console.error('[BroadcastChannelManager]
|
|
2266
|
+
this.channel.onmessageerror = (e) => {
|
|
2267
|
+
console.error('[BroadcastChannelManager] Message receive error', e);
|
|
2063
2268
|
};
|
|
2064
2269
|
this.channel.onmessage = (e) => {
|
|
2065
|
-
this.subject.next(e.data);
|
|
2066
2270
|
const bag = e.data;
|
|
2067
|
-
if (bag)
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2271
|
+
if (!bag)
|
|
2272
|
+
return;
|
|
2273
|
+
this.subject.next(bag);
|
|
2274
|
+
const info = this.subscriptions.find(m => m.messageId === bag.messageId);
|
|
2275
|
+
info?.action(bag);
|
|
2071
2276
|
};
|
|
2072
2277
|
}
|
|
2073
2278
|
else {
|
|
2074
|
-
console.error('[BroadcastChannelManager]
|
|
2279
|
+
console.error('[BroadcastChannelManager] BroadcastChannel API is not supported in this browser');
|
|
2075
2280
|
}
|
|
2076
2281
|
}
|
|
2282
|
+
/**
|
|
2283
|
+
* Closes the underlying channel and completes the internal subject after a short delay
|
|
2284
|
+
* to allow any in-flight messages to be delivered.
|
|
2285
|
+
*/
|
|
2077
2286
|
dispose() {
|
|
2078
2287
|
setTimeout(() => {
|
|
2079
2288
|
this.subscriptions = [];
|
|
@@ -2082,22 +2291,22 @@ class BroadcastChannelManager {
|
|
|
2082
2291
|
this.subject.complete();
|
|
2083
2292
|
}, 1000);
|
|
2084
2293
|
}
|
|
2085
|
-
/**
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
sendMessage(messageIdorBag, dataOrDelay, delay) {
|
|
2294
|
+
/** @internal Implementation that handles all overloads. */
|
|
2295
|
+
sendMessage(messageIdOrBag, dataOrDelay, delay) {
|
|
2296
|
+
const effectiveDelay = (typeof dataOrDelay === 'number' ? dataOrDelay : delay) ?? 0;
|
|
2089
2297
|
setTimeout(() => {
|
|
2090
|
-
if (typeof
|
|
2091
|
-
this.channel?.postMessage({ messageId:
|
|
2298
|
+
if (typeof messageIdOrBag === 'string') {
|
|
2299
|
+
this.channel?.postMessage({ messageId: messageIdOrBag, data: dataOrDelay });
|
|
2092
2300
|
}
|
|
2093
2301
|
else {
|
|
2094
|
-
this.channel?.postMessage(
|
|
2302
|
+
this.channel?.postMessage(messageIdOrBag);
|
|
2095
2303
|
}
|
|
2096
|
-
},
|
|
2304
|
+
}, effectiveDelay);
|
|
2097
2305
|
}
|
|
2098
2306
|
/**
|
|
2099
|
-
*
|
|
2100
|
-
*
|
|
2307
|
+
* Registers a handler for messages with the given `messageId`.
|
|
2308
|
+
* If a handler for the same `messageId` is already registered it is replaced.
|
|
2309
|
+
* @param args - The subscription descriptor containing the message ID and handler.
|
|
2101
2310
|
*/
|
|
2102
2311
|
subscribe(args) {
|
|
2103
2312
|
const i = this.subscriptions.findIndex(m => m.messageId === args.messageId);
|
|
@@ -2109,8 +2318,8 @@ class BroadcastChannelManager {
|
|
|
2109
2318
|
}
|
|
2110
2319
|
}
|
|
2111
2320
|
/**
|
|
2112
|
-
*
|
|
2113
|
-
* @param messageId The message
|
|
2321
|
+
* Removes the subscription for the given message ID, if one exists.
|
|
2322
|
+
* @param messageId - The message type identifier whose subscription should be removed.
|
|
2114
2323
|
*/
|
|
2115
2324
|
unsubscribe(messageId) {
|
|
2116
2325
|
const i = this.subscriptions.findIndex(m => m.messageId === messageId);
|
|
@@ -2119,60 +2328,66 @@ class BroadcastChannelManager {
|
|
|
2119
2328
|
}
|
|
2120
2329
|
}
|
|
2121
2330
|
/**
|
|
2122
|
-
*
|
|
2331
|
+
* Removes all registered subscriptions.
|
|
2123
2332
|
*/
|
|
2124
2333
|
unsubscribeAll() {
|
|
2125
2334
|
this.subscriptions = [];
|
|
2126
2335
|
}
|
|
2127
2336
|
}
|
|
2128
2337
|
|
|
2338
|
+
/**
|
|
2339
|
+
* Application-level messaging service that combines two transports:
|
|
2340
|
+
* - An in-process RxJS `Subject` for same-tab communication (`sendMessage` / `getMessage`).
|
|
2341
|
+
* - A native `BroadcastChannel` for cross-tab communication (`sendChannelMessage` / `subscribeChannelMessage`).
|
|
2342
|
+
*/
|
|
2129
2343
|
class BroadcastService {
|
|
2130
2344
|
constructor() {
|
|
2131
2345
|
this.subject = new Subject();
|
|
2132
2346
|
this.channel = new BroadcastChannelManager('ARS-CHANNEL');
|
|
2133
2347
|
}
|
|
2134
2348
|
/**
|
|
2135
|
-
*
|
|
2349
|
+
* Creates a new standalone `BroadcastChannelManager` instance bound to the shared ARS channel.
|
|
2350
|
+
* Useful when a caller needs direct channel access outside the service.
|
|
2351
|
+
* @returns A new `BroadcastChannelManager` connected to `'ARS-CHANNEL'`.
|
|
2136
2352
|
*/
|
|
2137
2353
|
static GetChannel() {
|
|
2138
|
-
return new BroadcastChannelManager(
|
|
2354
|
+
return new BroadcastChannelManager('ARS-CHANNEL');
|
|
2139
2355
|
}
|
|
2140
2356
|
ngOnDestroy() {
|
|
2141
2357
|
this.channel?.dispose();
|
|
2142
2358
|
}
|
|
2143
2359
|
/**
|
|
2144
|
-
*
|
|
2145
|
-
* @param id
|
|
2146
|
-
* @param data
|
|
2147
|
-
* @param delay
|
|
2360
|
+
* Publishes a message to the in-process subject, optionally after a delay.
|
|
2361
|
+
* @param id - The message type identifier.
|
|
2362
|
+
* @param data - Optional payload to attach to the message.
|
|
2363
|
+
* @param delay - Milliseconds to wait before publishing (default: `0`).
|
|
2148
2364
|
*/
|
|
2149
|
-
|
|
2365
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2366
|
+
sendMessage(id, data, delay = 0) {
|
|
2150
2367
|
setTimeout(() => {
|
|
2151
2368
|
this.subject.next({ id, data });
|
|
2152
2369
|
}, delay);
|
|
2153
2370
|
}
|
|
2154
2371
|
/**
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2372
|
+
* Publishes a typed message over the native `BroadcastChannel` (cross-tab), optionally after a delay.
|
|
2373
|
+
* @param id - The message type identifier.
|
|
2374
|
+
* @param data - Optional typed payload to send.
|
|
2375
|
+
* @param delay - Milliseconds to wait before sending (forwarded to the channel manager).
|
|
2376
|
+
*/
|
|
2160
2377
|
sendChannelMessage(id, data, delay) {
|
|
2161
|
-
|
|
2162
|
-
this.channel.sendMessage(id, data, delay);
|
|
2163
|
-
}, delay);
|
|
2378
|
+
this.channel.sendMessage(id, data, delay);
|
|
2164
2379
|
}
|
|
2165
2380
|
/**
|
|
2166
|
-
*
|
|
2167
|
-
* @param id
|
|
2168
|
-
* @param action
|
|
2381
|
+
* Registers a handler that is invoked whenever a channel message with the given `id` is received.
|
|
2382
|
+
* @param id - The message type identifier to listen for.
|
|
2383
|
+
* @param action - Callback invoked with the full `BroadcastChannelMessageBag` when a matching message arrives.
|
|
2169
2384
|
*/
|
|
2170
2385
|
subscribeChannelMessage(id, action) {
|
|
2171
|
-
this.channel.subscribe({ messageId: id, action
|
|
2386
|
+
this.channel.subscribe({ messageId: id, action });
|
|
2172
2387
|
}
|
|
2173
2388
|
/**
|
|
2174
|
-
*
|
|
2175
|
-
* @returns
|
|
2389
|
+
* Returns an `Observable` that emits every in-process message published via `sendMessage`.
|
|
2390
|
+
* @returns An observable stream of `BroadcastMessageInfo` objects.
|
|
2176
2391
|
*/
|
|
2177
2392
|
getMessage() {
|
|
2178
2393
|
return this.subject.asObservable();
|
|
@@ -2187,37 +2402,54 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2187
2402
|
}]
|
|
2188
2403
|
}] });
|
|
2189
2404
|
|
|
2405
|
+
/**
|
|
2406
|
+
* Application-wide environment configuration service.
|
|
2407
|
+
* Exposes writable signals for the base URIs used throughout the application.
|
|
2408
|
+
* Values should be set once during application bootstrap (e.g. in `APP_INITIALIZER`).
|
|
2409
|
+
*/
|
|
2190
2410
|
class EnvironmentService {
|
|
2191
2411
|
constructor() {
|
|
2192
|
-
|
|
2193
|
-
this.
|
|
2194
|
-
|
|
2195
|
-
this.
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
this.
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
}
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
set
|
|
2219
|
-
|
|
2220
|
-
|
|
2412
|
+
/** Writable signal holding the base URI of the frontend application (e.g. `https://app.example.com`). */
|
|
2413
|
+
this.appUriSignal = signal('', ...(ngDevMode ? [{ debugName: "appUriSignal" }] : /* istanbul ignore next */ []));
|
|
2414
|
+
/** Writable signal holding the base URI of the backend API service (e.g. `https://api.example.com`). */
|
|
2415
|
+
this.appServiceUriSignal = signal('', ...(ngDevMode ? [{ debugName: "appServiceUriSignal" }] : /* istanbul ignore next */ []));
|
|
2416
|
+
/**
|
|
2417
|
+
* Writable signal holding the redirect URI used after login.
|
|
2418
|
+
* When empty, `appLoginRedirectUri` falls back to `appUri`.
|
|
2419
|
+
*/
|
|
2420
|
+
this.appLoginRedirectUriSignal = signal('', ...(ngDevMode ? [{ debugName: "appLoginRedirectUriSignal" }] : /* istanbul ignore next */ []));
|
|
2421
|
+
/**
|
|
2422
|
+
* Writable signal holding the login endpoint URI of the backend service.
|
|
2423
|
+
* When empty, `appServiceLoginUri` falls back to `appServiceUri`.
|
|
2424
|
+
*/
|
|
2425
|
+
this.appServiceLoginUriSignal = signal('', ...(ngDevMode ? [{ debugName: "appServiceLoginUriSignal" }] : /* istanbul ignore next */ []));
|
|
2426
|
+
// ── Computed: fallback logic ──────────────────────────────────────────────
|
|
2427
|
+
this._effectiveLoginRedirectUri = computed(() => this.appLoginRedirectUriSignal() || this.appUriSignal(), ...(ngDevMode ? [{ debugName: "_effectiveLoginRedirectUri" }] : /* istanbul ignore next */ []));
|
|
2428
|
+
this._effectiveServiceLoginUri = computed(() => this.appServiceLoginUriSignal() || this.appServiceUriSignal(), ...(ngDevMode ? [{ debugName: "_effectiveServiceLoginUri" }] : /* istanbul ignore next */ []));
|
|
2429
|
+
}
|
|
2430
|
+
// ── Getter / setter API (kept for backward compatibility) ─────────────────
|
|
2431
|
+
/** Base URI of the frontend application. */
|
|
2432
|
+
get appUri() { return this.appUriSignal(); }
|
|
2433
|
+
/** @param value - The base URI of the frontend application. */
|
|
2434
|
+
set appUri(value) { this.appUriSignal.set(value); }
|
|
2435
|
+
/** Base URI of the backend API service. */
|
|
2436
|
+
get appServiceUri() { return this.appServiceUriSignal(); }
|
|
2437
|
+
/** @param value - The base URI of the backend API service. */
|
|
2438
|
+
set appServiceUri(value) { this.appServiceUriSignal.set(value); }
|
|
2439
|
+
/**
|
|
2440
|
+
* Effective login redirect URI.
|
|
2441
|
+
* Returns the explicitly set value, or falls back to `appUri` when empty.
|
|
2442
|
+
*/
|
|
2443
|
+
get appLoginRedirectUri() { return this._effectiveLoginRedirectUri(); }
|
|
2444
|
+
/** @param value - The redirect URI to use after login. */
|
|
2445
|
+
set appLoginRedirectUri(value) { this.appLoginRedirectUriSignal.set(value); }
|
|
2446
|
+
/**
|
|
2447
|
+
* Effective service login URI.
|
|
2448
|
+
* Returns the explicitly set value, or falls back to `appServiceUri` when empty.
|
|
2449
|
+
*/
|
|
2450
|
+
get appServiceLoginUri() { return this._effectiveServiceLoginUri(); }
|
|
2451
|
+
/** @param value - The login endpoint URI of the backend service. */
|
|
2452
|
+
set appServiceLoginUri(value) { this.appServiceLoginUriSignal.set(value); }
|
|
2221
2453
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EnvironmentService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2222
2454
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: EnvironmentService, providedIn: 'root' }); }
|
|
2223
2455
|
}
|
|
@@ -2228,13 +2460,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2228
2460
|
}]
|
|
2229
2461
|
}] });
|
|
2230
2462
|
|
|
2463
|
+
/**
|
|
2464
|
+
* Service that exposes reactive screen and device-capability information.
|
|
2465
|
+
*/
|
|
2231
2466
|
class ScreenService {
|
|
2232
2467
|
constructor() {
|
|
2468
|
+
/**
|
|
2469
|
+
* Writable signal that holds the current Material breakpoint alias
|
|
2470
|
+
* (e.g. `'xs'`, `'sm'`, `'md'`, `'lg'`, `'xl'`).
|
|
2471
|
+
* Updated externally by the breakpoint observer in the consuming component or module.
|
|
2472
|
+
*/
|
|
2233
2473
|
this.mq = signal('', ...(ngDevMode ? [{ debugName: "mq" }] : /* istanbul ignore next */ []));
|
|
2234
2474
|
}
|
|
2475
|
+
/**
|
|
2476
|
+
* Returns `true` when the primary input mechanism can hover over elements
|
|
2477
|
+
* with coarse pointer precision (i.e. a touch screen).
|
|
2478
|
+
*/
|
|
2235
2479
|
get isTouchable() {
|
|
2236
2480
|
return SystemUtils.isTouchable();
|
|
2237
2481
|
}
|
|
2482
|
+
/**
|
|
2483
|
+
* Returns `true` when the current browser is Internet Explorer or the legacy Edge (EdgeHTML).
|
|
2484
|
+
* Modern Chromium-based Edge reports a different user-agent and will return `false`.
|
|
2485
|
+
*/
|
|
2238
2486
|
get isIEOrEdge() {
|
|
2239
2487
|
return /msie\s|trident\/|edge\//i.test(window.navigator.userAgent);
|
|
2240
2488
|
}
|
|
@@ -2248,124 +2496,115 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2248
2496
|
}]
|
|
2249
2497
|
}] });
|
|
2250
2498
|
|
|
2499
|
+
/**
|
|
2500
|
+
* Application-level theme management service.
|
|
2501
|
+
*
|
|
2502
|
+
* Responsibilities:
|
|
2503
|
+
* - Persists the user's preferred theme in `localStorage`.
|
|
2504
|
+
* - Applies `'light'` or `'dark'` CSS class to `<body>`.
|
|
2505
|
+
* - Reacts to OS-level colour-scheme changes when the theme is set to `'auto'`.
|
|
2506
|
+
* - Synchronises theme changes across browser tabs via `BroadcastChannel`.
|
|
2507
|
+
* - Exposes reactive signals (`themeIcon`, `themeName`, `toggleTooltip`) for the UI.
|
|
2508
|
+
*/
|
|
2251
2509
|
class ThemeService {
|
|
2252
2510
|
constructor() {
|
|
2253
|
-
// messaggi broadcast
|
|
2254
2511
|
this.broadcastChannel = new BroadcastChannelManager('THEME-SERVICE');
|
|
2255
2512
|
this.broadcastMessage = '$theme-changed';
|
|
2256
|
-
// per intercettare il cambio di tema a livello di sistema
|
|
2257
2513
|
this.prefersColorSchemeMediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
|
|
2258
|
-
/**
|
|
2259
|
-
* Theme changed e.
|
|
2260
|
-
* Notify the consuming component about the incoming theme change.
|
|
2261
|
-
* @example
|
|
2262
|
-
* // React to theme changes
|
|
2263
|
-
* this.themeService.themeChanged
|
|
2264
|
-
* .pipe(takeUntil(this.unsubscribe))
|
|
2265
|
-
* .subscribe((value: ThemeType) => {
|
|
2266
|
-
* // do the job done....
|
|
2267
|
-
* });
|
|
2268
|
-
*/
|
|
2269
2514
|
this.themeChanged = new BehaviorSubject('auto');
|
|
2270
|
-
// espone al consumatore un'observable
|
|
2271
|
-
this.changed = this.themeChanged.asObservable();
|
|
2272
2515
|
this.renderFactory2 = inject(RendererFactory2);
|
|
2273
2516
|
this.renderer = this.renderFactory2.createRenderer(null, null);
|
|
2274
|
-
/**
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
this.theme = signal("light", ...(ngDevMode ? [{ debugName: "theme" }] : /* istanbul ignore next */ []));
|
|
2278
|
-
/**
|
|
2279
|
-
* The current theme is "System default"
|
|
2280
|
-
*/
|
|
2517
|
+
/** The currently selected `ThemeType` (may be `'auto'`). */
|
|
2518
|
+
this.theme = signal('light', ...(ngDevMode ? [{ debugName: "theme" }] : /* istanbul ignore next */ []));
|
|
2519
|
+
/** `true` when the theme is set to follow the system preference. */
|
|
2281
2520
|
this.auto = computed(() => this.theme() === 'auto', ...(ngDevMode ? [{ debugName: "auto" }] : /* istanbul ignore next */ []));
|
|
2282
|
-
/**
|
|
2283
|
-
* Configuration schema about the icon, the text and the toggle() sequence
|
|
2284
|
-
*/
|
|
2285
2521
|
this.themeInfo = {
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2522
|
+
auto: { icon: 'routine', name: 'Tema di sistema', next: 'light', tooltip: 'Passa a tema chiaro' },
|
|
2523
|
+
light: { icon: 'light_mode', name: 'Tema chiaro', next: 'dark', tooltip: 'Passa a tema scuro' },
|
|
2524
|
+
dark: { icon: 'dark_mode', name: 'Tema scuro', next: 'auto', tooltip: 'Passa a tema di sistema' },
|
|
2289
2525
|
};
|
|
2290
|
-
/**
|
|
2291
|
-
* Icon of the current theme (mat-icon code)
|
|
2292
|
-
*/
|
|
2526
|
+
/** Reactive Material icon code for the current theme (use with `<mat-icon>`). */
|
|
2293
2527
|
this.themeIcon = computed(() => this.themeInfo[this.theme()].icon, ...(ngDevMode ? [{ debugName: "themeIcon" }] : /* istanbul ignore next */ []));
|
|
2294
|
-
/**
|
|
2295
|
-
* Current theme name
|
|
2296
|
-
*/
|
|
2528
|
+
/** Reactive human-readable name of the current theme. */
|
|
2297
2529
|
this.themeName = computed(() => this.themeInfo[this.theme()].name, ...(ngDevMode ? [{ debugName: "themeName" }] : /* istanbul ignore next */ []));
|
|
2298
|
-
/**
|
|
2299
|
-
* Tooltip text on toggle
|
|
2300
|
-
*/
|
|
2530
|
+
/** Reactive tooltip text for the theme toggle button. */
|
|
2301
2531
|
this.toggleTooltip = computed(() => this.themeInfo[this.theme()].tooltip, ...(ngDevMode ? [{ debugName: "toggleTooltip" }] : /* istanbul ignore next */ []));
|
|
2532
|
+
this.changed = this.themeChanged.asObservable();
|
|
2302
2533
|
}
|
|
2303
2534
|
/**
|
|
2304
|
-
*
|
|
2535
|
+
* Initialises the service: loads the preferred theme, registers OS-change listener,
|
|
2536
|
+
* and subscribes to cross-tab broadcast messages.
|
|
2537
|
+
* Call this once during application bootstrap (e.g. in `APP_INITIALIZER`).
|
|
2538
|
+
* @param theme - The initial theme to apply (defaults to the value stored in `localStorage`).
|
|
2305
2539
|
*/
|
|
2306
2540
|
initialize(theme = this.getPreferredTheme()) {
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2541
|
+
this.prefersColorSchemeMediaQueryList.onchange = () => {
|
|
2542
|
+
if (this.auto())
|
|
2543
|
+
this.setTheme(this.theme());
|
|
2544
|
+
};
|
|
2310
2545
|
this.broadcastChannel.subscribe({
|
|
2311
2546
|
messageId: this.broadcastMessage,
|
|
2312
2547
|
action: (bag) => {
|
|
2313
|
-
if (this.theme() !== bag.data) {
|
|
2548
|
+
if (bag.data && this.theme() !== bag.data) {
|
|
2314
2549
|
this.setTheme(bag.data);
|
|
2315
2550
|
}
|
|
2316
2551
|
}
|
|
2317
2552
|
});
|
|
2318
|
-
// Load theme
|
|
2319
2553
|
this.setTheme(theme);
|
|
2320
2554
|
}
|
|
2321
2555
|
ngOnDestroy() {
|
|
2322
2556
|
this.broadcastChannel.dispose();
|
|
2323
2557
|
}
|
|
2324
2558
|
/**
|
|
2325
|
-
*
|
|
2559
|
+
* Advances the theme through the cycle: `auto` → `light` → `dark` → `auto`.
|
|
2326
2560
|
*/
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2561
|
+
toggleTheme() {
|
|
2562
|
+
this.setTheme(this.themeInfo[this.theme()].next);
|
|
2563
|
+
}
|
|
2564
|
+
/**
|
|
2565
|
+
* Returns the resolved effective theme (`'light'` or `'dark'`), taking the OS preference
|
|
2566
|
+
* into account when the current mode is `'auto'`.
|
|
2567
|
+
* @returns `'light'` or `'dark'` — never `'auto'`.
|
|
2568
|
+
*/
|
|
2569
|
+
getTheme() {
|
|
2570
|
+
if (this.auto()) {
|
|
2571
|
+
return this.prefersColorSchemeMediaQueryList.matches ? 'dark' : 'light';
|
|
2331
2572
|
}
|
|
2332
|
-
return
|
|
2573
|
+
return this.getPreferredTheme();
|
|
2333
2574
|
}
|
|
2334
2575
|
/**
|
|
2335
|
-
*
|
|
2336
|
-
*
|
|
2337
|
-
* @returns
|
|
2576
|
+
* Reads the theme stored in `localStorage` and validates it.
|
|
2577
|
+
* Falls back to `'auto'` when the stored value is missing or invalid.
|
|
2578
|
+
* @returns The persisted `ThemeType`, or `'auto'` as default.
|
|
2579
|
+
*/
|
|
2580
|
+
getPreferredTheme() {
|
|
2581
|
+
const stored = localStorage.getItem('preferred-theme');
|
|
2582
|
+
return stored && this.isTheme(stored) ? stored : 'auto';
|
|
2583
|
+
}
|
|
2584
|
+
/**
|
|
2585
|
+
* Returns `true` when `value` is a valid `ThemeType` string.
|
|
2586
|
+
* Acts as a TypeScript type guard.
|
|
2587
|
+
* @param value - The string to check.
|
|
2338
2588
|
*/
|
|
2339
2589
|
isTheme(value) {
|
|
2340
|
-
return
|
|
2590
|
+
return value === 'auto' || value === 'light' || value === 'dark';
|
|
2341
2591
|
}
|
|
2342
2592
|
/**
|
|
2343
|
-
*
|
|
2344
|
-
*
|
|
2593
|
+
* Applies the given theme to `<body>` (CSS class), persists it to `localStorage`,
|
|
2594
|
+
* notifies the `changed` observable, and broadcasts the change to other tabs.
|
|
2595
|
+
* @param theme - The `ThemeType` to apply (defaults to `'auto'`).
|
|
2345
2596
|
*/
|
|
2346
2597
|
setTheme(theme = 'auto') {
|
|
2347
2598
|
this.renderer.removeClass(document.body, 'light');
|
|
2348
2599
|
this.renderer.removeClass(document.body, 'dark');
|
|
2349
2600
|
this.theme.set(theme);
|
|
2350
|
-
localStorage.setItem(
|
|
2351
|
-
|
|
2352
|
-
|
|
2601
|
+
localStorage.setItem('preferred-theme', theme);
|
|
2602
|
+
const effectiveClass = this.auto()
|
|
2603
|
+
? (this.prefersColorSchemeMediaQueryList.matches ? 'dark' : 'light')
|
|
2604
|
+
: theme;
|
|
2605
|
+
this.renderer.addClass(document.body, effectiveClass);
|
|
2353
2606
|
this.themeChanged.next(this.getTheme());
|
|
2354
|
-
this.broadcastChannel.sendMessage(this.broadcastMessage,
|
|
2355
|
-
}
|
|
2356
|
-
/**
|
|
2357
|
-
* Toggle theme
|
|
2358
|
-
*/
|
|
2359
|
-
toggleTheme() {
|
|
2360
|
-
this.setTheme(this.themeInfo[this.theme()].next);
|
|
2361
|
-
}
|
|
2362
|
-
/**
|
|
2363
|
-
* Get current real theme: light or dark
|
|
2364
|
-
*/
|
|
2365
|
-
getTheme() {
|
|
2366
|
-
return (this.auto()
|
|
2367
|
-
? (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light")
|
|
2368
|
-
: this.getPreferredTheme());
|
|
2607
|
+
this.broadcastChannel.sendMessage(this.broadcastMessage, theme);
|
|
2369
2608
|
}
|
|
2370
2609
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2371
2610
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ThemeService, providedIn: "root" }); }
|
|
@@ -2375,7 +2614,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2375
2614
|
args: [{
|
|
2376
2615
|
providedIn: "root",
|
|
2377
2616
|
}]
|
|
2378
|
-
}] });
|
|
2617
|
+
}], ctorParameters: () => [] });
|
|
2379
2618
|
|
|
2380
2619
|
class ArsCoreModule {
|
|
2381
2620
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ArsCoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
@@ -2495,7 +2734,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2495
2734
|
}]
|
|
2496
2735
|
}] });
|
|
2497
2736
|
|
|
2498
|
-
/**
|
|
2737
|
+
/**
|
|
2738
|
+
* Creates an array of the given length, filling each slot with the result of `valueFunction`.
|
|
2739
|
+
* @param length - Number of elements to create.
|
|
2740
|
+
* @param valueFunction - Factory called with each index to produce the element value.
|
|
2741
|
+
* @returns Typed array of `length` elements.
|
|
2742
|
+
*/
|
|
2499
2743
|
function range(length, valueFunction) {
|
|
2500
2744
|
const valuesArray = Array(length);
|
|
2501
2745
|
for (let i = 0; i < length; i++) {
|
|
@@ -2526,32 +2770,61 @@ const MAT_DATE_FNS_FORMATS = {
|
|
|
2526
2770
|
monthYearA11yLabel: 'LLLL uuuu',
|
|
2527
2771
|
},
|
|
2528
2772
|
};
|
|
2529
|
-
/**
|
|
2773
|
+
/**
|
|
2774
|
+
* date-fns adapter that integrates Angular Material's date picker with the date-fns library,
|
|
2775
|
+
* applying `Europe/Rome` timezone for all parsed and created dates.
|
|
2776
|
+
*/
|
|
2530
2777
|
class DateFnsAdapter extends DateAdapter {
|
|
2531
2778
|
constructor() {
|
|
2532
2779
|
super();
|
|
2533
2780
|
const matDateLocale = inject(MAT_DATE_LOCALE, { optional: true });
|
|
2534
|
-
|
|
2781
|
+
if (matDateLocale) {
|
|
2782
|
+
this.setLocale(matDateLocale);
|
|
2783
|
+
}
|
|
2535
2784
|
}
|
|
2785
|
+
/**
|
|
2786
|
+
* Returns the year component of the given date.
|
|
2787
|
+
* @param date - The source date.
|
|
2788
|
+
*/
|
|
2536
2789
|
getYear(date) {
|
|
2537
2790
|
return getYear(date);
|
|
2538
2791
|
}
|
|
2792
|
+
/**
|
|
2793
|
+
* Returns the zero-based month index of the given date (0 = January).
|
|
2794
|
+
* @param date - The source date.
|
|
2795
|
+
*/
|
|
2539
2796
|
getMonth(date) {
|
|
2540
2797
|
return getMonth(date);
|
|
2541
2798
|
}
|
|
2799
|
+
/**
|
|
2800
|
+
* Returns the day-of-month of the given date (1-based).
|
|
2801
|
+
* @param date - The source date.
|
|
2802
|
+
*/
|
|
2542
2803
|
getDate(date) {
|
|
2543
2804
|
return getDate(date);
|
|
2544
2805
|
}
|
|
2806
|
+
/**
|
|
2807
|
+
* Returns the day-of-week of the given date (0 = Sunday).
|
|
2808
|
+
* @param date - The source date.
|
|
2809
|
+
*/
|
|
2545
2810
|
getDayOfWeek(date) {
|
|
2546
2811
|
return getDay(date);
|
|
2547
2812
|
}
|
|
2813
|
+
/**
|
|
2814
|
+
* Returns an array of 12 month name strings formatted for the active locale.
|
|
2815
|
+
* @param style - One of `'long'`, `'short'`, or `'narrow'`.
|
|
2816
|
+
*/
|
|
2548
2817
|
getMonthNames(style) {
|
|
2549
2818
|
const pattern = MONTH_FORMATS[style];
|
|
2550
2819
|
return range(12, i => this.format(new Date(2017, i, 1), pattern));
|
|
2551
2820
|
}
|
|
2821
|
+
/**
|
|
2822
|
+
* Returns an array of 31 day-of-month label strings formatted using `Intl.DateTimeFormat`
|
|
2823
|
+
* when available, falling back to plain numeric strings.
|
|
2824
|
+
*/
|
|
2552
2825
|
getDateNames() {
|
|
2553
2826
|
const dtf = typeof Intl !== 'undefined'
|
|
2554
|
-
? new Intl.DateTimeFormat(this.locale
|
|
2827
|
+
? new Intl.DateTimeFormat(this.locale?.code, {
|
|
2555
2828
|
day: 'numeric',
|
|
2556
2829
|
timeZone: 'utc',
|
|
2557
2830
|
})
|
|
@@ -2565,28 +2838,52 @@ class DateFnsAdapter extends DateAdapter {
|
|
|
2565
2838
|
date.setUTCHours(0, 0, 0, 0);
|
|
2566
2839
|
return dtf.format(date).replace(/[\u200e\u200f]/g, '');
|
|
2567
2840
|
}
|
|
2568
|
-
return i +
|
|
2841
|
+
return String(i + 1);
|
|
2569
2842
|
});
|
|
2570
2843
|
}
|
|
2844
|
+
/**
|
|
2845
|
+
* Returns an array of 7 day-of-week name strings formatted for the active locale.
|
|
2846
|
+
* @param style - One of `'long'`, `'short'`, or `'narrow'`.
|
|
2847
|
+
*/
|
|
2571
2848
|
getDayOfWeekNames(style) {
|
|
2572
2849
|
const pattern = DAY_OF_WEEK_FORMATS[style];
|
|
2573
2850
|
return range(7, i => this.format(new Date(2017, 0, i + 1), pattern));
|
|
2574
2851
|
}
|
|
2852
|
+
/**
|
|
2853
|
+
* Returns the four-digit year string for the given date.
|
|
2854
|
+
* @param date - The source date.
|
|
2855
|
+
*/
|
|
2575
2856
|
getYearName(date) {
|
|
2576
2857
|
return this.format(date, 'y');
|
|
2577
2858
|
}
|
|
2859
|
+
/**
|
|
2860
|
+
* Returns the first day of the week for the active locale (0 = Sunday, 1 = Monday, …).
|
|
2861
|
+
*/
|
|
2578
2862
|
getFirstDayOfWeek() {
|
|
2579
|
-
return this.locale
|
|
2863
|
+
return this.locale?.options?.weekStartsOn ?? 0;
|
|
2580
2864
|
}
|
|
2865
|
+
/**
|
|
2866
|
+
* Returns the number of days in the month of the given date.
|
|
2867
|
+
* @param date - The source date.
|
|
2868
|
+
*/
|
|
2581
2869
|
getNumDaysInMonth(date) {
|
|
2582
2870
|
return getDaysInMonth(date);
|
|
2583
2871
|
}
|
|
2872
|
+
/**
|
|
2873
|
+
* Creates an independent copy of the given date.
|
|
2874
|
+
* @param date - The date to clone.
|
|
2875
|
+
*/
|
|
2584
2876
|
clone(date) {
|
|
2585
2877
|
return new Date(date.getTime());
|
|
2586
2878
|
}
|
|
2879
|
+
/**
|
|
2880
|
+
* Creates a `Date` in the `Europe/Rome` timezone for the given year, month, and day.
|
|
2881
|
+
* Throws an `Error` when any component is out of range.
|
|
2882
|
+
* @param year - Full four-digit year.
|
|
2883
|
+
* @param month - Zero-based month index (0 = January, 11 = December).
|
|
2884
|
+
* @param date - Day-of-month (1-based).
|
|
2885
|
+
*/
|
|
2587
2886
|
createDate(year, month, date) {
|
|
2588
|
-
// Check for invalid month and date (except upper bound on date which we have to check after
|
|
2589
|
-
// creating the Date).
|
|
2590
2887
|
if (month < 0 || month > 11) {
|
|
2591
2888
|
throw Error(`Invalid month index "${month}". Month index has to be between 0 and 11.`);
|
|
2592
2889
|
}
|
|
@@ -2598,32 +2895,41 @@ class DateFnsAdapter extends DateAdapter {
|
|
|
2598
2895
|
const result = new Date();
|
|
2599
2896
|
result.setFullYear(year, month, date);
|
|
2600
2897
|
result.setHours(0, 0, 0, 0);
|
|
2601
|
-
const result2 = new TZDate(result,
|
|
2602
|
-
// Check that the date wasn't above the upper bound for the month, causing the month to overflow
|
|
2898
|
+
const result2 = new TZDate(result, 'Europe/Rome');
|
|
2603
2899
|
if (result2.getMonth() !== month) {
|
|
2604
2900
|
throw Error(`Invalid date "${date}" for month with index "${month}".`);
|
|
2605
2901
|
}
|
|
2606
2902
|
return result2;
|
|
2607
2903
|
}
|
|
2904
|
+
/**
|
|
2905
|
+
* Returns today's date in the local timezone.
|
|
2906
|
+
*/
|
|
2608
2907
|
today() {
|
|
2609
2908
|
return new Date();
|
|
2610
2909
|
}
|
|
2910
|
+
/**
|
|
2911
|
+
* Parses a value into a `Date`.
|
|
2912
|
+
* - Strings are first attempted as ISO 8601, then matched against each format in `parseFormat`.
|
|
2913
|
+
* - Numbers are treated as Unix timestamps (milliseconds).
|
|
2914
|
+
* - Existing `Date` instances are cloned.
|
|
2915
|
+
* @param value - The value to parse.
|
|
2916
|
+
* @param parseFormat - A format string or an array of format strings (date-fns tokens).
|
|
2917
|
+
* @returns A valid `Date` in `Europe/Rome`, an invalid sentinel, or `null` for unrecognised input.
|
|
2918
|
+
*/
|
|
2611
2919
|
parse(value, parseFormat) {
|
|
2612
2920
|
if (typeof value === 'string' && value.length > 0) {
|
|
2613
|
-
|
|
2921
|
+
const iso8601Date = parseISO(value);
|
|
2614
2922
|
if (this.isValid(iso8601Date)) {
|
|
2615
|
-
|
|
2616
|
-
return iso8601Date;
|
|
2923
|
+
return new TZDate(iso8601Date, 'Europe/Rome');
|
|
2617
2924
|
}
|
|
2618
2925
|
const formats = Array.isArray(parseFormat) ? parseFormat : [parseFormat];
|
|
2619
|
-
if (!
|
|
2926
|
+
if (!formats.length) {
|
|
2620
2927
|
throw Error('Formats array must not be empty.');
|
|
2621
2928
|
}
|
|
2622
2929
|
for (const currentFormat of formats) {
|
|
2623
|
-
|
|
2930
|
+
const fromFormat = parse(value, currentFormat, new Date(), { locale: this.locale });
|
|
2624
2931
|
if (this.isValid(fromFormat)) {
|
|
2625
|
-
|
|
2626
|
-
return fromFormat;
|
|
2932
|
+
return new TZDate(fromFormat, 'Europe/Rome');
|
|
2627
2933
|
}
|
|
2628
2934
|
}
|
|
2629
2935
|
return this.invalid();
|
|
@@ -2634,30 +2940,56 @@ class DateFnsAdapter extends DateAdapter {
|
|
|
2634
2940
|
else if (value instanceof Date) {
|
|
2635
2941
|
return this.clone(value);
|
|
2636
2942
|
}
|
|
2637
|
-
return
|
|
2943
|
+
return null;
|
|
2638
2944
|
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Formats a `Date` using the given date-fns display format string.
|
|
2947
|
+
* Throws an `Error` when `date` is not valid.
|
|
2948
|
+
* @param date - The date to format.
|
|
2949
|
+
* @param displayFormat - A date-fns format string (e.g. `'P'`, `'LLL uuuu'`).
|
|
2950
|
+
*/
|
|
2639
2951
|
format(date, displayFormat) {
|
|
2640
2952
|
if (!this.isValid(date)) {
|
|
2641
2953
|
throw Error('DateFnsAdapter: Cannot format invalid date.');
|
|
2642
2954
|
}
|
|
2643
2955
|
return format(date, displayFormat, { locale: this.locale });
|
|
2644
2956
|
}
|
|
2957
|
+
/**
|
|
2958
|
+
* Adds the given number of whole years to a date.
|
|
2959
|
+
* @param date - The base date.
|
|
2960
|
+
* @param years - Number of years to add (can be negative).
|
|
2961
|
+
*/
|
|
2645
2962
|
addCalendarYears(date, years) {
|
|
2646
2963
|
return addYears(date, years);
|
|
2647
2964
|
}
|
|
2965
|
+
/**
|
|
2966
|
+
* Adds the given number of whole months to a date.
|
|
2967
|
+
* @param date - The base date.
|
|
2968
|
+
* @param months - Number of months to add (can be negative).
|
|
2969
|
+
*/
|
|
2648
2970
|
addCalendarMonths(date, months) {
|
|
2649
2971
|
return addMonths(date, months);
|
|
2650
2972
|
}
|
|
2973
|
+
/**
|
|
2974
|
+
* Adds the given number of whole days to a date.
|
|
2975
|
+
* @param date - The base date.
|
|
2976
|
+
* @param days - Number of days to add (can be negative).
|
|
2977
|
+
*/
|
|
2651
2978
|
addCalendarDays(date, days) {
|
|
2652
2979
|
return addDays(date, days);
|
|
2653
2980
|
}
|
|
2981
|
+
/**
|
|
2982
|
+
* Serialises a date to an ISO 8601 date string (`yyyy-MM-dd`).
|
|
2983
|
+
* @param date - The date to serialise.
|
|
2984
|
+
*/
|
|
2654
2985
|
toIso8601(date) {
|
|
2655
2986
|
return formatISO(date, { representation: 'date' });
|
|
2656
2987
|
}
|
|
2657
2988
|
/**
|
|
2658
|
-
* Returns the given value
|
|
2659
|
-
*
|
|
2660
|
-
*
|
|
2989
|
+
* Returns the given value when it is a valid `Date`, or `null` for an empty string.
|
|
2990
|
+
* Deserialises valid ISO 8601 strings into `Date` instances.
|
|
2991
|
+
* Delegates all other values to the base-class implementation.
|
|
2992
|
+
* @param value - The raw value to deserialise.
|
|
2661
2993
|
*/
|
|
2662
2994
|
deserialize(value) {
|
|
2663
2995
|
if (typeof value === 'string') {
|
|
@@ -2671,12 +3003,23 @@ class DateFnsAdapter extends DateAdapter {
|
|
|
2671
3003
|
}
|
|
2672
3004
|
return super.deserialize(value);
|
|
2673
3005
|
}
|
|
3006
|
+
/**
|
|
3007
|
+
* Returns `true` when `obj` is an instance of `Date`.
|
|
3008
|
+
* @param obj - The object to test.
|
|
3009
|
+
*/
|
|
2674
3010
|
isDateInstance(obj) {
|
|
2675
3011
|
return isDate(obj);
|
|
2676
3012
|
}
|
|
3013
|
+
/**
|
|
3014
|
+
* Returns `true` when `date` represents a valid point in time.
|
|
3015
|
+
* @param date - The date to validate.
|
|
3016
|
+
*/
|
|
2677
3017
|
isValid(date) {
|
|
2678
3018
|
return isValid(date);
|
|
2679
3019
|
}
|
|
3020
|
+
/**
|
|
3021
|
+
* Returns a sentinel `Date` that represents an invalid date (`new Date(NaN)`).
|
|
3022
|
+
*/
|
|
2680
3023
|
invalid() {
|
|
2681
3024
|
return new Date(NaN);
|
|
2682
3025
|
}
|
|
@@ -2712,21 +3055,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2712
3055
|
}]
|
|
2713
3056
|
}] });
|
|
2714
3057
|
|
|
3058
|
+
/**
|
|
3059
|
+
* Directive that removes focus from the host element after it is clicked,
|
|
3060
|
+
* preventing the browser from keeping a visible focus ring post-interaction.
|
|
3061
|
+
* Apply `removeFocus` to any focusable element (e.g. a button).
|
|
3062
|
+
*/
|
|
2715
3063
|
class RemoveFocusDirective {
|
|
2716
3064
|
constructor() {
|
|
2717
3065
|
this.elementRef = inject(ElementRef);
|
|
2718
3066
|
}
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
3067
|
+
/**
|
|
3068
|
+
* Handles click events on the host element and blurs it on the next event-loop tick
|
|
3069
|
+
* so that the click action completes before focus is removed.
|
|
3070
|
+
*/
|
|
3071
|
+
onClick() {
|
|
3072
|
+
const el = this.elementRef.nativeElement;
|
|
3073
|
+
if (el && typeof el.blur === 'function') {
|
|
3074
|
+
setTimeout(() => el.blur(), 0);
|
|
2726
3075
|
}
|
|
2727
3076
|
}
|
|
2728
3077
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RemoveFocusDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
2729
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RemoveFocusDirective, isStandalone: true, selector: "[removeFocus]", host: { listeners: { "click": "onClick(
|
|
3078
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RemoveFocusDirective, isStandalone: true, selector: "[removeFocus]", host: { listeners: { "click": "onClick()" } }, ngImport: i0 }); }
|
|
2730
3079
|
}
|
|
2731
3080
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RemoveFocusDirective, decorators: [{
|
|
2732
3081
|
type: Directive,
|
|
@@ -2736,13 +3085,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2736
3085
|
}]
|
|
2737
3086
|
}], propDecorators: { onClick: [{
|
|
2738
3087
|
type: HostListener,
|
|
2739
|
-
args: ['click'
|
|
3088
|
+
args: ['click']
|
|
2740
3089
|
}] } });
|
|
2741
3090
|
|
|
3091
|
+
/**
|
|
3092
|
+
* Pipe that converts a Markdown string to sanitized HTML using `SystemUtils.markdownToHtml`.
|
|
3093
|
+
*
|
|
3094
|
+
* Usage: `{{ text | formatMarkdown }}`
|
|
3095
|
+
*/
|
|
2742
3096
|
class FormatMarkdownPipe {
|
|
2743
3097
|
constructor() {
|
|
2744
3098
|
this.sanitizer = inject(DomSanitizer);
|
|
2745
3099
|
}
|
|
3100
|
+
/**
|
|
3101
|
+
* Transforms a Markdown string into sanitized HTML.
|
|
3102
|
+
* @param value - The Markdown input to convert. Treated as an empty string when `undefined`.
|
|
3103
|
+
* @returns A `SafeHtml` value that can be rendered with `[innerHTML]`.
|
|
3104
|
+
*/
|
|
2746
3105
|
transform(value) {
|
|
2747
3106
|
return this.sanitizer.bypassSecurityTrustHtml(SystemUtils.markdownToHtml(value ?? ''));
|
|
2748
3107
|
}
|