turbogui-angular 20.7.0 → 20.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,25 +32,27 @@ import { FormsModule, Validators, FormControl } from '@angular/forms';
32
32
  * License Url : -> http://www.apache.org/licenses/LICENSE-2.0
33
33
  * CopyRight : -> Copyright 2018 Edertone Advanded Solutions. https://www.edertone.com
34
34
  */
35
- /** This directive is used to trigger an event when the user clicks outside of an element */
36
35
  /**
36
+ * It is necessary to import TurboGuiAngularModule at the component that uses this directive
37
+ *
37
38
  * This directive is used to execute an action when the user clicks outside of an element.
38
- * If we set the elementClickOutside tag to the html element and set the onClickOutside event to a function: <element elementClickOutside (onClickOutside)="onClickedOutside()" ...,
39
+ *
40
+ * If we set the elementClickOutside tag to the html element and set the clickOutsideElement event to a function: <element elementClickOutside (clickOutsideElement)="clickOutsideElement()" ...,
39
41
  * this function will be executed when the user clicks outside of the element.
40
42
  */
41
43
  class ElementClickOutsideDirective {
42
44
  constructor(elementRef) {
43
45
  this.elementRef = elementRef;
44
- this.onClickOutside = new EventEmitter();
46
+ this.clickOutsideElement = new EventEmitter();
45
47
  }
46
48
  onClick(targetElement) {
47
49
  const clickedInside = this.elementRef.nativeElement.contains(targetElement);
48
50
  if (!clickedInside) {
49
- this.onClickOutside.emit();
51
+ this.clickOutsideElement.emit();
50
52
  }
51
53
  }
52
54
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: ElementClickOutsideDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); }
53
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.5", type: ElementClickOutsideDirective, isStandalone: false, selector: "[elementClickOutside]", outputs: { onClickOutside: "onClickOutside" }, host: { listeners: { "document:click": "onClick($event.target)" } }, ngImport: i0 }); }
55
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.5", type: ElementClickOutsideDirective, isStandalone: false, selector: "[elementClickOutside]", outputs: { clickOutsideElement: "clickOutsideElement" }, host: { listeners: { "document:click": "onClick($event.target)" } }, ngImport: i0 }); }
54
56
  }
55
57
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: ElementClickOutsideDirective, decorators: [{
56
58
  type: Directive,
@@ -58,7 +60,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImpor
58
60
  selector: '[elementClickOutside]',
59
61
  standalone: false
60
62
  }]
61
- }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { onClickOutside: [{
63
+ }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { clickOutsideElement: [{
62
64
  type: Output
63
65
  }], onClick: [{
64
66
  type: HostListener,
@@ -102,8 +104,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImpor
102
104
  standalone: false
103
105
  }]
104
106
  }], propDecorators: { elementCreated: [{
105
- type: Output,
106
- args: ['elementCreated']
107
+ type: Output
107
108
  }] } });
108
109
 
109
110
  /**
@@ -143,8 +144,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImpor
143
144
  standalone: false
144
145
  }]
145
146
  }], propDecorators: { elementDestroyed: [{
146
- type: Output,
147
- args: ['elementDestroyed']
147
+ type: Output
148
148
  }] } });
149
149
 
150
150
  /**
@@ -1075,6 +1075,130 @@ class DialogService extends SingletoneStrictClass {
1075
1075
  });
1076
1076
  });
1077
1077
  }
1078
+ /**
1079
+ * Shows a native OS file browser dialog to let the user select a single file from their local file system.
1080
+ *
1081
+ * @param options An object containing options for the file browser dialog:
1082
+ * - accept: A string that defines the file types the file input should accept. For example: '.csv,.xlsx', 'image/*', '.pdf', 'image/jpeg, image/png'.
1083
+ * - maxFileSize: (Optional) The maximum file size in bytes allowed for the selected file. If the selected file exceeds this size, the promise will be rejected with an error
1084
+ * - loadData: (Optional) Defines how the file content should be read and returned.
1085
+ * 'no' (default): Returns the raw File object without its data.
1086
+ * 'ArrayBuffer': Returns the File object with its content read as a raw binary `ArrayBuffer` in the `data` property.
1087
+ * 'text': Returns the File object with its content read as a text string in the `data` property.
1088
+ * 'base64': Returns the File object with its content read as a Base64 encoded string in the `data` property.
1089
+ *
1090
+ * @returns A Promise that resolves with the selected `File` object (which may have an added `data` property), or `null` if the user cancels the dialog.
1091
+ * The promise will be rejected with an Error if the selected file exceeds the specified size limits.
1092
+ */
1093
+ async addFileBrowserDialog(options) {
1094
+ const files = await this._addFileBrowserDialogInternal({
1095
+ multiple: false,
1096
+ accept: options.accept,
1097
+ maxFileSize: options.maxFileSize,
1098
+ loadData: options.loadData
1099
+ });
1100
+ return files ? files[0] : null;
1101
+ }
1102
+ /**
1103
+ * Shows a native OS file browser dialog to let the user select one or more files from their local file system.
1104
+ *
1105
+ * @param options An object containing options for the file browser dialog:
1106
+ * - accept: A string that defines the file types the file input should accept. For example: '.csv,.xlsx', 'image/*', '.pdf', 'image/jpeg, image/png'.
1107
+ * - maxFileSize: (Optional) The maximum file size in bytes allowed for any single selected file. If a selected file exceeds this size, the promise will be rejected with an error.
1108
+ * - maxTotalSize: (Optional) The maximum total size in bytes for all selected files combined. If the total size of all files exceeds this limit, the promise will be rejected with an error.
1109
+ * - loadData: (Optional) Defines how the file content should be read and returned.
1110
+ * 'no' (default): Returns an array of `File` objects without their data.
1111
+ * 'ArrayBuffer': Returns an array of `File` objects, each with its content read as a raw binary `ArrayBuffer` in the `data` property.
1112
+ * 'text': Returns an array of `File` objects, each with its content read as a text string in the `data` property.
1113
+ * 'base64': Returns an array of `File` objects, each with its content read as a Base64 encoded string in the `data` property.
1114
+ *
1115
+ * @returns A Promise that resolves with an array of `File` objects (which may have an added `data` property), or `null` if the user cancels the dialog.
1116
+ * The promise will be rejected with an Error if the selected files exceed the specified size limits.
1117
+ */
1118
+ addFilesBrowserDialog(options) {
1119
+ return this._addFileBrowserDialogInternal({
1120
+ multiple: true,
1121
+ accept: options.accept,
1122
+ maxFileSize: options.maxFileSize,
1123
+ maxTotalSize: options.maxTotalSize,
1124
+ loadData: options.loadData
1125
+ });
1126
+ }
1127
+ /**
1128
+ * Auxiliary method that combines the logic for addFileBrowserDialog and addFilesBrowserDialog
1129
+ */
1130
+ async _addFileBrowserDialogInternal(options) {
1131
+ if (!this._isEnabled) {
1132
+ return null;
1133
+ }
1134
+ // Create a hidden input element to show the file browser dialog
1135
+ const input = this._renderer.createElement('input');
1136
+ this._renderer.setAttribute(input, 'type', 'file');
1137
+ this._renderer.setAttribute(input, 'accept', options.accept);
1138
+ this._renderer.setAttribute(input, 'id', 'turbogui-file-browser-input-hidden-dialog');
1139
+ if (options.multiple) {
1140
+ this._renderer.setAttribute(input, 'multiple', 'true');
1141
+ }
1142
+ this._renderer.setStyle(input, 'display', 'none');
1143
+ this._renderer.appendChild(document.body, input);
1144
+ try {
1145
+ const files = await new Promise((resolve) => {
1146
+ const onFocus = () => {
1147
+ setTimeout(() => {
1148
+ if (!input.files || input.files.length === 0) {
1149
+ resolve(null);
1150
+ }
1151
+ }, 600);
1152
+ };
1153
+ const onChange = (event) => {
1154
+ const fileList = event.target.files;
1155
+ resolve(fileList ? Array.from(fileList) : null);
1156
+ };
1157
+ this._renderer.listen(input, 'change', onChange);
1158
+ window.addEventListener('focus', onFocus, { once: true });
1159
+ input.click();
1160
+ });
1161
+ if (!files || files.length === 0) {
1162
+ return null;
1163
+ }
1164
+ let totalSize = 0;
1165
+ for (const file of files) {
1166
+ if (options.maxFileSize !== undefined && file.size > options.maxFileSize) {
1167
+ throw new Error(`Max file size exceeded: "${file.name}" exceeds ${options.maxFileSize} bytes`);
1168
+ }
1169
+ totalSize += file.size;
1170
+ }
1171
+ if (options.maxTotalSize !== undefined && totalSize > options.maxTotalSize) {
1172
+ throw new Error(`Max total size exceeded: ${options.maxTotalSize} bytes`);
1173
+ }
1174
+ if (!options.loadData || options.loadData === 'no') {
1175
+ return files;
1176
+ }
1177
+ const fileReadPromises = files.map(async (file) => {
1178
+ switch (options.loadData) {
1179
+ case 'ArrayBuffer':
1180
+ file.data = await file.arrayBuffer();
1181
+ break;
1182
+ case 'text':
1183
+ file.data = await file.text();
1184
+ break;
1185
+ case 'base64':
1186
+ file.data = await new Promise((resolve, reject) => {
1187
+ const reader = new FileReader();
1188
+ reader.onload = () => resolve(reader.result.split(',')[1]);
1189
+ reader.onerror = () => reject(reader.error ?? new Error('Unknown FileReader error'));
1190
+ reader.readAsDataURL(file);
1191
+ });
1192
+ break;
1193
+ }
1194
+ return file;
1195
+ });
1196
+ return await Promise.all(fileReadPromises);
1197
+ }
1198
+ finally {
1199
+ this._renderer.removeChild(document.body, input);
1200
+ }
1201
+ }
1078
1202
  /**
1079
1203
  * Show a dialog with an iframe inside it, to show external web pages or web applications.
1080
1204
  *
@@ -2833,11 +2957,11 @@ class DialogSingleSelectionListComponent extends DialogBaseComponent {
2833
2957
  }
2834
2958
  }
2835
2959
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogSingleSelectionListComponent, deps: [{ token: i0.ElementRef }, { token: i1.MatDialogRef }, { token: BrowserService }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component }); }
2836
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.5", type: DialogSingleSelectionListComponent, isStandalone: true, selector: "tg-dialog-single-selection-list", providers: [], usesInheritance: true, ngImport: i0, template: "<h3>\r\n {{data.texts[0]}}\r\n</h3>\r\n\r\n<!-- Here goes the dialog subtitle. Leave it blank if you don't need it -->\r\n<p *ngIf=\"data.texts.length > 1 &amp;&amp; !stringUtils.isEmpty(data.texts[1])\">\r\n {{data.texts[1]}}\r\n</p>\r\n\r\n<mat-form-field *ngIf=\"data.texts.length > 2 &amp;&amp; !stringUtils.isEmpty(data.texts[2])\"\r\n class=\"searchItemInputContainer\">\r\n \r\n <mat-label>{{data.texts[2]}}</mat-label>\r\n <input matInput autoFocusOnDisplay\r\n (keyup.enter)=\"onIntroKeyPress()\"\r\n (input)=\"onSearchChange($event.target)\">\r\n \r\n</mat-form-field>\r\n\r\n<!-- here goes the list of elements that will be shown to the user -->\r\n<div class=\"listItemsContainer\"\r\n [style.max-height]=\"getListItemsContainerMaxheight()\">\r\n\r\n <div class=\"listItemContainer\"\r\n [style.background-color]=\"selectedItemIndex === i ? '#90d1ffad' : (i % 2 === 0 ? 'initial' : '#00000009')\"\r\n *ngFor=\"let item of filteredOptions; let i = index; trackBy: trackByFn\"\r\n (click)=\"data.texts.length < 4 ? closeDialog(i) : selectedItemIndex = i\">\r\n \r\n <p *ngIf=\"item !== ''\">\r\n {{item}}\r\n </p>\r\n \r\n </div>\r\n\r\n</div>\r\n\r\n<button mat-raised-button color=\"primary\"\r\n [disabled]=\"selectedItemIndex < 0\"\r\n (click)=\"closeDialog(selectedItemIndex)\"\r\n *ngIf=\"data.texts.length > 3\">\r\n\r\n {{data.texts[3]}}\r\n \r\n</button>", styles: [":host{min-height:300px}h3{margin-top:0;margin-bottom:10px}p{margin-top:0;margin-bottom:5px}.searchItemInputContainer{width:100%;margin-top:0;margin-bottom:0}.listItemsContainer{overflow-y:auto;border:1px solid rgba(0,0,0,.2);border-radius:4px;margin-bottom:5px}.listItemContainer p{line-height:36px;width:calc(100% - 12px);margin:0 0 0 6px;cursor:pointer}button{float:right}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: TurboGuiAngularModule }, { kind: "directive", type: AutoFocusOnDisplayDirective, selector: "[autoFocusOnDisplay]" }] }); }
2960
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.5", type: DialogSingleSelectionListComponent, isStandalone: true, selector: "tg-dialog-single-selection-list", providers: [], usesInheritance: true, ngImport: i0, template: "<h3>\r\n {{data.texts[0]}}\r\n</h3>\r\n\r\n<!-- Here goes the dialog subtitle. Leave it blank if you don't need it -->\r\n<p *ngIf=\"data.texts.length > 1 &amp;&amp; !stringUtils.isEmpty(data.texts[1])\">\r\n {{data.texts[1]}}\r\n</p>\r\n\r\n<mat-form-field *ngIf=\"data.texts.length > 2 &amp;&amp; !stringUtils.isEmpty(data.texts[2])\"\r\n class=\"searchItemInputContainer\">\r\n \r\n <mat-label>{{data.texts[2]}}</mat-label>\r\n <input matInput autoFocusOnDisplay\r\n (keyup.enter)=\"onIntroKeyPress()\"\r\n (input)=\"onSearchChange($event.target)\">\r\n \r\n</mat-form-field>\r\n\r\n<!-- here goes the list of elements that will be shown to the user -->\r\n<div class=\"listItemsContainer\"\r\n [style.max-height]=\"getListItemsContainerMaxheight()\">\r\n\r\n <div class=\"listItemContainer\"\r\n [style.background-color]=\"selectedItemIndex === i ? '#90d1ffad' : (i % 2 === 0 ? 'initial' : '#00000009')\"\r\n *ngFor=\"let item of filteredOptions; let i = index; trackBy: trackByFn\"\r\n (click)=\"data.texts.length < 4 ? closeDialog(i) : selectedItemIndex = i\">\r\n \r\n <p *ngIf=\"item !== ''\">\r\n {{item}}\r\n </p>\r\n \r\n </div>\r\n\r\n</div>\r\n\r\n<button mat-raised-button color=\"primary\"\r\n [disabled]=\"selectedItemIndex < 0\"\r\n (click)=\"closeDialog(selectedItemIndex)\"\r\n *ngIf=\"data.texts.length > 3\">\r\n\r\n {{data.texts[3]}}\r\n \r\n</button>", styles: [":host{min-height:300px}h3{margin-top:0;margin-bottom:10px}p{margin-top:0;margin-bottom:5px}.searchItemInputContainer{width:100%;margin-top:0;margin-bottom:0}.listItemsContainer{overflow-y:auto;border:1px solid rgba(0,0,0,.2);border-radius:4px;margin-bottom:5px;cursor:pointer}.listItemContainer p{line-height:36px;width:calc(100% - 12px);margin:0 0 0 6px}button{float:right}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "ngmodule", type: TurboGuiAngularModule }, { kind: "directive", type: AutoFocusOnDisplayDirective, selector: "[autoFocusOnDisplay]" }] }); }
2837
2961
  }
2838
2962
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DialogSingleSelectionListComponent, decorators: [{
2839
2963
  type: Component,
2840
- args: [{ selector: 'tg-dialog-single-selection-list', imports: [CommonModule, MatInputModule, MatFormFieldModule, TurboGuiAngularModule], providers: [], template: "<h3>\r\n {{data.texts[0]}}\r\n</h3>\r\n\r\n<!-- Here goes the dialog subtitle. Leave it blank if you don't need it -->\r\n<p *ngIf=\"data.texts.length > 1 &amp;&amp; !stringUtils.isEmpty(data.texts[1])\">\r\n {{data.texts[1]}}\r\n</p>\r\n\r\n<mat-form-field *ngIf=\"data.texts.length > 2 &amp;&amp; !stringUtils.isEmpty(data.texts[2])\"\r\n class=\"searchItemInputContainer\">\r\n \r\n <mat-label>{{data.texts[2]}}</mat-label>\r\n <input matInput autoFocusOnDisplay\r\n (keyup.enter)=\"onIntroKeyPress()\"\r\n (input)=\"onSearchChange($event.target)\">\r\n \r\n</mat-form-field>\r\n\r\n<!-- here goes the list of elements that will be shown to the user -->\r\n<div class=\"listItemsContainer\"\r\n [style.max-height]=\"getListItemsContainerMaxheight()\">\r\n\r\n <div class=\"listItemContainer\"\r\n [style.background-color]=\"selectedItemIndex === i ? '#90d1ffad' : (i % 2 === 0 ? 'initial' : '#00000009')\"\r\n *ngFor=\"let item of filteredOptions; let i = index; trackBy: trackByFn\"\r\n (click)=\"data.texts.length < 4 ? closeDialog(i) : selectedItemIndex = i\">\r\n \r\n <p *ngIf=\"item !== ''\">\r\n {{item}}\r\n </p>\r\n \r\n </div>\r\n\r\n</div>\r\n\r\n<button mat-raised-button color=\"primary\"\r\n [disabled]=\"selectedItemIndex < 0\"\r\n (click)=\"closeDialog(selectedItemIndex)\"\r\n *ngIf=\"data.texts.length > 3\">\r\n\r\n {{data.texts[3]}}\r\n \r\n</button>", styles: [":host{min-height:300px}h3{margin-top:0;margin-bottom:10px}p{margin-top:0;margin-bottom:5px}.searchItemInputContainer{width:100%;margin-top:0;margin-bottom:0}.listItemsContainer{overflow-y:auto;border:1px solid rgba(0,0,0,.2);border-radius:4px;margin-bottom:5px}.listItemContainer p{line-height:36px;width:calc(100% - 12px);margin:0 0 0 6px;cursor:pointer}button{float:right}\n"] }]
2964
+ args: [{ selector: 'tg-dialog-single-selection-list', imports: [CommonModule, MatInputModule, MatFormFieldModule, TurboGuiAngularModule], providers: [], template: "<h3>\r\n {{data.texts[0]}}\r\n</h3>\r\n\r\n<!-- Here goes the dialog subtitle. Leave it blank if you don't need it -->\r\n<p *ngIf=\"data.texts.length > 1 &amp;&amp; !stringUtils.isEmpty(data.texts[1])\">\r\n {{data.texts[1]}}\r\n</p>\r\n\r\n<mat-form-field *ngIf=\"data.texts.length > 2 &amp;&amp; !stringUtils.isEmpty(data.texts[2])\"\r\n class=\"searchItemInputContainer\">\r\n \r\n <mat-label>{{data.texts[2]}}</mat-label>\r\n <input matInput autoFocusOnDisplay\r\n (keyup.enter)=\"onIntroKeyPress()\"\r\n (input)=\"onSearchChange($event.target)\">\r\n \r\n</mat-form-field>\r\n\r\n<!-- here goes the list of elements that will be shown to the user -->\r\n<div class=\"listItemsContainer\"\r\n [style.max-height]=\"getListItemsContainerMaxheight()\">\r\n\r\n <div class=\"listItemContainer\"\r\n [style.background-color]=\"selectedItemIndex === i ? '#90d1ffad' : (i % 2 === 0 ? 'initial' : '#00000009')\"\r\n *ngFor=\"let item of filteredOptions; let i = index; trackBy: trackByFn\"\r\n (click)=\"data.texts.length < 4 ? closeDialog(i) : selectedItemIndex = i\">\r\n \r\n <p *ngIf=\"item !== ''\">\r\n {{item}}\r\n </p>\r\n \r\n </div>\r\n\r\n</div>\r\n\r\n<button mat-raised-button color=\"primary\"\r\n [disabled]=\"selectedItemIndex < 0\"\r\n (click)=\"closeDialog(selectedItemIndex)\"\r\n *ngIf=\"data.texts.length > 3\">\r\n\r\n {{data.texts[3]}}\r\n \r\n</button>", styles: [":host{min-height:300px}h3{margin-top:0;margin-bottom:10px}p{margin-top:0;margin-bottom:5px}.searchItemInputContainer{width:100%;margin-top:0;margin-bottom:0}.listItemsContainer{overflow-y:auto;border:1px solid rgba(0,0,0,.2);border-radius:4px;margin-bottom:5px;cursor:pointer}.listItemContainer p{line-height:36px;width:calc(100% - 12px);margin:0 0 0 6px}button{float:right}\n"] }]
2841
2965
  }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.MatDialogRef }, { type: BrowserService }, { type: undefined, decorators: [{
2842
2966
  type: Inject,
2843
2967
  args: [MAT_DIALOG_DATA]
@@ -3343,11 +3467,11 @@ class ValidatorsPlus extends Validators {
3343
3467
  }
3344
3468
  // first check if the control has a value
3345
3469
  if (control.value && control.value.length > 0) {
3346
- if (!StringUtils.isEmpty(control.value)) {
3347
- return null;
3470
+ if (StringUtils.isEmpty(control.value)) {
3471
+ return { isEmpty: true };
3348
3472
  }
3349
3473
  else {
3350
- return { isEmpty: true };
3474
+ return null;
3351
3475
  }
3352
3476
  }
3353
3477
  else {
@@ -3376,6 +3500,34 @@ class ValidatorsPlus extends Validators {
3376
3500
  return hasValue ? null : { atLeastOneRequired: true };
3377
3501
  };
3378
3502
  }
3503
+ /**
3504
+ * Validator to check that a control value is a valid mobile phone number with country code.
3505
+ *
3506
+ * The expected format is: +<country code><number>, where:
3507
+ * - <country code> is 1 to 3 digits
3508
+ * - <number> is 6 to 14 digits
3509
+ *
3510
+ * The country code must be prefixed with a plus sign (+). Spaces, dots (.), and hyphens (-) are allowed as separators.
3511
+ *
3512
+ * Examples of valid formats:
3513
+ * - +1 1234567890
3514
+ * - +44-1234-567890
3515
+ * - +91.9876543210
3516
+ *
3517
+ * Examples of invalid formats:
3518
+ * - 1234567890 (missing country code)
3519
+ * - +123 (too short)
3520
+ * - +123456789012345671 (too long)
3521
+ *
3522
+ * @param control The form control to validate.
3523
+ *
3524
+ * @returns An object with the validation error if invalid, or null if valid.
3525
+ */
3526
+ static mobilePhoneWithCountryCode(control) {
3527
+ const mobilePhoneRegex = /^\+\d{1,3}[\s.-]?\d{6,14}$/;
3528
+ const valid = mobilePhoneRegex.test(control.value);
3529
+ return valid ? null : { mobilePhoneWithCountryCode: true };
3530
+ }
3379
3531
  }
3380
3532
 
3381
3533
  /*