@yomotech/view-toolkit 1.0.19 → 1.0.28
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.
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as i1$1 from '@angular/common';
|
|
2
2
|
import { CommonModule, NgIf } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { EventEmitter, HostListener, Output, Input, Component, signal, Injectable, Directive, inject, Pipe, ElementRef, NgZone, ViewChild, HostBinding, NgModule, effect, DestroyRef } from '@angular/core';
|
|
4
|
+
import { EventEmitter, HostListener, Output, Input, Component, signal, Injectable, Directive, inject, Pipe, ElementRef, NgZone, ViewChild, forwardRef, HostBinding, NgModule, effect, DestroyRef } from '@angular/core';
|
|
5
5
|
import * as i2 from '@angular/forms';
|
|
6
|
-
import { FormsModule } from '@angular/forms';
|
|
7
|
-
import * as i1$
|
|
6
|
+
import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
|
|
7
|
+
import * as i1$2 from '@angular/common/http';
|
|
8
8
|
import { HttpHeaders, HttpParams } from '@angular/common/http';
|
|
9
9
|
import { Capacitor, CapacitorHttp } from '@capacitor/core';
|
|
10
10
|
import * as i11 from 'ng-zorro-antd/icon';
|
|
@@ -13,7 +13,7 @@ import * as i13 from 'ng-zorro-antd/button';
|
|
|
13
13
|
import { NzButtonModule } from 'ng-zorro-antd/button';
|
|
14
14
|
import * as i4 from 'ng-zorro-antd/date-picker';
|
|
15
15
|
import { NzDatePickerModule } from 'ng-zorro-antd/date-picker';
|
|
16
|
-
import * as i5
|
|
16
|
+
import * as i5 from 'ng-zorro-antd/modal';
|
|
17
17
|
import { NzModalModule } from 'ng-zorro-antd/modal';
|
|
18
18
|
import * as i12 from 'primeng/datepicker';
|
|
19
19
|
import { DatePickerModule } from 'primeng/datepicker';
|
|
@@ -31,7 +31,7 @@ import * as i3$1 from 'primeng/inputtext';
|
|
|
31
31
|
import { InputTextModule } from 'primeng/inputtext';
|
|
32
32
|
import * as i18 from 'primeng/autocomplete';
|
|
33
33
|
import { AutoCompleteModule } from 'primeng/autocomplete';
|
|
34
|
-
import * as i1$
|
|
34
|
+
import * as i1$3 from 'ngx-toastr';
|
|
35
35
|
import * as i7 from 'angular-oauth2-oidc';
|
|
36
36
|
import * as i14 from 'ng-zorro-antd/core/transition-patch';
|
|
37
37
|
import * as i15 from 'ng-zorro-antd/core/wave';
|
|
@@ -47,12 +47,12 @@ import * as i11$1 from 'ng-zorro-antd/menu';
|
|
|
47
47
|
import { NzMenuModule } from 'ng-zorro-antd/menu';
|
|
48
48
|
import * as i4$1 from '@okta/okta-angular';
|
|
49
49
|
import { OktaAuthStateService, OKTA_AUTH } from '@okta/okta-angular';
|
|
50
|
-
import * as i1$
|
|
50
|
+
import * as i1$4 from 'primeng/dynamicdialog';
|
|
51
51
|
import { DynamicDialogModule } from 'primeng/dynamicdialog';
|
|
52
52
|
import { SelectModule } from 'primeng/select';
|
|
53
53
|
import { Device } from '@capacitor/device';
|
|
54
54
|
import { jwtDecode } from 'jwt-decode';
|
|
55
|
-
import * as i1$
|
|
55
|
+
import * as i1$5 from 'ng-zorro-antd/i18n';
|
|
56
56
|
import { de_DE, en_US } from 'ng-zorro-antd/i18n';
|
|
57
57
|
import * as i13$1 from 'ng-zorro-antd/tooltip';
|
|
58
58
|
import { NzTooltipModule } from 'ng-zorro-antd/tooltip';
|
|
@@ -290,7 +290,7 @@ class PaginationComponent {
|
|
|
290
290
|
this.onGoToPage.emit(page);
|
|
291
291
|
}
|
|
292
292
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: PaginationComponent, deps: [{ token: i1.Platform }], target: i0.ɵɵFactoryTarget.Component });
|
|
293
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: PaginationComponent, isStandalone: true, selector: "vl-pagination", inputs: { itemInfo: "itemInfo", pageSize: "pageSize", currentPage: "currentPage", selectedLanguageInfo: "selectedLanguageInfo", pages: "pages" }, outputs: { pageSizeChange: "pageSizeChange", onGoToPage: "onGoToPage" }, host: { listeners: { "window:resize": "onWindowResize()" } }, ngImport: i0, template: "<div [ngStyle]=\"paginationStyle\">\r\n\r\n <ul [ngStyle]=\"listStyle\" [style.z-index]=\"'auto'\">\r\n\r\n <!-- Existing desktop pagination: unchanged -->\r\n <ng-container *ngIf=\"!isMobile\">\r\n\r\n <li (click)=\"invokeGoToPage(0)\" [ngStyle]=\"getPageItemStyle(undefined, currentPage === 0)\">\r\n \u00AB\u00AB\r\n </li>\r\n\r\n <li (click)=\"invokeGoToPage(currentPage - 1)\" [ngStyle]=\"getPageItemStyle(undefined, currentPage === 0)\">\r\n \u00AB\r\n </li>\r\n\r\n <li *ngFor=\"let page of pages\" (click)=\"invokeGoToPage(page)\" [ngStyle]=\"getPageItemStyle(page)\">\r\n {{ page + 1 }}\r\n </li>\r\n\r\n <li (click)=\"invokeGoToPage(currentPage + 1)\" [ngStyle]=\"\r\n getPageItemStyle(\r\n undefined,\r\n currentPage === pages.length - 1\r\n )\r\n \">\r\n \u00BB\r\n </li>\r\n\r\n <li (click)=\"invokeGoToPage(pages.length - 1)\" [ngStyle]=\"\r\n getPageItemStyle(\r\n undefined,\r\n currentPage === pages.length - 1\r\n )\r\n \">\r\n \u00BB\u00BB\r\n </li>\r\n\r\n </ng-container>\r\n\r\n <!-- Compact mobile pagination -->\r\n <ng-container *ngIf=\"isMobile\">\r\n\r\n <!-- First -->\r\n <li (click)=\"invokeGoToPage(0)\" [ngStyle]=\"getPageItemStyle(undefined, isFirstPage)\">\r\n \u00AB\u00AB\r\n </li>\r\n\r\n <!-- Previous -->\r\n <li (click)=\"invokeGoToPage(currentPage - 1)\" [ngStyle]=\"getPageItemStyle(undefined, isFirstPage)\">\r\n \u00AB\r\n </li>\r\n\r\n <!-- Current page / total pages -->\r\n <li [ngStyle]=\"pageSummaryStyle\">\r\n {{ currentPage + 1 }} / {{ pages.length }}\r\n </li>\r\n\r\n <!-- Next -->\r\n <li (click)=\"invokeGoToPage(currentPage + 1)\" [ngStyle]=\"getPageItemStyle(undefined, isLastPage)\">\r\n \u00BB\r\n </li>\r\n\r\n <!-- Last -->\r\n <li (click)=\"invokeGoToPage(pages.length - 1)\" [ngStyle]=\"getPageItemStyle(undefined, isLastPage)\">\r\n \u00BB\u00BB\r\n </li>\r\n\r\n </ng-container>\r\n\r\n </ul>\r\n\r\n <div *ngIf=\"itemInfo?.layoutSchema?.pagination?.pageSize\" [ngStyle]=\"sizeControlStyle\">\r\n <label for=\"pageSize\" [ngStyle]=\"labelStyle\">\r\n {{ getPaginationCaption() }}\r\n </label>\r\n\r\n <select id=\"pageSize\" [(ngModel)]=\"pageSize\" (change)=\"invokeOnPageSizeChange(pageSize)\"\r\n [ngStyle]=\"selectStyle\">\r\n <option *ngFor=\"\r\n let size of\r\n itemInfo?.layoutSchema\r\n ?.pagination?.pageSizeOptions ||\r\n [5, 10, 15, 20]\r\n \" [value]=\"size\">\r\n {{ size }}\r\n </option>\r\n </select>\r\n </div>\r\n\r\n</div>", styles: [":host{display:block;width:100%;min-width:0;box-sizing:border-box}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type:
|
|
293
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: PaginationComponent, isStandalone: true, selector: "vl-pagination", inputs: { itemInfo: "itemInfo", pageSize: "pageSize", currentPage: "currentPage", selectedLanguageInfo: "selectedLanguageInfo", pages: "pages" }, outputs: { pageSizeChange: "pageSizeChange", onGoToPage: "onGoToPage" }, host: { listeners: { "window:resize": "onWindowResize()" } }, ngImport: i0, template: "<div [ngStyle]=\"paginationStyle\">\r\n\r\n <ul [ngStyle]=\"listStyle\" [style.z-index]=\"'auto'\">\r\n\r\n <!-- Existing desktop pagination: unchanged -->\r\n <ng-container *ngIf=\"!isMobile\">\r\n\r\n <li (click)=\"invokeGoToPage(0)\" [ngStyle]=\"getPageItemStyle(undefined, currentPage === 0)\">\r\n \u00AB\u00AB\r\n </li>\r\n\r\n <li (click)=\"invokeGoToPage(currentPage - 1)\" [ngStyle]=\"getPageItemStyle(undefined, currentPage === 0)\">\r\n \u00AB\r\n </li>\r\n\r\n <li *ngFor=\"let page of pages\" (click)=\"invokeGoToPage(page)\" [ngStyle]=\"getPageItemStyle(page)\">\r\n {{ page + 1 }}\r\n </li>\r\n\r\n <li (click)=\"invokeGoToPage(currentPage + 1)\" [ngStyle]=\"\r\n getPageItemStyle(\r\n undefined,\r\n currentPage === pages.length - 1\r\n )\r\n \">\r\n \u00BB\r\n </li>\r\n\r\n <li (click)=\"invokeGoToPage(pages.length - 1)\" [ngStyle]=\"\r\n getPageItemStyle(\r\n undefined,\r\n currentPage === pages.length - 1\r\n )\r\n \">\r\n \u00BB\u00BB\r\n </li>\r\n\r\n </ng-container>\r\n\r\n <!-- Compact mobile pagination -->\r\n <ng-container *ngIf=\"isMobile\">\r\n\r\n <!-- First -->\r\n <li (click)=\"invokeGoToPage(0)\" [ngStyle]=\"getPageItemStyle(undefined, isFirstPage)\">\r\n \u00AB\u00AB\r\n </li>\r\n\r\n <!-- Previous -->\r\n <li (click)=\"invokeGoToPage(currentPage - 1)\" [ngStyle]=\"getPageItemStyle(undefined, isFirstPage)\">\r\n \u00AB\r\n </li>\r\n\r\n <!-- Current page / total pages -->\r\n <li [ngStyle]=\"pageSummaryStyle\">\r\n {{ currentPage + 1 }} / {{ pages.length }}\r\n </li>\r\n\r\n <!-- Next -->\r\n <li (click)=\"invokeGoToPage(currentPage + 1)\" [ngStyle]=\"getPageItemStyle(undefined, isLastPage)\">\r\n \u00BB\r\n </li>\r\n\r\n <!-- Last -->\r\n <li (click)=\"invokeGoToPage(pages.length - 1)\" [ngStyle]=\"getPageItemStyle(undefined, isLastPage)\">\r\n \u00BB\u00BB\r\n </li>\r\n\r\n </ng-container>\r\n\r\n </ul>\r\n\r\n <div *ngIf=\"itemInfo?.layoutSchema?.pagination?.pageSize\" [ngStyle]=\"sizeControlStyle\">\r\n <label for=\"pageSize\" [ngStyle]=\"labelStyle\">\r\n {{ getPaginationCaption() }}\r\n </label>\r\n\r\n <select id=\"pageSize\" [(ngModel)]=\"pageSize\" (change)=\"invokeOnPageSizeChange(pageSize)\"\r\n [ngStyle]=\"selectStyle\">\r\n <option *ngFor=\"\r\n let size of\r\n itemInfo?.layoutSchema\r\n ?.pagination?.pageSizeOptions ||\r\n [5, 10, 15, 20]\r\n \" [value]=\"size\">\r\n {{ size }}\r\n </option>\r\n </select>\r\n </div>\r\n\r\n</div>", styles: [":host{display:block;width:100%;min-width:0;box-sizing:border-box}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PlatformModule }] });
|
|
294
294
|
}
|
|
295
295
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: PaginationComponent, decorators: [{
|
|
296
296
|
type: Component,
|
|
@@ -1689,7 +1689,7 @@ class RequestService {
|
|
|
1689
1689
|
params
|
|
1690
1690
|
});
|
|
1691
1691
|
}
|
|
1692
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: RequestService, deps: [{ token: i1$
|
|
1692
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: RequestService, deps: [{ token: i1$2.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1693
1693
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: RequestService, providedIn: 'root' });
|
|
1694
1694
|
}
|
|
1695
1695
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: RequestService, decorators: [{
|
|
@@ -1697,7 +1697,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
1697
1697
|
args: [{
|
|
1698
1698
|
providedIn: 'root'
|
|
1699
1699
|
}]
|
|
1700
|
-
}], ctorParameters: () => [{ type: i1$
|
|
1700
|
+
}], ctorParameters: () => [{ type: i1$2.HttpClient }] });
|
|
1701
1701
|
|
|
1702
1702
|
class UtilityService {
|
|
1703
1703
|
platform;
|
|
@@ -1975,11 +1975,16 @@ class UtilityService {
|
|
|
1975
1975
|
authorizationBaseUrl: environment.login.mobile.authorizationBaseUrl,
|
|
1976
1976
|
accessTokenEndpoint: environment.login.mobile.accessTokenEndpoint,
|
|
1977
1977
|
redirectUrl: environment.login.mobile.redirectUrl,
|
|
1978
|
-
scope: environment.login.mobile.scope,
|
|
1978
|
+
scope: this.oauthScope(environment.login.mobile.scope),
|
|
1979
1979
|
responseType: environment.login.mobile.responseType,
|
|
1980
1980
|
pkceEnabled: environment.login.mobile.pkceEnabled
|
|
1981
1981
|
};
|
|
1982
1982
|
}
|
|
1983
|
+
/** OAuth scope as the space separated string the OAuth2 plugin expects. */
|
|
1984
|
+
oauthScope(scope) {
|
|
1985
|
+
const value = Array.isArray(scope) ? scope.join(' ') : scope;
|
|
1986
|
+
return value?.trim() || 'openid profile email';
|
|
1987
|
+
}
|
|
1983
1988
|
async setupStripePaymentForm(environment, form, onCardChange) {
|
|
1984
1989
|
let hasCreditCard = false;
|
|
1985
1990
|
if (form.layoutSchema?.formSections) {
|
|
@@ -2825,7 +2830,7 @@ class NotificationService {
|
|
|
2825
2830
|
error(error, time = 20000, message) {
|
|
2826
2831
|
this.toastr.error(error, message, { messageClass: 'toaster-font', titleClass: 'toaster-font', closeButton: true, timeOut: time });
|
|
2827
2832
|
}
|
|
2828
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: NotificationService, deps: [{ token: i1$
|
|
2833
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: NotificationService, deps: [{ token: i1$3.ToastrService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2829
2834
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: NotificationService, providedIn: 'root' });
|
|
2830
2835
|
}
|
|
2831
2836
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: NotificationService, decorators: [{
|
|
@@ -2833,7 +2838,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
2833
2838
|
args: [{
|
|
2834
2839
|
providedIn: 'root'
|
|
2835
2840
|
}]
|
|
2836
|
-
}], ctorParameters: () => [{ type: i1$
|
|
2841
|
+
}], ctorParameters: () => [{ type: i1$3.ToastrService }] });
|
|
2837
2842
|
|
|
2838
2843
|
// Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13
|
|
2839
2844
|
// - new: RequestTimeoutService / stagedRequest, one waiting rule for every request of the runtime
|
|
@@ -3536,6 +3541,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
3536
3541
|
}]
|
|
3537
3542
|
}], ctorParameters: () => [{ type: Couchdb }, { type: UtilityService }, { type: i1.Platform }] });
|
|
3538
3543
|
|
|
3544
|
+
// Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-14
|
|
3545
|
+
// - fix: white cards on the luv2shop start page (signed out / after logout): the untranslated
|
|
3546
|
+
// layout copy was taken while the layout was still empty, and the language pass then wrote the
|
|
3547
|
+
// empty copy over the real layout. The copy is renewed once the layout has content
|
|
3548
|
+
// - fix: a failed save of a time recording in the web (webSaveDb) left the loader turning without
|
|
3549
|
+
// any message; it now stops the loader and shows one. (The CORS cause - P5 sends no CORS
|
|
3550
|
+
// headers - is solved by the backend proxy, shuaib#162/#154)
|
|
3551
|
+
// Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13
|
|
3552
|
+
// - fix: getHorizontalLayoutCaption read layout.label.caption; a horizontal layout without label
|
|
3553
|
+
// threw in the template and the cards stayed half drawn (luv2shop start page: no texts)
|
|
3539
3554
|
// Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13
|
|
3540
3555
|
// - new: fields with a backend source (option.autocomplete) load their list through
|
|
3541
3556
|
// RemoteListService - autocomplete suggestions, dropdown/combobox items; the autocompletes of this
|
|
@@ -3672,13 +3687,24 @@ class CardComponent {
|
|
|
3672
3687
|
if (isNativePhone()) {
|
|
3673
3688
|
this.mobileDisplay = true;
|
|
3674
3689
|
}
|
|
3675
|
-
|
|
3690
|
+
/*
|
|
3691
|
+
* The untranslated copies the language is applied to. The app fills layoutSchema, header and
|
|
3692
|
+
* footer into the same itemInfo after it arrived (empty) - so a copy taken then stayed empty,
|
|
3693
|
+
* and the next language pass wrote that empty copy over the real layout: white cards (luv2shop
|
|
3694
|
+
* start page). A copy is therefore also taken while it is still empty and the source is not.
|
|
3695
|
+
*/
|
|
3696
|
+
const hasContent = (value) => !!value && Object.keys(value).length > 0;
|
|
3697
|
+
if (changes['itemInfo'] || (!hasContent(this.layoutSchemaCopy) && hasContent(this.itemInfo?.layoutSchema))) {
|
|
3676
3698
|
if (this.itemInfo.layoutSchema) {
|
|
3677
3699
|
this.layoutSchemaCopy = JSON.parse(JSON.stringify(this.itemInfo.layoutSchema));
|
|
3678
3700
|
}
|
|
3701
|
+
}
|
|
3702
|
+
if (changes['itemInfo'] || (!hasContent(this.headerSchemaCopy) && hasContent(this.itemInfo?.header))) {
|
|
3679
3703
|
if (this.itemInfo.header) {
|
|
3680
3704
|
this.headerSchemaCopy = JSON.parse(JSON.stringify(this.itemInfo.header));
|
|
3681
3705
|
}
|
|
3706
|
+
}
|
|
3707
|
+
if (changes['itemInfo'] || (!hasContent(this.footerSchemaCopy) && hasContent(this.itemInfo?.footer))) {
|
|
3682
3708
|
if (this.itemInfo.footer) {
|
|
3683
3709
|
this.footerSchemaCopy = JSON.parse(JSON.stringify(this.itemInfo.footer));
|
|
3684
3710
|
}
|
|
@@ -3819,7 +3845,7 @@ class CardComponent {
|
|
|
3819
3845
|
}
|
|
3820
3846
|
getHorizontalLayoutCaption(layout) {
|
|
3821
3847
|
let lang = this.selectedLanguageInfo?.selectedLanguage;
|
|
3822
|
-
let caption = layout
|
|
3848
|
+
let caption = layout?.label?.caption
|
|
3823
3849
|
? layout.label.caption
|
|
3824
3850
|
: this.itemInfo.propertySchema[this.getScope(layout.scope?.reference)]?.[layout.scope?.reference.split('/').pop()]?.caption;
|
|
3825
3851
|
return lang && lang[caption] ? lang[caption] : caption;
|
|
@@ -4155,6 +4181,12 @@ class CardComponent {
|
|
|
4155
4181
|
else {
|
|
4156
4182
|
this.deleteAllRecord();
|
|
4157
4183
|
}
|
|
4184
|
+
},
|
|
4185
|
+
error: (error) => {
|
|
4186
|
+
// The loader kept turning and the record stayed without any message.
|
|
4187
|
+
this.safeUpdate(() => this.isLoading = false);
|
|
4188
|
+
console.error('Failed to save time recording', error);
|
|
4189
|
+
this.notificationService.error(this.selectedLanguageInfo?.selectedLanguage?.['saveFailed'] ?? 'The record could not be saved.');
|
|
4158
4190
|
}
|
|
4159
4191
|
});
|
|
4160
4192
|
}
|
|
@@ -4319,8 +4351,79 @@ class CardComponent {
|
|
|
4319
4351
|
}
|
|
4320
4352
|
return scope.substring(start, end);
|
|
4321
4353
|
}
|
|
4322
|
-
|
|
4323
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.2", type: CardComponent, isStandalone: true, selector: "vl-card", inputs: { itemInfo: "itemInfo", currentPage: "currentPage", pageSize: "pageSize", selectedLanguageInfo: "selectedLanguageInfo", mobileDisplay: "mobileDisplay", scrollPagination: "scrollPagination", pages: "pages", paginationMode: "paginationMode", paginationLoading: "paginationLoading" }, outputs: { cardButtonClick: "cardButtonClick", cardHeaderButtonClick: "cardHeaderButtonClick", linkDetails: "linkDetails", imageDetails: "imageDetails", onPageSizeChange: "onPageSizeChange", onPageChange: "onPageChange", cardDataChange: "cardDataChange" }, host: { listeners: { "window:resize": "onWindowResize()" } }, viewQueries: [{ propertyName: "scrollPaginationSentinel", first: true, predicate: ["scrollPaginationSentinel"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: autocompletes get their suggestions from RemoteListService (completeRemote) -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\r\n project defines none; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - change: autocomplete fields take the toolkit string keyboard unless option.keyboardType\r\n names another one ('native' keeps the system keyboard)\r\n - change: the date filter uses the shared picker markup (picker-field, DateTimePickerService) -->\r\n\r\n<!-- shuaib 2026-09-24\r\n - change: card field switches use renderFieldType so legacy checkbox fields render as boolean. -->\r\n\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: a @contract comment naming the types this container does not render. It carries four\r\n ngSwitch blocks, so the gap belongs to the container and not to one of them -->\r\n\r\n<!-- @contract gap: dateRange, quantity, credit card\r\n reality: a card renders string, number, image, date, time, dropdown, boolean and\r\n autocomplete. Legacy checkbox fields normalize to boolean. dropdown and boolean arrived on 2026-09-18; before that a card\r\n dropped them silently.\r\n failure: a field of a missing type renders empty, with no error.\r\n instead: quantity is grid-only by design; for a dateRange or a credit card use a modal.\r\n seen: this file carries four switch blocks (lines 23, 94, 267, 488) and none of them has\r\n a case for those three - the gap belongs to the container, not to one block -->\r\n\r\n<!-- Loader Spinner -->\r\n\r\n<div *ngIf=\"isLoading\" class=\"loader-overlay\">\r\n <div class=\"spinner\"></div>\r\n</div>\r\n<div class=\"vl-card-runtime\">\r\n <!-- top pagination on card -->\r\n <div *ngIf=\"\r\n itemInfo?.layoutSchema?.pagination?.visible &&\r\n itemInfo?.layoutSchema?.pagination?.top\r\n \" class=\"card-pagination-wrapper\">\r\n <vl-pagination [itemInfo]=\"itemInfo\"\r\n [selectedLanguageInfo]=\"selectedLanguageInfo\" [pageSize]=\"pageSize\"\r\n [currentPage]=\"currentPage\" [pages]=\"pages\"\r\n (pageSizeChange)=\"invokeOnPageSizeChange($event)\"\r\n (onGoToPage)=\"invokeGoToPage($event)\">\r\n </vl-pagination>\r\n </div>\r\n <div *ngIf=\"itemInfo?.header?.elements?.length\" [ngStyle]=\"getHeaderStyle()\"\r\n [class]=\"getStyleWithoutClasses(\r\n itemInfo?.header?.style\r\n ).classes\">\r\n <div *ngFor=\"let element of itemInfo.header?.elements; let c = index\"\r\n [ngStyle]=\"getHeaderElementWrapperStyle(element)\"\r\n [class]=\"element.option?.innerAction && element.option?.innerAction === 'saveAll' && !displayedRecords?.length ? \r\n 'hidden' :getStyleWithoutClasses(element?.wrapperStyle).classes\">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(element.scope?.reference && itemInfo.propertySchema[getScope(element.scope?.reference)] ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.type : 'default')\">\r\n\r\n <!-- String Control -->\r\n <div *ngSwitchCase=\"'string'\"\r\n [class]=\"element.option?.visible ? 'card-field-wrapper' : 'hidden'\">\r\n\r\n <!-- LABEL -->\r\n <span *ngIf=\"element.label?.caption\"\r\n [style]=\"getStyleWithoutClasses(element.label?.style).style\"\r\n [class]=\"element.label?.style?.classes?.[0]\">\r\n {{ element.label.caption }}\r\n </span>\r\n </div>\r\n\r\n <!-- Date control -->\r\n <div *ngSwitchCase=\"'date'\" class=\"mb-3\">\r\n <span *ngIf=\"element.option.protected\"\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">\r\n <vl-string [formField]=\"element\"\r\n [modalItemInfo]=\"itemInfo\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!element.option.protected\"\r\n [class]=\"element?.layout == 'horizontal' ? 'horizontal-layout' : ''\"\r\n [ngClass]=\"element?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(element?.style).style\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">{{\r\n element.label?.value\r\n ?\r\n element.label?.value : element.label?.caption ?\r\n element.label?.caption :\r\n itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <div class=\"picker-field\">\r\n <p-datepicker #cardDatePicker\r\n (ngModelChange)=\"dateChangeEvent(element, $event)\"\r\n [dateFormat]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.format ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()].format : 'yy-mm-dd'\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()].value\"\r\n (onInput)=\"onDateTyping($event)\"\r\n [class]=\"element?.scope?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(element?.scope?.style).style\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\"\r\n appendTo=\"body\"\r\n [panelStyleClass]=\"picker.datePanelClass\"\r\n [motionOptions]=\"picker.dockedMotion\"\r\n [vlCustomKeyboard]=\"element?.option?.keyboardType ?? 'date'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, cardDatePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(cardDatePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\"\r\n aria-label=\"Open calendar\"\r\n (pointerdown)=\"picker.openFromIcon($event, cardDatePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-calendar\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\"\r\n [class]=\"element.option?.visible ? 'row' : 'hidden'\"\r\n (click)=\"invokeOnCardHeader(element)\">\r\n <button nz-button type=\"button\"\r\n [matTooltip]=\"element?.option?.tooltip || ''\"\r\n [matTooltipTouchGestures]=\"'auto'\"\r\n [ngStyle]=\"getStyleWithoutClasses(element.style).style\"\r\n [class]=\"element.option.protected ? 'disabled-button' : getStyleWithoutClasses(element.style).classes\">\r\n <ng-container\r\n *ngIf=\"!element.caption; else headerTextContent\">\r\n <i nz-icon [nzType]=\"element.icon\"\r\n nzTheme=\"outline\"></i>\r\n </ng-container>\r\n <ng-template #headerTextContent>{{ element.caption\r\n }}</ng-template>\r\n </button>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n <div class=\"card-responsive-grid\" [ngStyle]=\"getCardContainerStyle()\"\r\n [class]=\"getStyleWithoutClasses(\r\n itemInfo?.layoutSchema?.style\r\n ).classes\">\r\n <div *ngFor=\"let record of displayedRecords; let k = index\"\r\n class=\"card-grid-item\">\r\n <div [ngStyle]=\"getSingleCardStyle()\"\r\n [class]=\"getStyleWithoutClasses(itemInfo?.layoutSchema?.stylePerCard).classes\">\r\n <div *ngFor=\"let layout of itemInfo?.layoutSchema?.cards || []\"\r\n class=\"card-layout-item\" [ngStyle]=\"\r\n layout?.type === 'HorizontalLayout'\r\n ? {}\r\n : getStyleWithoutClasses(layout?.style).style\r\n \" [class]=\"\r\n layout?.type === 'HorizontalLayout'\r\n ? ''\r\n : getStyleWithoutClasses(layout?.style).classes\r\n \">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(layout?.scope?.reference && itemInfo.propertySchema[getScope(layout.scope?.reference)] ? itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.type : layout?.type == 'HorizontalLayout' ? 'HorizontalLayout' : 'default')\">\r\n <!-- Image Control -->\r\n <div *ngSwitchCase=\"'image'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <img\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [src]=\"layout?.option?.src ? layout?.option?.src : record[layout.scope?.reference.split('/').pop()]\"\r\n alt=\"{{ layout.label ? layout.label : getProperty(layout.scope?.reference).productSchema.caption }}\"\r\n (click)=\"showImageDetails(layout.option, record)\" />\r\n </div>\r\n\r\n <!-- String Control -->\r\n <div *ngSwitchCase=\"'string'\"\r\n [class]=\"layout.option?.visible ? 'card-field-wrapper' : 'hidden'\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(layout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(layout.label?.style).classes\">\r\n {{ getHorizontalLayoutCaption(layout) }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n @if(layout.option && layout.option.control &&\r\n !layout.option.protected){\r\n @switch (layout.option.control) {\r\n @case ('textInput') {\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ??\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ??\r\n '--'\r\n }}\r\n </p>\r\n }\r\n @case ('password') {\r\n <span>Switch not supported yet</span>\r\n\r\n }\r\n @case ('combobox') {\r\n <span>Combobox not supported yet</span>\r\n }\r\n @case('textArea'){\r\n <span>TextArea not supported yet</span>\r\n }\r\n @case ('switch') {\r\n <span>Switch not supported yet</span>\r\n }\r\n\r\n @case('radiobutton') {\r\n <span>Radiobutton not supported yet</span>\r\n }\r\n\r\n @case ('dropdown') {\r\n <select id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(layout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style)?.style\"\r\n [name]=\"layout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)][\r\n layout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[\r\n layout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[layout?.option?.optionValue]\">\r\n {{ item[layout?.option?.optionLabel] }}\r\n </option>\r\n </select>\r\n }\r\n\r\n @case ('autocomplete') {\r\n <p-autocomplete [suggestions]=\"remoteSuggestions[layout.scope?.reference] ?? []\" (completeMethod)=\"completeRemote($event, layout)\" #autocompleteField=\"ngModel\"\r\n [required]=\"layout.option.required\"\r\n [name]=\"layout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"layout?.scope?.style?.classes ? layout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"layout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: layout.scope?.reference\"\r\n [vlCustomKeyboard]=\"layout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n }\r\n }\r\n }@else {\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ??\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ??\r\n '--'\r\n }}\r\n </p>\r\n }\r\n\r\n </div>\r\n\r\n <!-- Date Control -->\r\n <div *ngSwitchCase=\"'date'\"\r\n [class]=\"layout.option?.visible ? 'card-field-wrapper' : 'hidden'\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <!-- LABEL -->\r\n <span *ngIf=\"layout?.label?.caption\"\r\n [class]=\"layout.label?.style?.classes?.[0]\">\r\n {{ layout.label.caption }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ?? itemInfo.propertySchema[\r\n getScope(layout.scope?.reference)\r\n ]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ?? '--'\r\n }}\r\n </p>\r\n </div>\r\n\r\n <!-- Time Control -->\r\n <div *ngSwitchCase=\"'time'\"\r\n [class]=\"layout.option?.visible ? 'card-field-wrapper' : 'hidden'\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span *ngIf=\"layout?.label?.caption\"\r\n [class]=\"layout.label?.style?.classes?.[0]\">\r\n {{ layout.label.caption }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ?? itemInfo.propertySchema[\r\n getScope(layout.scope?.reference)\r\n ]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ?? '--'\r\n }}\r\n </p>\r\n </div>\r\n\r\n <!-- number Control -->\r\n <div *ngSwitchCase=\"'number'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <!-- VALUE -->\r\n @if(layout.option && layout.option.control &&\r\n !layout.option.protected){\r\n @switch (layout.option.control) {\r\n @case ('textInput') {\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n {{\r\n record[layout.scope?.reference.split('/').pop()]\r\n | currency: layout.option?.currency\r\n ?\r\n layout.option.currency\r\n :\r\n 'USD' }}\r\n </h3>\r\n }\r\n @case ('slider') {\r\n <p>Slider not Implemented yet</p>\r\n\r\n }\r\n @case ('spinner') {\r\n <p>Spinner not Implemented yet</p>\r\n }\r\n }\r\n } @else{\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n {{\r\n record[layout.scope?.reference.split('/').pop()]\r\n | currency: layout.option?.currency\r\n ?\r\n layout.option.currency\r\n :\r\n 'USD' }}\r\n </h3>\r\n }\r\n\r\n </div>\r\n\r\n <!-- dropdown Control -->\r\n <div *ngSwitchCase=\"'dropdown'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <select id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(layout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style)?.style\"\r\n [name]=\"layout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)][\r\n layout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[\r\n layout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[layout?.option?.optionValue]\">\r\n {{ item[layout?.option?.optionLabel]\r\n }}\r\n </option>\r\n </select>\r\n </h3>\r\n </div>\r\n\r\n <!-- autocomplete Control -->\r\n <div *ngSwitchCase=\"'autocomplete'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <p-autocomplete [suggestions]=\"remoteSuggestions[layout.scope?.reference] ?? []\" (completeMethod)=\"completeRemote($event, layout)\" #autocompleteField=\"ngModel\"\r\n [required]=\"layout.option.required\"\r\n [name]=\"layout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"layout?.scope?.style?.classes ? layout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"layout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: layout.scope?.reference\"\r\n [vlCustomKeyboard]=\"layout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n </h3>\r\n </div>\r\n\r\n <!-- checkbox Control -->\r\n <div *ngSwitchCase=\"'boolean'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <input type=\"checkbox\"\r\n class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)][\r\n layout.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [name]=\"layout.scope?.reference\" />\r\n </h3>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <button\r\n [style]=\"getStyleWithoutClasses(layout?.style).style\"\r\n [class]=\"layout.option.protected ? 'disabled-button' : getStyleWithoutClasses(layout?.style).classes\"\r\n (click)=\"\r\n invokeButtonClick(\r\n layout,\r\n record,\r\n getRecordIndex(record)\r\n )\r\n\">\r\n <ng-container\r\n *ngIf=\"!layout.caption; else textContent\">\r\n <i nz-icon [nzType]=\"layout.icon\"\r\n nzTheme=\"outline\"></i>\r\n </ng-container>\r\n <ng-template #textContent>\r\n {{ layout.caption }}\r\n </ng-template>\r\n </button>\r\n </div>\r\n\r\n <!-- HorizontalLayout Control -->\r\n <div *ngSwitchCase=\"'HorizontalLayout'\">\r\n <div class=\"card-horizontal-layout\"\r\n [class]=\"getStyleWithoutClasses(\r\n layout?.style\r\n ).classes\" [ngStyle]=\"getHorizontalLayoutStyle(layout)\">\r\n <ng-container\r\n *ngFor=\"let innerLayout of layout.elements\">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(innerLayout.scope?.reference && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] ? itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.type : 'default')\">\r\n <!-- Image Control -->\r\n <div *ngSwitchCase=\"'image'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n <img\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n [src]=\"innerLayout?.option?.src ? innerLayout?.option?.src : record[innerLayout.scope?.reference.split('/').pop()]\"\r\n alt=\"{{ innerLayout.label ? innerLayout.label : getProperty(innerLayout.scope?.reference).productSchema.caption }}\"\r\n (click)=\"showImageDetails(innerLayout.option, record)\" />\r\n </div>\r\n\r\n <!-- String Control -->\r\n <div *ngSwitchCase=\"'string'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(innerLayout.label?.style).classes\">\r\n {{\r\n getHorizontalLayoutCaption(innerLayout)\r\n }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n @if(innerLayout.option &&\r\n innerLayout.option.control &&\r\n !innerLayout.option.protected){\r\n @switch\r\n (innerLayout.option.control)\r\n {\r\n @case ('textInput') {\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n\r\n <!-- DATE FORMAT -->\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.dateFormat; else checkInnerTime\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option.dateFormat)\r\n : '--' }}\r\n </ng-container>\r\n\r\n <!-- TIME FORMAT -->\r\n <ng-template\r\n #checkInnerTime>\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.timeFormat; else normalInnerValue\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n |\r\n date:\r\n innerLayout.option.timeFormat)\r\n : '--' }}\r\n </ng-container>\r\n </ng-template>\r\n\r\n <!-- NORMAL STRING -->\r\n <ng-template\r\n #normalInnerValue>\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ??\r\n '--' }}\r\n </ng-template>\r\n </h3>\r\n }\r\n @case ('password') {\r\n <span>Switch not supported\r\n yet</span>\r\n\r\n }\r\n @case ('combobox') {\r\n <span>Combobox not supported\r\n yet</span>\r\n }\r\n @case('textArea'){\r\n <span>TextArea not supported\r\n yet</span>\r\n }\r\n @case ('switch') {\r\n <span>Switch not supported\r\n yet</span>\r\n }\r\n\r\n @case('radiobutton') {\r\n <span>Radiobutton not supported\r\n yet</span>\r\n }\r\n\r\n @case ('dropdown') {\r\n <select id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.style\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][\r\n innerLayout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[\r\n innerLayout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[innerLayout?.option?.optionValue]\">\r\n {{\r\n item[innerLayout?.option?.optionLabel]\r\n }}\r\n </option>\r\n </select>\r\n }\r\n\r\n @case ('autocomplete') {\r\n\r\n <p-autocomplete [suggestions]=\"remoteSuggestions[innerLayout.scope?.reference] ?? []\" (completeMethod)=\"completeRemote($event, innerLayout)\"\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"innerLayout.option.required\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n [class]=\"innerLayout?.scope?.style?.classes ? innerLayout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"innerLayout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: innerLayout.scope?.reference\"\r\n [vlCustomKeyboard]=\"innerLayout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n }\r\n }\r\n }@else{\r\n\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n\r\n <!-- DATE FORMAT -->\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.dateFormat; else checkInnerTime\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option.dateFormat)\r\n : '--' }}\r\n </ng-container>\r\n\r\n <!-- TIME FORMAT -->\r\n <ng-template\r\n #checkInnerTime>\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.timeFormat; else normalInnerValue\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n |\r\n date:\r\n innerLayout.option.timeFormat)\r\n : '--' }}\r\n </ng-container>\r\n </ng-template>\r\n\r\n <!-- NORMAL STRING -->\r\n <ng-template\r\n #normalInnerValue>\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ??\r\n '--' }}\r\n </ng-template>\r\n </h3>\r\n }\r\n\r\n </div>\r\n\r\n <!-- number Control -->\r\n <div *ngSwitchCase=\"'number'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n {{\r\n record[innerLayout.scope?.reference.split('/').pop()]\r\n | currency:\r\n innerLayout.option?.currency\r\n ?\r\n innerLayout.option.currency\r\n :\r\n 'USD'\r\n }}\r\n </h3>\r\n </div>\r\n\r\n <!-- Date Control -->\r\n <div *ngSwitchCase=\"'date'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(innerLayout.label?.style).classes\">\r\n {{\r\n getHorizontalLayoutCaption(innerLayout)\r\n }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option?.dateFormat\r\n ?\r\n innerLayout.option?.dateFormat\r\n :\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[innerLayout.scope?.reference.split('/').pop()]?.format\r\n || 'yyyy-MM-dd') : '--' }}\r\n </h3>\r\n </div>\r\n\r\n <!-- Time Control -->\r\n <div *ngSwitchCase=\"'time'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(innerLayout.label?.style).classes\">\r\n {{\r\n getHorizontalLayoutCaption(innerLayout)\r\n }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option?.timeFormat\r\n ?\r\n innerLayout.option?.timeFormat\r\n :itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[innerLayout.scope?.reference.split('/').pop()]?.format\r\n || 'HH:mm') : '--' }}\r\n </h3>\r\n </div>\r\n\r\n <!-- dropdown Control -->\r\n <div *ngSwitchCase=\"'dropdown'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.format.currency\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n <select\r\n id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.style\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][\r\n innerLayout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[\r\n innerLayout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[innerLayout?.option?.optionValue]\">\r\n {{\r\n item[innerLayout?.option?.optionLabel]\r\n }}\r\n </option>\r\n </select>\r\n </h3>\r\n </div>\r\n\r\n <!-- autocomplete Control -->\r\n <div *ngSwitchCase=\"'autocomplete'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.format.currency\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n <p-autocomplete [suggestions]=\"remoteSuggestions[innerLayout.scope?.reference] ?? []\" (completeMethod)=\"completeRemote($event, innerLayout)\"\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"innerLayout.option.required\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n [class]=\"innerLayout?.scope?.style?.classes ? innerLayout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"innerLayout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: innerLayout.scope?.reference\"\r\n [vlCustomKeyboard]=\"innerLayout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n </h3>\r\n </div>\r\n\r\n <!-- checkbox Control -->\r\n <div *ngSwitchCase=\"'boolean'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n <input type=\"checkbox\"\r\n class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][\r\n innerLayout.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [name]=\"innerLayout.scope?.reference\" />\r\n </h3>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\">\r\n <button nz-button\r\n [matTooltip]=\"getHorizontalLayoutTooltip(innerLayout)\"\r\n [matTooltipTouchGestures]=\"'auto'\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.style).style\"\r\n [class]=\"innerLayout.option?.visible ? innerLayout.option.protected ? 'disabled-button' : getStyleWithoutClasses(innerLayout?.style).classes : 'hidden'\"\r\n (click)=\"\r\n invokeButtonClick(\r\n innerLayout,\r\n record,\r\n getRecordIndex(record)\r\n )\r\n\">\r\n <span\r\n class=\"schema-btn-content\">\r\n <i\r\n *ngIf=\"showButtonIcon(innerLayout)\"\r\n nz-icon\r\n [nzType]=\"innerLayout.icon\"\r\n nzTheme=\"outline\">\r\n </i>\r\n\r\n <span\r\n *ngIf=\"showButtonCaption(innerLayout)\">\r\n {{\r\n innerLayout.caption\r\n }}\r\n </span>\r\n\r\n <span *ngIf=\"\r\n !showButtonIcon(innerLayout) &&\r\n !showButtonCaption(innerLayout)\r\n \">\r\n Button\r\n </span>\r\n </span>\r\n </button>\r\n </div>\r\n\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div\r\n *ngIf=\"itemInfo?.footer?.elements && itemInfo?.data && itemInfo?.data.length\"\r\n [class]=\"getStyleWithoutClasses(itemInfo?.footer?.style).classes\">\r\n <span\r\n *ngFor=\"let element of itemInfo.footer?.elements; let c = index\">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(element.scope?.reference && itemInfo.propertySchema[getScope(element.scope?.reference)] ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.type : 'default')\">\r\n\r\n <!-- Date control -->\r\n <div *ngSwitchCase=\"'date'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"element.option.protected\"\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">\r\n <h5>{{\r\n itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.caption\r\n }}\r\n </h5>\r\n </span>\r\n <div *ngIf=\"!element.option.protected\">\r\n <div\r\n [class]=\"element?.layout == 'horizontal' ? 'horizontal-layout' : 'row'\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">{{\r\n element.label?.value\r\n ?\r\n element.label?.value :\r\n element.label?.caption ?\r\n element.label?.caption :\r\n itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n\r\n <nz-date-picker\r\n [required]=\"element.option.required\"\r\n [name]=\"element.scope?.reference\"\r\n [nzDisabled]=\"element?.option?.disabled\"\r\n (ngModelChange)=\"dateChangeEvent(element, $event)\"\r\n [nzFormat]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.format ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.format : 'yyyy-MM-dd'\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(element?.style).style\"></nz-date-picker>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\"\r\n [class]=\"element.option?.visible ? 'row' : 'hidden'\"\r\n (click)=\"invokeOnCardFooter(element)\">\r\n <button nz-button\r\n [style]=\"getStyleWithoutClasses(element.style).style\"\r\n [class]=\"element.option.protected ? 'disabled-button' : getStyleWithoutClasses(element.style).classes\">\r\n <ng-container\r\n *ngIf=\"!element.caption; else textContent\">\r\n <i nz-icon [nzType]=\"element.icon\"\r\n nzTheme=\"outline\"></i>\r\n </ng-container>\r\n <ng-template #textContent>\r\n {{ element.caption }}\r\n </ng-template>\r\n </button>\r\n </div>\r\n </ng-container>\r\n </span>\r\n </div>\r\n <!-- bottom pagination on card -->\r\n <div *ngIf=\"\r\n itemInfo?.data.length && \r\n itemInfo?.layoutSchema?.pagination?.visible &&\r\n !itemInfo?.layoutSchema?.pagination?.top &&\r\n !scrollPagination\r\n \" class=\"card-pagination-wrapper\">\r\n <vl-pagination [itemInfo]=\"itemInfo\"\r\n [selectedLanguageInfo]=\"selectedLanguageInfo\"\r\n [pageSize]=\"pageSize\"\r\n [currentPage]=\"currentPage\" [pages]=\"pages\"\r\n (pageSizeChange)=\"invokeOnPageSizeChange($event)\"\r\n (onGoToPage)=\"invokeGoToPage($event)\">\r\n </vl-pagination>\r\n </div>\r\n\r\n @if (scrollPagination) {\r\n <div #scrollPaginationSentinel class=\"scroll-pagination-sentinel\"\r\n aria-hidden=\"true\">\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;min-width:0}.vl-card-runtime{min-width:0;box-sizing:border-box}.card-responsive-grid{width:100%;min-width:0;box-sizing:border-box}.scroll-pagination-sentinel{width:100%;height:1px}.card-grid-item,.card-layout-item,.card-horizontal-layout,.card-field-wrapper{width:100%;min-width:0;box-sizing:border-box}.card-field-wrapper{overflow-wrap:anywhere}.card-field-wrapper p,.card-field-wrapper h3{margin:0;min-width:0}.card-layout-item img{display:block;max-width:100%;height:auto}.card-layout-item button{max-width:100%;box-sizing:border-box}.schema-btn-content{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-width:0}.hidden{display:none!important}.card-pagination-wrapper{width:100%;min-width:0;box-sizing:border-box;padding:0 10px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i5.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i5.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i5.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i5.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i5.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i11.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i12.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i13.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i14.ɵNzTransitionPatchDirective, selector: "[nz-button], [nz-icon], nz-icon, [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "directive", type: i15.NzWaveDirective, selector: "[nz-wave],button[nz-button]:not([nzType=\"link\"]):not([nzType=\"text\"])", inputs: ["nzWaveExtraNode"], exportAs: ["nzWave"] }, { kind: "ngmodule", type: NzDatePickerModule }, { kind: "component", type: i4.NzDatePickerComponent, selector: "nz-date-picker,nz-week-picker,nz-month-picker,nz-quarter-picker,nz-year-picker,nz-range-picker", inputs: ["nzInline", "nzAllowClear", "nzAutoFocus", "nzDisabled", "nzInputReadOnly", "nzOpen", "nzDisabledDate", "nzLocale", "nzPlaceHolder", "nzPopupStyle", "nzDropdownClassName", "nzSize", "nzStatus", "nzFormat", "nzVariant", "nzDateRender", "nzDisabledTime", "nzRenderExtraFooter", "nzShowToday", "nzMode", "nzShowNow", "nzRanges", "nzDefaultPickerValue", "nzSeparator", "nzSuffixIcon", "nzBackdrop", "nzId", "nzPlacement", "nzShowWeekNumber", "nzShowTime"], outputs: ["nzOnPanelChange", "nzOnCalendarChange", "nzOnOk", "nzOnOpenChange"], exportAs: ["nzDatePicker"] }, { kind: "ngmodule", type: NzModalModule }, { kind: "component", type: PaginationComponent, selector: "vl-pagination", inputs: ["itemInfo", "pageSize", "currentPage", "selectedLanguageInfo", "pages"], outputs: ["pageSizeChange", "onGoToPage"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i17.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: String$1, selector: "vl-string", inputs: ["formField", "modalItemInfo"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i18.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: CustomKeyboardDirective, selector: "[vlCustomKeyboard]", inputs: ["vlCustomKeyboard"] }, { kind: "pipe", type: PlaceholderPipe, name: "vlPlaceholder" }, { kind: "pipe", type: i5.CurrencyPipe, name: "currency" }, { kind: "pipe", type: i5.DatePipe, name: "date" }] });
|
|
4354
|
+
getMediaSource(layout, record) {
|
|
4355
|
+
const field = layout?.scope?.reference?.split('/').pop();
|
|
4356
|
+
const value = layout?.option?.src || (field ? record?.[field] : '');
|
|
4357
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
4358
|
+
}
|
|
4359
|
+
getMediaLabel(layout) {
|
|
4360
|
+
const label = layout?.label;
|
|
4361
|
+
if (typeof label === 'string' && label.trim()) {
|
|
4362
|
+
return label;
|
|
4363
|
+
}
|
|
4364
|
+
const reference = layout?.scope?.reference;
|
|
4365
|
+
const field = reference?.split('/').pop();
|
|
4366
|
+
const scope = reference ? this.getScope(reference) : '';
|
|
4367
|
+
return label?.caption
|
|
4368
|
+
|| this.itemInfo?.propertySchema?.[scope]?.[field]?.caption
|
|
4369
|
+
|| 'Media';
|
|
4370
|
+
}
|
|
4371
|
+
getMediaKind(layout, record) {
|
|
4372
|
+
const source = this.getMediaSource(layout, record);
|
|
4373
|
+
// Base64 sources can identify their type without a filename.
|
|
4374
|
+
const mime = /^data:([^;,]+)/i.exec(source)?.[1]?.toLowerCase();
|
|
4375
|
+
if (mime?.startsWith('image/'))
|
|
4376
|
+
return 'image';
|
|
4377
|
+
if (mime?.startsWith('video/'))
|
|
4378
|
+
return 'video';
|
|
4379
|
+
if (mime?.startsWith('audio/'))
|
|
4380
|
+
return 'audio';
|
|
4381
|
+
if (mime === 'application/pdf')
|
|
4382
|
+
return 'pdf';
|
|
4383
|
+
// Blob and Base64 URLs may have no extension.
|
|
4384
|
+
// Try the original filename/reference as well.
|
|
4385
|
+
const field = layout?.scope?.reference?.split('/').pop();
|
|
4386
|
+
const originalValue = field ? record?.[field] : undefined;
|
|
4387
|
+
const candidates = [
|
|
4388
|
+
source,
|
|
4389
|
+
layout?.option?.fileName,
|
|
4390
|
+
originalValue
|
|
4391
|
+
];
|
|
4392
|
+
const videos = ['mp4', 'm4v', 'webm', 'mov', 'ogv', 'avi', 'mkv'];
|
|
4393
|
+
const audios = ['mp3', 'wav', 'ogg', 'oga', 'm4a', 'aac', 'flac', 'opus'];
|
|
4394
|
+
const files = [
|
|
4395
|
+
'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
|
4396
|
+
'txt', 'csv', 'zip', 'rar', '7z'
|
|
4397
|
+
];
|
|
4398
|
+
for (const candidate of candidates) {
|
|
4399
|
+
if (typeof candidate !== 'string')
|
|
4400
|
+
continue;
|
|
4401
|
+
if (/^(data|blob):/i.test(candidate))
|
|
4402
|
+
continue;
|
|
4403
|
+
// Ignore URL query parameters and fragments.
|
|
4404
|
+
let path = candidate.split(/[?#]/)[0];
|
|
4405
|
+
try {
|
|
4406
|
+
path = decodeURIComponent(path);
|
|
4407
|
+
}
|
|
4408
|
+
catch {
|
|
4409
|
+
// Keep the original path when URL encoding is invalid.
|
|
4410
|
+
}
|
|
4411
|
+
const extension = /\.([a-z0-9]+)$/i.exec(path)?.[1]?.toLowerCase();
|
|
4412
|
+
if (!extension)
|
|
4413
|
+
continue;
|
|
4414
|
+
if (videos.includes(extension))
|
|
4415
|
+
return 'video';
|
|
4416
|
+
if (audios.includes(extension))
|
|
4417
|
+
return 'audio';
|
|
4418
|
+
if (extension === 'pdf')
|
|
4419
|
+
return 'pdf';
|
|
4420
|
+
if (files.includes(extension))
|
|
4421
|
+
return 'file';
|
|
4422
|
+
}
|
|
4423
|
+
return 'image';
|
|
4424
|
+
}
|
|
4425
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: CardComponent, deps: [{ token: UtilityService }, { token: i1.Platform }, { token: i1$2.HttpClient }, { token: ConfigService }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }, { token: i5.NzModalService }, { token: NotificationService }, { token: i7.OAuthService }, { token: RequestService }], target: i0.ɵɵFactoryTarget.Component });
|
|
4426
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.2", type: CardComponent, isStandalone: true, selector: "vl-card", inputs: { itemInfo: "itemInfo", currentPage: "currentPage", pageSize: "pageSize", selectedLanguageInfo: "selectedLanguageInfo", mobileDisplay: "mobileDisplay", scrollPagination: "scrollPagination", pages: "pages", paginationMode: "paginationMode", paginationLoading: "paginationLoading" }, outputs: { cardButtonClick: "cardButtonClick", cardHeaderButtonClick: "cardHeaderButtonClick", linkDetails: "linkDetails", imageDetails: "imageDetails", onPageSizeChange: "onPageSizeChange", onPageChange: "onPageChange", cardDataChange: "cardDataChange" }, host: { listeners: { "window:resize": "onWindowResize()" } }, viewQueries: [{ propertyName: "scrollPaginationSentinel", first: true, predicate: ["scrollPaginationSentinel"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the bottom pagination read itemInfo?.data.length; before the records arrive data is\r\n undefined, the template threw and the cards stayed half drawn (luv2shop start page: one\r\n card without texts)\r\n - fix: the price fields read ...?.format.currency; a property without format threw the same\r\n way (luv2shop: no prices, cards cut off) -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: autocompletes get their suggestions from RemoteListService (completeRemote) -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\r\n project defines none; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - change: autocomplete fields take the toolkit string keyboard unless option.keyboardType\r\n names another one ('native' keeps the system keyboard)\r\n - change: the date filter uses the shared picker markup (picker-field, DateTimePickerService) -->\r\n\r\n<!-- shuaib 2026-09-24\r\n - change: card field switches use renderFieldType so legacy checkbox fields render as boolean. -->\r\n\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: a @contract comment naming the types this container does not render. It carries four\r\n ngSwitch blocks, so the gap belongs to the container and not to one of them -->\r\n\r\n<!-- @contract gap: dateRange, quantity, credit card\r\n reality: a card renders string, number, image, date, time, dropdown, boolean and\r\n autocomplete. Legacy checkbox fields normalize to boolean. dropdown and boolean arrived on 2026-09-18; before that a card\r\n dropped them silently.\r\n failure: a field of a missing type renders empty, with no error.\r\n instead: quantity is grid-only by design; for a dateRange or a credit card use a modal.\r\n seen: this file carries four switch blocks (lines 23, 94, 267, 488) and none of them has\r\n a case for those three - the gap belongs to the container, not to one block -->\r\n\r\n<!-- Loader Spinner -->\r\n\r\n<div *ngIf=\"isLoading\" class=\"loader-overlay\">\r\n <div class=\"spinner\"></div>\r\n</div>\r\n<div class=\"vl-card-runtime\">\r\n <!-- top pagination on card -->\r\n <div *ngIf=\"\r\n itemInfo?.layoutSchema?.pagination?.visible &&\r\n itemInfo?.layoutSchema?.pagination?.top\r\n \" class=\"card-pagination-wrapper\">\r\n <vl-pagination [itemInfo]=\"itemInfo\"\r\n [selectedLanguageInfo]=\"selectedLanguageInfo\" [pageSize]=\"pageSize\"\r\n [currentPage]=\"currentPage\" [pages]=\"pages\"\r\n (pageSizeChange)=\"invokeOnPageSizeChange($event)\"\r\n (onGoToPage)=\"invokeGoToPage($event)\">\r\n </vl-pagination>\r\n </div>\r\n <div *ngIf=\"itemInfo?.header?.elements?.length\" [ngStyle]=\"getHeaderStyle()\"\r\n [class]=\"getStyleWithoutClasses(\r\n itemInfo?.header?.style\r\n ).classes\">\r\n <div *ngFor=\"let element of itemInfo.header?.elements; let c = index\"\r\n [ngStyle]=\"getHeaderElementWrapperStyle(element)\"\r\n [class]=\"element.option?.innerAction && element.option?.innerAction === 'saveAll' && !displayedRecords?.length ? \r\n 'hidden' :getStyleWithoutClasses(element?.wrapperStyle).classes\">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(element.scope?.reference && itemInfo.propertySchema[getScope(element.scope?.reference)] ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.type : 'default')\">\r\n\r\n <!-- String Control -->\r\n <div *ngSwitchCase=\"'string'\"\r\n [class]=\"element.option?.visible ? 'card-field-wrapper' : 'hidden'\">\r\n\r\n <!-- LABEL -->\r\n <span *ngIf=\"element.label?.caption\"\r\n [style]=\"getStyleWithoutClasses(element.label?.style).style\"\r\n [class]=\"element.label?.style?.classes?.[0]\">\r\n {{ element.label.caption }}\r\n </span>\r\n </div>\r\n\r\n <!-- Date control -->\r\n <div *ngSwitchCase=\"'date'\" class=\"mb-3\">\r\n <span *ngIf=\"element.option.protected\"\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">\r\n <vl-string [formField]=\"element\"\r\n [modalItemInfo]=\"itemInfo\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!element.option.protected\"\r\n [class]=\"element?.layout == 'horizontal' ? 'horizontal-layout' : ''\"\r\n [ngClass]=\"element?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(element?.style).style\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">{{\r\n element.label?.value\r\n ?\r\n element.label?.value : element.label?.caption ?\r\n element.label?.caption :\r\n itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <div class=\"picker-field\">\r\n <p-datepicker #cardDatePicker\r\n (ngModelChange)=\"dateChangeEvent(element, $event)\"\r\n [dateFormat]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.format ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()].format : 'yy-mm-dd'\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()].value\"\r\n (onInput)=\"onDateTyping($event)\"\r\n [class]=\"element?.scope?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(element?.scope?.style).style\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\"\r\n appendTo=\"body\"\r\n [panelStyleClass]=\"picker.datePanelClass\"\r\n [motionOptions]=\"picker.dockedMotion\"\r\n [vlCustomKeyboard]=\"element?.option?.keyboardType ?? 'date'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, cardDatePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(cardDatePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\"\r\n aria-label=\"Open calendar\"\r\n (pointerdown)=\"picker.openFromIcon($event, cardDatePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-calendar\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\"\r\n [class]=\"element.option?.visible ? 'row' : 'hidden'\"\r\n (click)=\"invokeOnCardHeader(element)\">\r\n <button nz-button type=\"button\"\r\n [matTooltip]=\"element?.option?.tooltip || ''\"\r\n [matTooltipTouchGestures]=\"'auto'\"\r\n [ngStyle]=\"getStyleWithoutClasses(element.style).style\"\r\n [class]=\"element.option.protected ? 'disabled-button' : getStyleWithoutClasses(element.style).classes\">\r\n <ng-container\r\n *ngIf=\"!element.caption; else headerTextContent\">\r\n <i nz-icon [nzType]=\"element.icon\"\r\n nzTheme=\"outline\"></i>\r\n </ng-container>\r\n <ng-template #headerTextContent>{{ element.caption\r\n }}</ng-template>\r\n </button>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n <div class=\"card-responsive-grid\" [ngStyle]=\"getCardContainerStyle()\"\r\n [class]=\"getStyleWithoutClasses(\r\n itemInfo?.layoutSchema?.style\r\n ).classes\">\r\n <div *ngFor=\"let record of displayedRecords; let k = index\"\r\n class=\"card-grid-item\">\r\n <div [ngStyle]=\"getSingleCardStyle()\"\r\n [class]=\"getStyleWithoutClasses(itemInfo?.layoutSchema?.stylePerCard).classes\">\r\n <div *ngFor=\"let layout of itemInfo?.layoutSchema?.cards || []\"\r\n class=\"card-layout-item\" [ngStyle]=\"\r\n layout?.type === 'HorizontalLayout'\r\n ? {}\r\n : getStyleWithoutClasses(layout?.style).style\r\n \" [class]=\"\r\n layout?.type === 'HorizontalLayout'\r\n ? ''\r\n : getStyleWithoutClasses(layout?.style).classes\r\n \">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(layout?.scope?.reference && itemInfo.propertySchema[getScope(layout.scope?.reference)] ? itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.type : layout?.type == 'HorizontalLayout' ? 'HorizontalLayout' : 'default')\">\r\n <!-- Media Control -->\r\n <div\r\n *ngSwitchCase=\"'media'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <ng-container\r\n *ngTemplateOutlet=\"mediaContent; context: { layout: layout, record: record }\">\r\n </ng-container>\r\n </div>\r\n\r\n <!-- String Control -->\r\n <div *ngSwitchCase=\"'string'\"\r\n [class]=\"layout.option?.visible ? 'card-field-wrapper' : 'hidden'\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(layout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(layout.label?.style).classes\">\r\n {{ getHorizontalLayoutCaption(layout) }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n @if(layout.option && layout.option.control &&\r\n !layout.option.protected){\r\n @switch (layout.option.control) {\r\n @case ('textInput') {\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ??\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ??\r\n '--'\r\n }}\r\n </p>\r\n }\r\n @case ('password') {\r\n <span>Switch not supported yet</span>\r\n\r\n }\r\n @case ('combobox') {\r\n <span>Combobox not supported yet</span>\r\n }\r\n @case('textArea'){\r\n <span>TextArea not supported yet</span>\r\n }\r\n @case ('switch') {\r\n <span>Switch not supported yet</span>\r\n }\r\n\r\n @case('radiobutton') {\r\n <span>Radiobutton not supported yet</span>\r\n }\r\n\r\n @case ('dropdown') {\r\n <select id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(layout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style)?.style\"\r\n [name]=\"layout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)][\r\n layout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[\r\n layout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[layout?.option?.optionValue]\">\r\n {{ item[layout?.option?.optionLabel] }}\r\n </option>\r\n </select>\r\n }\r\n\r\n @case ('autocomplete') {\r\n <p-autocomplete\r\n [suggestions]=\"remoteSuggestions[layout.scope?.reference] ?? []\"\r\n (completeMethod)=\"completeRemote($event, layout)\"\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"layout.option.required\"\r\n [name]=\"layout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"layout?.scope?.style?.classes ? layout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"layout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: layout.scope?.reference\"\r\n [vlCustomKeyboard]=\"layout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n }\r\n }\r\n }@else {\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ??\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ??\r\n '--'\r\n }}\r\n </p>\r\n }\r\n\r\n </div>\r\n\r\n <!-- Date Control -->\r\n <div *ngSwitchCase=\"'date'\"\r\n [class]=\"layout.option?.visible ? 'card-field-wrapper' : 'hidden'\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <!-- LABEL -->\r\n <span *ngIf=\"layout?.label?.caption\"\r\n [class]=\"layout.label?.style?.classes?.[0]\">\r\n {{ layout.label.caption }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ?? itemInfo.propertySchema[\r\n getScope(layout.scope?.reference)\r\n ]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ?? '--'\r\n }}\r\n </p>\r\n </div>\r\n\r\n <!-- Time Control -->\r\n <div *ngSwitchCase=\"'time'\"\r\n [class]=\"layout.option?.visible ? 'card-field-wrapper' : 'hidden'\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span *ngIf=\"layout?.label?.caption\"\r\n [class]=\"layout.label?.style?.classes?.[0]\">\r\n {{ layout.label.caption }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ?? itemInfo.propertySchema[\r\n getScope(layout.scope?.reference)\r\n ]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ?? '--'\r\n }}\r\n </p>\r\n </div>\r\n\r\n <!-- number Control -->\r\n <div *ngSwitchCase=\"'number'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <!-- VALUE -->\r\n @if(layout.option && layout.option.control &&\r\n !layout.option.protected){\r\n @switch (layout.option.control) {\r\n @case ('textInput') {\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n {{\r\n record[layout.scope?.reference.split('/').pop()]\r\n | currency: layout.option?.currency\r\n ?\r\n layout.option.currency\r\n :\r\n 'USD' }}\r\n </h3>\r\n }\r\n @case ('slider') {\r\n <p>Slider not Implemented yet</p>\r\n\r\n }\r\n @case ('spinner') {\r\n <p>Spinner not Implemented yet</p>\r\n }\r\n }\r\n } @else{\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n {{\r\n record[layout.scope?.reference.split('/').pop()]\r\n | currency: layout.option?.currency\r\n ?\r\n layout.option.currency\r\n :\r\n 'USD' }}\r\n </h3>\r\n }\r\n\r\n </div>\r\n\r\n <!-- dropdown Control -->\r\n <div *ngSwitchCase=\"'dropdown'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <select id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(layout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style)?.style\"\r\n [name]=\"layout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)][\r\n layout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[\r\n layout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[layout?.option?.optionValue]\">\r\n {{ item[layout?.option?.optionLabel]\r\n }}\r\n </option>\r\n </select>\r\n </h3>\r\n </div>\r\n\r\n <!-- autocomplete Control -->\r\n <div *ngSwitchCase=\"'autocomplete'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <p-autocomplete\r\n [suggestions]=\"remoteSuggestions[layout.scope?.reference] ?? []\"\r\n (completeMethod)=\"completeRemote($event, layout)\"\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"layout.option.required\"\r\n [name]=\"layout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"layout?.scope?.style?.classes ? layout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"layout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: layout.scope?.reference\"\r\n [vlCustomKeyboard]=\"layout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n </h3>\r\n </div>\r\n\r\n <!-- checkbox Control -->\r\n <div *ngSwitchCase=\"'boolean'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <input type=\"checkbox\"\r\n class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)][\r\n layout.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [name]=\"layout.scope?.reference\" />\r\n </h3>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <button\r\n [style]=\"getStyleWithoutClasses(layout?.style).style\"\r\n [class]=\"layout.option.protected ? 'disabled-button' : getStyleWithoutClasses(layout?.style).classes\"\r\n (click)=\"\r\n invokeButtonClick(\r\n layout,\r\n record,\r\n getRecordIndex(record)\r\n )\r\n\">\r\n <ng-container\r\n *ngIf=\"!layout.caption; else textContent\">\r\n <i nz-icon [nzType]=\"layout.icon\"\r\n nzTheme=\"outline\"></i>\r\n </ng-container>\r\n <ng-template #textContent>\r\n {{ layout.caption }}\r\n </ng-template>\r\n </button>\r\n </div>\r\n\r\n <!-- HorizontalLayout Control -->\r\n <div *ngSwitchCase=\"'HorizontalLayout'\">\r\n <div class=\"card-horizontal-layout\"\r\n [class]=\"getStyleWithoutClasses(\r\n layout?.style\r\n ).classes\" [ngStyle]=\"getHorizontalLayoutStyle(layout)\">\r\n <ng-container\r\n *ngFor=\"let innerLayout of layout.elements\">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(innerLayout.scope?.reference && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] ? itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.type : 'default')\">\r\n <!-- Media Control -->\r\n <div\r\n *ngSwitchCase=\"'media'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n\r\n <ng-container\r\n *ngTemplateOutlet=\"mediaContent; context: {\r\n layout: innerLayout,\r\n record: record\r\n }\">\r\n </ng-container>\r\n </div>\r\n\r\n <!-- String Control -->\r\n <div *ngSwitchCase=\"'string'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(innerLayout.label?.style).classes\">\r\n {{\r\n getHorizontalLayoutCaption(innerLayout)\r\n }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n @if(innerLayout.option &&\r\n innerLayout.option.control &&\r\n !innerLayout.option.protected){\r\n @switch\r\n (innerLayout.option.control)\r\n {\r\n @case ('textInput') {\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n\r\n <!-- DATE FORMAT -->\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.dateFormat; else checkInnerTime\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option.dateFormat)\r\n : '--' }}\r\n </ng-container>\r\n\r\n <!-- TIME FORMAT -->\r\n <ng-template\r\n #checkInnerTime>\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.timeFormat; else normalInnerValue\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n |\r\n date:\r\n innerLayout.option.timeFormat)\r\n : '--' }}\r\n </ng-container>\r\n </ng-template>\r\n\r\n <!-- NORMAL STRING -->\r\n <ng-template\r\n #normalInnerValue>\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ??\r\n '--' }}\r\n </ng-template>\r\n </h3>\r\n }\r\n @case ('password') {\r\n <span>Switch not supported\r\n yet</span>\r\n\r\n }\r\n @case ('combobox') {\r\n <span>Combobox not supported\r\n yet</span>\r\n }\r\n @case('textArea'){\r\n <span>TextArea not supported\r\n yet</span>\r\n }\r\n @case ('switch') {\r\n <span>Switch not supported\r\n yet</span>\r\n }\r\n\r\n @case('radiobutton') {\r\n <span>Radiobutton not supported\r\n yet</span>\r\n }\r\n\r\n @case ('dropdown') {\r\n <select id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.style\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][\r\n innerLayout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[\r\n innerLayout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[innerLayout?.option?.optionValue]\">\r\n {{\r\n item[innerLayout?.option?.optionLabel]\r\n }}\r\n </option>\r\n </select>\r\n }\r\n\r\n @case ('autocomplete') {\r\n\r\n <p-autocomplete\r\n [suggestions]=\"remoteSuggestions[innerLayout.scope?.reference] ?? []\"\r\n (completeMethod)=\"completeRemote($event, innerLayout)\"\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"innerLayout.option.required\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n [class]=\"innerLayout?.scope?.style?.classes ? innerLayout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"innerLayout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: innerLayout.scope?.reference\"\r\n [vlCustomKeyboard]=\"innerLayout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n }\r\n }\r\n }@else{\r\n\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n\r\n <!-- DATE FORMAT -->\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.dateFormat; else checkInnerTime\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option.dateFormat)\r\n : '--' }}\r\n </ng-container>\r\n\r\n <!-- TIME FORMAT -->\r\n <ng-template\r\n #checkInnerTime>\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.timeFormat; else normalInnerValue\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n |\r\n date:\r\n innerLayout.option.timeFormat)\r\n : '--' }}\r\n </ng-container>\r\n </ng-template>\r\n\r\n <!-- NORMAL STRING -->\r\n <ng-template\r\n #normalInnerValue>\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ??\r\n '--' }}\r\n </ng-template>\r\n </h3>\r\n }\r\n\r\n </div>\r\n\r\n <!-- number Control -->\r\n <div *ngSwitchCase=\"'number'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n {{\r\n record[innerLayout.scope?.reference.split('/').pop()]\r\n | currency:\r\n innerLayout.option?.currency\r\n ?\r\n innerLayout.option.currency\r\n :\r\n 'USD'\r\n }}\r\n </h3>\r\n </div>\r\n\r\n <!-- Date Control -->\r\n <div *ngSwitchCase=\"'date'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(innerLayout.label?.style).classes\">\r\n {{\r\n getHorizontalLayoutCaption(innerLayout)\r\n }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option?.dateFormat\r\n ?\r\n innerLayout.option?.dateFormat\r\n :\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[innerLayout.scope?.reference.split('/').pop()]?.format\r\n || 'yyyy-MM-dd') : '--' }}\r\n </h3>\r\n </div>\r\n\r\n <!-- Time Control -->\r\n <div *ngSwitchCase=\"'time'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(innerLayout.label?.style).classes\">\r\n {{\r\n getHorizontalLayoutCaption(innerLayout)\r\n }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option?.timeFormat\r\n ?\r\n innerLayout.option?.timeFormat\r\n :itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[innerLayout.scope?.reference.split('/').pop()]?.format\r\n || 'HH:mm') : '--' }}\r\n </h3>\r\n </div>\r\n\r\n <!-- dropdown Control -->\r\n <div *ngSwitchCase=\"'dropdown'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n <select\r\n id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.style\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][\r\n innerLayout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[\r\n innerLayout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[innerLayout?.option?.optionValue]\">\r\n {{\r\n item[innerLayout?.option?.optionLabel]\r\n }}\r\n </option>\r\n </select>\r\n </h3>\r\n </div>\r\n\r\n <!-- autocomplete Control -->\r\n <div *ngSwitchCase=\"'autocomplete'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n <p-autocomplete\r\n [suggestions]=\"remoteSuggestions[innerLayout.scope?.reference] ?? []\"\r\n (completeMethod)=\"completeRemote($event, innerLayout)\"\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"innerLayout.option.required\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n [class]=\"innerLayout?.scope?.style?.classes ? innerLayout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"innerLayout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: innerLayout.scope?.reference\"\r\n [vlCustomKeyboard]=\"innerLayout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n </h3>\r\n </div>\r\n\r\n <!-- checkbox Control -->\r\n <div *ngSwitchCase=\"'boolean'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n <input type=\"checkbox\"\r\n class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][\r\n innerLayout.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [name]=\"innerLayout.scope?.reference\" />\r\n </h3>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\">\r\n <button nz-button\r\n [matTooltip]=\"getHorizontalLayoutTooltip(innerLayout)\"\r\n [matTooltipTouchGestures]=\"'auto'\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.style).style\"\r\n [class]=\"innerLayout.option?.visible ? innerLayout.option.protected ? 'disabled-button' : getStyleWithoutClasses(innerLayout?.style).classes : 'hidden'\"\r\n (click)=\"\r\n invokeButtonClick(\r\n innerLayout,\r\n record,\r\n getRecordIndex(record)\r\n )\r\n\">\r\n <span\r\n class=\"schema-btn-content\">\r\n <i\r\n *ngIf=\"showButtonIcon(innerLayout)\"\r\n nz-icon\r\n [nzType]=\"innerLayout.icon\"\r\n nzTheme=\"outline\">\r\n </i>\r\n\r\n <span\r\n *ngIf=\"showButtonCaption(innerLayout)\">\r\n {{\r\n innerLayout.caption\r\n }}\r\n </span>\r\n\r\n <span *ngIf=\"\r\n !showButtonIcon(innerLayout) &&\r\n !showButtonCaption(innerLayout)\r\n \">\r\n Button\r\n </span>\r\n </span>\r\n </button>\r\n </div>\r\n\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div\r\n *ngIf=\"itemInfo?.footer?.elements && itemInfo?.data && itemInfo?.data.length\"\r\n [class]=\"getStyleWithoutClasses(itemInfo?.footer?.style).classes\">\r\n <span\r\n *ngFor=\"let element of itemInfo.footer?.elements; let c = index\">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(element.scope?.reference && itemInfo.propertySchema[getScope(element.scope?.reference)] ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.type : 'default')\">\r\n\r\n <!-- Date control -->\r\n <div *ngSwitchCase=\"'date'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"element.option.protected\"\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">\r\n <h5>{{\r\n itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.caption\r\n }}\r\n </h5>\r\n </span>\r\n <div *ngIf=\"!element.option.protected\">\r\n <div\r\n [class]=\"element?.layout == 'horizontal' ? 'horizontal-layout' : 'row'\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">{{\r\n element.label?.value\r\n ?\r\n element.label?.value :\r\n element.label?.caption ?\r\n element.label?.caption :\r\n itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n\r\n <nz-date-picker\r\n [required]=\"element.option.required\"\r\n [name]=\"element.scope?.reference\"\r\n [nzDisabled]=\"element?.option?.disabled\"\r\n (ngModelChange)=\"dateChangeEvent(element, $event)\"\r\n [nzFormat]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.format ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.format : 'yyyy-MM-dd'\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(element?.style).style\"></nz-date-picker>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\"\r\n [class]=\"element.option?.visible ? 'row' : 'hidden'\"\r\n (click)=\"invokeOnCardFooter(element)\">\r\n <button nz-button\r\n [style]=\"getStyleWithoutClasses(element.style).style\"\r\n [class]=\"element.option.protected ? 'disabled-button' : getStyleWithoutClasses(element.style).classes\">\r\n <ng-container\r\n *ngIf=\"!element.caption; else textContent\">\r\n <i nz-icon [nzType]=\"element.icon\"\r\n nzTheme=\"outline\"></i>\r\n </ng-container>\r\n <ng-template #textContent>\r\n {{ element.caption }}\r\n </ng-template>\r\n </button>\r\n </div>\r\n </ng-container>\r\n </span>\r\n </div>\r\n <!-- bottom pagination on card -->\r\n <div *ngIf=\"\r\n itemInfo?.data?.length && \r\n itemInfo?.layoutSchema?.pagination?.visible &&\r\n !itemInfo?.layoutSchema?.pagination?.top &&\r\n !scrollPagination\r\n \" class=\"card-pagination-wrapper\">\r\n <vl-pagination [itemInfo]=\"itemInfo\"\r\n [selectedLanguageInfo]=\"selectedLanguageInfo\"\r\n [pageSize]=\"pageSize\"\r\n [currentPage]=\"currentPage\" [pages]=\"pages\"\r\n (pageSizeChange)=\"invokeOnPageSizeChange($event)\"\r\n (onGoToPage)=\"invokeGoToPage($event)\">\r\n </vl-pagination>\r\n </div>\r\n\r\n @if (scrollPagination) {\r\n <div #scrollPaginationSentinel class=\"scroll-pagination-sentinel\"\r\n aria-hidden=\"true\">\r\n </div>\r\n }\r\n</div>\r\n<ng-template #mediaContent let-layout=\"layout\" let-record=\"record\">\r\n <ng-container *ngIf=\"getMediaSource(layout, record) as source\">\r\n <ng-container [ngSwitch]=\"getMediaKind(layout, record)\">\r\n\r\n <!-- Default: image. Keep your existing image-click behaviour. -->\r\n <img\r\n *ngSwitchDefault\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [src]=\"source\"\r\n [alt]=\"getMediaLabel(layout)\"\r\n loading=\"lazy\"\r\n (click)=\"showImageDetails(layout.option, record)\" />\r\n\r\n <video\r\n *ngSwitchCase=\"'video'\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [src]=\"source\"\r\n [attr.aria-label]=\"getMediaLabel(layout)\"\r\n controls\r\n playsinline\r\n preload=\"metadata\">\r\n Your browser does not support video playback.\r\n </video>\r\n\r\n <audio\r\n *ngSwitchCase=\"'audio'\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [src]=\"source\"\r\n [attr.aria-label]=\"getMediaLabel(layout)\"\r\n controls\r\n preload=\"metadata\">\r\n Your browser does not support audio playback.\r\n </audio>\r\n\r\n <a\r\n *ngSwitchCase=\"'pdf'\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [href]=\"source\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\">\r\n Open PDF \u2014 {{ getMediaLabel(layout) }}\r\n </a>\r\n\r\n <a\r\n *ngSwitchCase=\"'file'\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [href]=\"source\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\">\r\n Open file \u2014 {{ getMediaLabel(layout) }}\r\n </a>\r\n\r\n </ng-container>\r\n </ng-container>\r\n</ng-template>", styles: [":host{display:block;width:100%;min-width:0}.vl-card-runtime{min-width:0;box-sizing:border-box}.card-responsive-grid{width:100%;min-width:0;box-sizing:border-box}.scroll-pagination-sentinel{width:100%;height:1px}.card-grid-item,.card-layout-item,.card-horizontal-layout,.card-field-wrapper{width:100%;min-width:0;box-sizing:border-box}.card-field-wrapper{overflow-wrap:anywhere}.card-field-wrapper p,.card-field-wrapper h3{margin:0;min-width:0}.card-layout-item img{display:block;max-width:100%;height:auto}.card-layout-item button{max-width:100%;box-sizing:border-box}.schema-btn-content{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-width:0}.hidden{display:none!important}.card-pagination-wrapper{width:100%;min-width:0;box-sizing:border-box;padding:0 10px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1$1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1$1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i11.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i12.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i13.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i14.ɵNzTransitionPatchDirective, selector: "[nz-button], [nz-icon], nz-icon, [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "directive", type: i15.NzWaveDirective, selector: "[nz-wave],button[nz-button]:not([nzType=\"link\"]):not([nzType=\"text\"])", inputs: ["nzWaveExtraNode"], exportAs: ["nzWave"] }, { kind: "ngmodule", type: NzDatePickerModule }, { kind: "component", type: i4.NzDatePickerComponent, selector: "nz-date-picker,nz-week-picker,nz-month-picker,nz-quarter-picker,nz-year-picker,nz-range-picker", inputs: ["nzInline", "nzAllowClear", "nzAutoFocus", "nzDisabled", "nzInputReadOnly", "nzOpen", "nzDisabledDate", "nzLocale", "nzPlaceHolder", "nzPopupStyle", "nzDropdownClassName", "nzSize", "nzStatus", "nzFormat", "nzVariant", "nzDateRender", "nzDisabledTime", "nzRenderExtraFooter", "nzShowToday", "nzMode", "nzShowNow", "nzRanges", "nzDefaultPickerValue", "nzSeparator", "nzSuffixIcon", "nzBackdrop", "nzId", "nzPlacement", "nzShowWeekNumber", "nzShowTime"], outputs: ["nzOnPanelChange", "nzOnCalendarChange", "nzOnOk", "nzOnOpenChange"], exportAs: ["nzDatePicker"] }, { kind: "ngmodule", type: NzModalModule }, { kind: "component", type: PaginationComponent, selector: "vl-pagination", inputs: ["itemInfo", "pageSize", "currentPage", "selectedLanguageInfo", "pages"], outputs: ["pageSizeChange", "onGoToPage"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i17.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: String$1, selector: "vl-string", inputs: ["formField", "modalItemInfo"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i18.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: CustomKeyboardDirective, selector: "[vlCustomKeyboard]", inputs: ["vlCustomKeyboard"] }, { kind: "pipe", type: PlaceholderPipe, name: "vlPlaceholder" }, { kind: "pipe", type: i1$1.CurrencyPipe, name: "currency" }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }] });
|
|
4324
4427
|
}
|
|
4325
4428
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: CardComponent, decorators: [{
|
|
4326
4429
|
type: Component,
|
|
@@ -4338,8 +4441,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
4338
4441
|
AutoCompleteModule,
|
|
4339
4442
|
InputTextModule,
|
|
4340
4443
|
CustomKeyboardDirective,
|
|
4341
|
-
], standalone: true, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: autocompletes get their suggestions from RemoteListService (completeRemote) -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\r\n project defines none; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - change: autocomplete fields take the toolkit string keyboard unless option.keyboardType\r\n names another one ('native' keeps the system keyboard)\r\n - change: the date filter uses the shared picker markup (picker-field, DateTimePickerService) -->\r\n\r\n<!-- shuaib 2026-09-24\r\n - change: card field switches use renderFieldType so legacy checkbox fields render as boolean. -->\r\n\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: a @contract comment naming the types this container does not render. It carries four\r\n ngSwitch blocks, so the gap belongs to the container and not to one of them -->\r\n\r\n<!-- @contract gap: dateRange, quantity, credit card\r\n reality: a card renders string, number, image, date, time, dropdown, boolean and\r\n autocomplete. Legacy checkbox fields normalize to boolean. dropdown and boolean arrived on 2026-09-18; before that a card\r\n dropped them silently.\r\n failure: a field of a missing type renders empty, with no error.\r\n instead: quantity is grid-only by design; for a dateRange or a credit card use a modal.\r\n seen: this file carries four switch blocks (lines 23, 94, 267, 488) and none of them has\r\n a case for those three - the gap belongs to the container, not to one block -->\r\n\r\n<!-- Loader Spinner -->\r\n\r\n<div *ngIf=\"isLoading\" class=\"loader-overlay\">\r\n <div class=\"spinner\"></div>\r\n</div>\r\n<div class=\"vl-card-runtime\">\r\n <!-- top pagination on card -->\r\n <div *ngIf=\"\r\n itemInfo?.layoutSchema?.pagination?.visible &&\r\n itemInfo?.layoutSchema?.pagination?.top\r\n \" class=\"card-pagination-wrapper\">\r\n <vl-pagination [itemInfo]=\"itemInfo\"\r\n [selectedLanguageInfo]=\"selectedLanguageInfo\" [pageSize]=\"pageSize\"\r\n [currentPage]=\"currentPage\" [pages]=\"pages\"\r\n (pageSizeChange)=\"invokeOnPageSizeChange($event)\"\r\n (onGoToPage)=\"invokeGoToPage($event)\">\r\n </vl-pagination>\r\n </div>\r\n <div *ngIf=\"itemInfo?.header?.elements?.length\" [ngStyle]=\"getHeaderStyle()\"\r\n [class]=\"getStyleWithoutClasses(\r\n itemInfo?.header?.style\r\n ).classes\">\r\n <div *ngFor=\"let element of itemInfo.header?.elements; let c = index\"\r\n [ngStyle]=\"getHeaderElementWrapperStyle(element)\"\r\n [class]=\"element.option?.innerAction && element.option?.innerAction === 'saveAll' && !displayedRecords?.length ? \r\n 'hidden' :getStyleWithoutClasses(element?.wrapperStyle).classes\">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(element.scope?.reference && itemInfo.propertySchema[getScope(element.scope?.reference)] ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.type : 'default')\">\r\n\r\n <!-- String Control -->\r\n <div *ngSwitchCase=\"'string'\"\r\n [class]=\"element.option?.visible ? 'card-field-wrapper' : 'hidden'\">\r\n\r\n <!-- LABEL -->\r\n <span *ngIf=\"element.label?.caption\"\r\n [style]=\"getStyleWithoutClasses(element.label?.style).style\"\r\n [class]=\"element.label?.style?.classes?.[0]\">\r\n {{ element.label.caption }}\r\n </span>\r\n </div>\r\n\r\n <!-- Date control -->\r\n <div *ngSwitchCase=\"'date'\" class=\"mb-3\">\r\n <span *ngIf=\"element.option.protected\"\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">\r\n <vl-string [formField]=\"element\"\r\n [modalItemInfo]=\"itemInfo\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!element.option.protected\"\r\n [class]=\"element?.layout == 'horizontal' ? 'horizontal-layout' : ''\"\r\n [ngClass]=\"element?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(element?.style).style\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">{{\r\n element.label?.value\r\n ?\r\n element.label?.value : element.label?.caption ?\r\n element.label?.caption :\r\n itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <div class=\"picker-field\">\r\n <p-datepicker #cardDatePicker\r\n (ngModelChange)=\"dateChangeEvent(element, $event)\"\r\n [dateFormat]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.format ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()].format : 'yy-mm-dd'\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()].value\"\r\n (onInput)=\"onDateTyping($event)\"\r\n [class]=\"element?.scope?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(element?.scope?.style).style\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\"\r\n appendTo=\"body\"\r\n [panelStyleClass]=\"picker.datePanelClass\"\r\n [motionOptions]=\"picker.dockedMotion\"\r\n [vlCustomKeyboard]=\"element?.option?.keyboardType ?? 'date'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, cardDatePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(cardDatePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\"\r\n aria-label=\"Open calendar\"\r\n (pointerdown)=\"picker.openFromIcon($event, cardDatePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-calendar\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\"\r\n [class]=\"element.option?.visible ? 'row' : 'hidden'\"\r\n (click)=\"invokeOnCardHeader(element)\">\r\n <button nz-button type=\"button\"\r\n [matTooltip]=\"element?.option?.tooltip || ''\"\r\n [matTooltipTouchGestures]=\"'auto'\"\r\n [ngStyle]=\"getStyleWithoutClasses(element.style).style\"\r\n [class]=\"element.option.protected ? 'disabled-button' : getStyleWithoutClasses(element.style).classes\">\r\n <ng-container\r\n *ngIf=\"!element.caption; else headerTextContent\">\r\n <i nz-icon [nzType]=\"element.icon\"\r\n nzTheme=\"outline\"></i>\r\n </ng-container>\r\n <ng-template #headerTextContent>{{ element.caption\r\n }}</ng-template>\r\n </button>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n <div class=\"card-responsive-grid\" [ngStyle]=\"getCardContainerStyle()\"\r\n [class]=\"getStyleWithoutClasses(\r\n itemInfo?.layoutSchema?.style\r\n ).classes\">\r\n <div *ngFor=\"let record of displayedRecords; let k = index\"\r\n class=\"card-grid-item\">\r\n <div [ngStyle]=\"getSingleCardStyle()\"\r\n [class]=\"getStyleWithoutClasses(itemInfo?.layoutSchema?.stylePerCard).classes\">\r\n <div *ngFor=\"let layout of itemInfo?.layoutSchema?.cards || []\"\r\n class=\"card-layout-item\" [ngStyle]=\"\r\n layout?.type === 'HorizontalLayout'\r\n ? {}\r\n : getStyleWithoutClasses(layout?.style).style\r\n \" [class]=\"\r\n layout?.type === 'HorizontalLayout'\r\n ? ''\r\n : getStyleWithoutClasses(layout?.style).classes\r\n \">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(layout?.scope?.reference && itemInfo.propertySchema[getScope(layout.scope?.reference)] ? itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.type : layout?.type == 'HorizontalLayout' ? 'HorizontalLayout' : 'default')\">\r\n <!-- Image Control -->\r\n <div *ngSwitchCase=\"'image'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <img\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [src]=\"layout?.option?.src ? layout?.option?.src : record[layout.scope?.reference.split('/').pop()]\"\r\n alt=\"{{ layout.label ? layout.label : getProperty(layout.scope?.reference).productSchema.caption }}\"\r\n (click)=\"showImageDetails(layout.option, record)\" />\r\n </div>\r\n\r\n <!-- String Control -->\r\n <div *ngSwitchCase=\"'string'\"\r\n [class]=\"layout.option?.visible ? 'card-field-wrapper' : 'hidden'\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(layout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(layout.label?.style).classes\">\r\n {{ getHorizontalLayoutCaption(layout) }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n @if(layout.option && layout.option.control &&\r\n !layout.option.protected){\r\n @switch (layout.option.control) {\r\n @case ('textInput') {\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ??\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ??\r\n '--'\r\n }}\r\n </p>\r\n }\r\n @case ('password') {\r\n <span>Switch not supported yet</span>\r\n\r\n }\r\n @case ('combobox') {\r\n <span>Combobox not supported yet</span>\r\n }\r\n @case('textArea'){\r\n <span>TextArea not supported yet</span>\r\n }\r\n @case ('switch') {\r\n <span>Switch not supported yet</span>\r\n }\r\n\r\n @case('radiobutton') {\r\n <span>Radiobutton not supported yet</span>\r\n }\r\n\r\n @case ('dropdown') {\r\n <select id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(layout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style)?.style\"\r\n [name]=\"layout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)][\r\n layout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[\r\n layout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[layout?.option?.optionValue]\">\r\n {{ item[layout?.option?.optionLabel] }}\r\n </option>\r\n </select>\r\n }\r\n\r\n @case ('autocomplete') {\r\n <p-autocomplete [suggestions]=\"remoteSuggestions[layout.scope?.reference] ?? []\" (completeMethod)=\"completeRemote($event, layout)\" #autocompleteField=\"ngModel\"\r\n [required]=\"layout.option.required\"\r\n [name]=\"layout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"layout?.scope?.style?.classes ? layout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"layout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: layout.scope?.reference\"\r\n [vlCustomKeyboard]=\"layout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n }\r\n }\r\n }@else {\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ??\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ??\r\n '--'\r\n }}\r\n </p>\r\n }\r\n\r\n </div>\r\n\r\n <!-- Date Control -->\r\n <div *ngSwitchCase=\"'date'\"\r\n [class]=\"layout.option?.visible ? 'card-field-wrapper' : 'hidden'\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <!-- LABEL -->\r\n <span *ngIf=\"layout?.label?.caption\"\r\n [class]=\"layout.label?.style?.classes?.[0]\">\r\n {{ layout.label.caption }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ?? itemInfo.propertySchema[\r\n getScope(layout.scope?.reference)\r\n ]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ?? '--'\r\n }}\r\n </p>\r\n </div>\r\n\r\n <!-- Time Control -->\r\n <div *ngSwitchCase=\"'time'\"\r\n [class]=\"layout.option?.visible ? 'card-field-wrapper' : 'hidden'\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span *ngIf=\"layout?.label?.caption\"\r\n [class]=\"layout.label?.style?.classes?.[0]\">\r\n {{ layout.label.caption }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ?? itemInfo.propertySchema[\r\n getScope(layout.scope?.reference)\r\n ]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ?? '--'\r\n }}\r\n </p>\r\n </div>\r\n\r\n <!-- number Control -->\r\n <div *ngSwitchCase=\"'number'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <!-- VALUE -->\r\n @if(layout.option && layout.option.control &&\r\n !layout.option.protected){\r\n @switch (layout.option.control) {\r\n @case ('textInput') {\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n {{\r\n record[layout.scope?.reference.split('/').pop()]\r\n | currency: layout.option?.currency\r\n ?\r\n layout.option.currency\r\n :\r\n 'USD' }}\r\n </h3>\r\n }\r\n @case ('slider') {\r\n <p>Slider not Implemented yet</p>\r\n\r\n }\r\n @case ('spinner') {\r\n <p>Spinner not Implemented yet</p>\r\n }\r\n }\r\n } @else{\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n {{\r\n record[layout.scope?.reference.split('/').pop()]\r\n | currency: layout.option?.currency\r\n ?\r\n layout.option.currency\r\n :\r\n 'USD' }}\r\n </h3>\r\n }\r\n\r\n </div>\r\n\r\n <!-- dropdown Control -->\r\n <div *ngSwitchCase=\"'dropdown'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <select id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(layout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style)?.style\"\r\n [name]=\"layout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)][\r\n layout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[\r\n layout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[layout?.option?.optionValue]\">\r\n {{ item[layout?.option?.optionLabel]\r\n }}\r\n </option>\r\n </select>\r\n </h3>\r\n </div>\r\n\r\n <!-- autocomplete Control -->\r\n <div *ngSwitchCase=\"'autocomplete'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <p-autocomplete [suggestions]=\"remoteSuggestions[layout.scope?.reference] ?? []\" (completeMethod)=\"completeRemote($event, layout)\" #autocompleteField=\"ngModel\"\r\n [required]=\"layout.option.required\"\r\n [name]=\"layout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"layout?.scope?.style?.classes ? layout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"layout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: layout.scope?.reference\"\r\n [vlCustomKeyboard]=\"layout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n </h3>\r\n </div>\r\n\r\n <!-- checkbox Control -->\r\n <div *ngSwitchCase=\"'boolean'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <input type=\"checkbox\"\r\n class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)][\r\n layout.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [name]=\"layout.scope?.reference\" />\r\n </h3>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <button\r\n [style]=\"getStyleWithoutClasses(layout?.style).style\"\r\n [class]=\"layout.option.protected ? 'disabled-button' : getStyleWithoutClasses(layout?.style).classes\"\r\n (click)=\"\r\n invokeButtonClick(\r\n layout,\r\n record,\r\n getRecordIndex(record)\r\n )\r\n\">\r\n <ng-container\r\n *ngIf=\"!layout.caption; else textContent\">\r\n <i nz-icon [nzType]=\"layout.icon\"\r\n nzTheme=\"outline\"></i>\r\n </ng-container>\r\n <ng-template #textContent>\r\n {{ layout.caption }}\r\n </ng-template>\r\n </button>\r\n </div>\r\n\r\n <!-- HorizontalLayout Control -->\r\n <div *ngSwitchCase=\"'HorizontalLayout'\">\r\n <div class=\"card-horizontal-layout\"\r\n [class]=\"getStyleWithoutClasses(\r\n layout?.style\r\n ).classes\" [ngStyle]=\"getHorizontalLayoutStyle(layout)\">\r\n <ng-container\r\n *ngFor=\"let innerLayout of layout.elements\">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(innerLayout.scope?.reference && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] ? itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.type : 'default')\">\r\n <!-- Image Control -->\r\n <div *ngSwitchCase=\"'image'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n <img\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n [src]=\"innerLayout?.option?.src ? innerLayout?.option?.src : record[innerLayout.scope?.reference.split('/').pop()]\"\r\n alt=\"{{ innerLayout.label ? innerLayout.label : getProperty(innerLayout.scope?.reference).productSchema.caption }}\"\r\n (click)=\"showImageDetails(innerLayout.option, record)\" />\r\n </div>\r\n\r\n <!-- String Control -->\r\n <div *ngSwitchCase=\"'string'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(innerLayout.label?.style).classes\">\r\n {{\r\n getHorizontalLayoutCaption(innerLayout)\r\n }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n @if(innerLayout.option &&\r\n innerLayout.option.control &&\r\n !innerLayout.option.protected){\r\n @switch\r\n (innerLayout.option.control)\r\n {\r\n @case ('textInput') {\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n\r\n <!-- DATE FORMAT -->\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.dateFormat; else checkInnerTime\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option.dateFormat)\r\n : '--' }}\r\n </ng-container>\r\n\r\n <!-- TIME FORMAT -->\r\n <ng-template\r\n #checkInnerTime>\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.timeFormat; else normalInnerValue\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n |\r\n date:\r\n innerLayout.option.timeFormat)\r\n : '--' }}\r\n </ng-container>\r\n </ng-template>\r\n\r\n <!-- NORMAL STRING -->\r\n <ng-template\r\n #normalInnerValue>\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ??\r\n '--' }}\r\n </ng-template>\r\n </h3>\r\n }\r\n @case ('password') {\r\n <span>Switch not supported\r\n yet</span>\r\n\r\n }\r\n @case ('combobox') {\r\n <span>Combobox not supported\r\n yet</span>\r\n }\r\n @case('textArea'){\r\n <span>TextArea not supported\r\n yet</span>\r\n }\r\n @case ('switch') {\r\n <span>Switch not supported\r\n yet</span>\r\n }\r\n\r\n @case('radiobutton') {\r\n <span>Radiobutton not supported\r\n yet</span>\r\n }\r\n\r\n @case ('dropdown') {\r\n <select id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.style\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][\r\n innerLayout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[\r\n innerLayout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[innerLayout?.option?.optionValue]\">\r\n {{\r\n item[innerLayout?.option?.optionLabel]\r\n }}\r\n </option>\r\n </select>\r\n }\r\n\r\n @case ('autocomplete') {\r\n\r\n <p-autocomplete [suggestions]=\"remoteSuggestions[innerLayout.scope?.reference] ?? []\" (completeMethod)=\"completeRemote($event, innerLayout)\"\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"innerLayout.option.required\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n [class]=\"innerLayout?.scope?.style?.classes ? innerLayout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"innerLayout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: innerLayout.scope?.reference\"\r\n [vlCustomKeyboard]=\"innerLayout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n }\r\n }\r\n }@else{\r\n\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n\r\n <!-- DATE FORMAT -->\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.dateFormat; else checkInnerTime\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option.dateFormat)\r\n : '--' }}\r\n </ng-container>\r\n\r\n <!-- TIME FORMAT -->\r\n <ng-template\r\n #checkInnerTime>\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.timeFormat; else normalInnerValue\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n |\r\n date:\r\n innerLayout.option.timeFormat)\r\n : '--' }}\r\n </ng-container>\r\n </ng-template>\r\n\r\n <!-- NORMAL STRING -->\r\n <ng-template\r\n #normalInnerValue>\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ??\r\n '--' }}\r\n </ng-template>\r\n </h3>\r\n }\r\n\r\n </div>\r\n\r\n <!-- number Control -->\r\n <div *ngSwitchCase=\"'number'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n {{\r\n record[innerLayout.scope?.reference.split('/').pop()]\r\n | currency:\r\n innerLayout.option?.currency\r\n ?\r\n innerLayout.option.currency\r\n :\r\n 'USD'\r\n }}\r\n </h3>\r\n </div>\r\n\r\n <!-- Date Control -->\r\n <div *ngSwitchCase=\"'date'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(innerLayout.label?.style).classes\">\r\n {{\r\n getHorizontalLayoutCaption(innerLayout)\r\n }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option?.dateFormat\r\n ?\r\n innerLayout.option?.dateFormat\r\n :\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[innerLayout.scope?.reference.split('/').pop()]?.format\r\n || 'yyyy-MM-dd') : '--' }}\r\n </h3>\r\n </div>\r\n\r\n <!-- Time Control -->\r\n <div *ngSwitchCase=\"'time'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(innerLayout.label?.style).classes\">\r\n {{\r\n getHorizontalLayoutCaption(innerLayout)\r\n }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option?.timeFormat\r\n ?\r\n innerLayout.option?.timeFormat\r\n :itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[innerLayout.scope?.reference.split('/').pop()]?.format\r\n || 'HH:mm') : '--' }}\r\n </h3>\r\n </div>\r\n\r\n <!-- dropdown Control -->\r\n <div *ngSwitchCase=\"'dropdown'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.format.currency\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n <select\r\n id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.style\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][\r\n innerLayout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[\r\n innerLayout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[innerLayout?.option?.optionValue]\">\r\n {{\r\n item[innerLayout?.option?.optionLabel]\r\n }}\r\n </option>\r\n </select>\r\n </h3>\r\n </div>\r\n\r\n <!-- autocomplete Control -->\r\n <div *ngSwitchCase=\"'autocomplete'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.format.currency\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n <p-autocomplete [suggestions]=\"remoteSuggestions[innerLayout.scope?.reference] ?? []\" (completeMethod)=\"completeRemote($event, innerLayout)\"\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"innerLayout.option.required\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n [class]=\"innerLayout?.scope?.style?.classes ? innerLayout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"innerLayout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: innerLayout.scope?.reference\"\r\n [vlCustomKeyboard]=\"innerLayout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n </h3>\r\n </div>\r\n\r\n <!-- checkbox Control -->\r\n <div *ngSwitchCase=\"'boolean'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n <input type=\"checkbox\"\r\n class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][\r\n innerLayout.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [name]=\"innerLayout.scope?.reference\" />\r\n </h3>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\">\r\n <button nz-button\r\n [matTooltip]=\"getHorizontalLayoutTooltip(innerLayout)\"\r\n [matTooltipTouchGestures]=\"'auto'\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.style).style\"\r\n [class]=\"innerLayout.option?.visible ? innerLayout.option.protected ? 'disabled-button' : getStyleWithoutClasses(innerLayout?.style).classes : 'hidden'\"\r\n (click)=\"\r\n invokeButtonClick(\r\n innerLayout,\r\n record,\r\n getRecordIndex(record)\r\n )\r\n\">\r\n <span\r\n class=\"schema-btn-content\">\r\n <i\r\n *ngIf=\"showButtonIcon(innerLayout)\"\r\n nz-icon\r\n [nzType]=\"innerLayout.icon\"\r\n nzTheme=\"outline\">\r\n </i>\r\n\r\n <span\r\n *ngIf=\"showButtonCaption(innerLayout)\">\r\n {{\r\n innerLayout.caption\r\n }}\r\n </span>\r\n\r\n <span *ngIf=\"\r\n !showButtonIcon(innerLayout) &&\r\n !showButtonCaption(innerLayout)\r\n \">\r\n Button\r\n </span>\r\n </span>\r\n </button>\r\n </div>\r\n\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div\r\n *ngIf=\"itemInfo?.footer?.elements && itemInfo?.data && itemInfo?.data.length\"\r\n [class]=\"getStyleWithoutClasses(itemInfo?.footer?.style).classes\">\r\n <span\r\n *ngFor=\"let element of itemInfo.footer?.elements; let c = index\">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(element.scope?.reference && itemInfo.propertySchema[getScope(element.scope?.reference)] ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.type : 'default')\">\r\n\r\n <!-- Date control -->\r\n <div *ngSwitchCase=\"'date'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"element.option.protected\"\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">\r\n <h5>{{\r\n itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.caption\r\n }}\r\n </h5>\r\n </span>\r\n <div *ngIf=\"!element.option.protected\">\r\n <div\r\n [class]=\"element?.layout == 'horizontal' ? 'horizontal-layout' : 'row'\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">{{\r\n element.label?.value\r\n ?\r\n element.label?.value :\r\n element.label?.caption ?\r\n element.label?.caption :\r\n itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n\r\n <nz-date-picker\r\n [required]=\"element.option.required\"\r\n [name]=\"element.scope?.reference\"\r\n [nzDisabled]=\"element?.option?.disabled\"\r\n (ngModelChange)=\"dateChangeEvent(element, $event)\"\r\n [nzFormat]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.format ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.format : 'yyyy-MM-dd'\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(element?.style).style\"></nz-date-picker>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\"\r\n [class]=\"element.option?.visible ? 'row' : 'hidden'\"\r\n (click)=\"invokeOnCardFooter(element)\">\r\n <button nz-button\r\n [style]=\"getStyleWithoutClasses(element.style).style\"\r\n [class]=\"element.option.protected ? 'disabled-button' : getStyleWithoutClasses(element.style).classes\">\r\n <ng-container\r\n *ngIf=\"!element.caption; else textContent\">\r\n <i nz-icon [nzType]=\"element.icon\"\r\n nzTheme=\"outline\"></i>\r\n </ng-container>\r\n <ng-template #textContent>\r\n {{ element.caption }}\r\n </ng-template>\r\n </button>\r\n </div>\r\n </ng-container>\r\n </span>\r\n </div>\r\n <!-- bottom pagination on card -->\r\n <div *ngIf=\"\r\n itemInfo?.data.length && \r\n itemInfo?.layoutSchema?.pagination?.visible &&\r\n !itemInfo?.layoutSchema?.pagination?.top &&\r\n !scrollPagination\r\n \" class=\"card-pagination-wrapper\">\r\n <vl-pagination [itemInfo]=\"itemInfo\"\r\n [selectedLanguageInfo]=\"selectedLanguageInfo\"\r\n [pageSize]=\"pageSize\"\r\n [currentPage]=\"currentPage\" [pages]=\"pages\"\r\n (pageSizeChange)=\"invokeOnPageSizeChange($event)\"\r\n (onGoToPage)=\"invokeGoToPage($event)\">\r\n </vl-pagination>\r\n </div>\r\n\r\n @if (scrollPagination) {\r\n <div #scrollPaginationSentinel class=\"scroll-pagination-sentinel\"\r\n aria-hidden=\"true\">\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;min-width:0}.vl-card-runtime{min-width:0;box-sizing:border-box}.card-responsive-grid{width:100%;min-width:0;box-sizing:border-box}.scroll-pagination-sentinel{width:100%;height:1px}.card-grid-item,.card-layout-item,.card-horizontal-layout,.card-field-wrapper{width:100%;min-width:0;box-sizing:border-box}.card-field-wrapper{overflow-wrap:anywhere}.card-field-wrapper p,.card-field-wrapper h3{margin:0;min-width:0}.card-layout-item img{display:block;max-width:100%;height:auto}.card-layout-item button{max-width:100%;box-sizing:border-box}.schema-btn-content{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-width:0}.hidden{display:none!important}.card-pagination-wrapper{width:100%;min-width:0;box-sizing:border-box;padding:0 10px}\n"] }]
|
|
4342
|
-
}], ctorParameters: () => [{ type: UtilityService }, { type: i1.Platform }, { type: i1$
|
|
4444
|
+
], standalone: true, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the bottom pagination read itemInfo?.data.length; before the records arrive data is\r\n undefined, the template threw and the cards stayed half drawn (luv2shop start page: one\r\n card without texts)\r\n - fix: the price fields read ...?.format.currency; a property without format threw the same\r\n way (luv2shop: no prices, cards cut off) -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: autocompletes get their suggestions from RemoteListService (completeRemote) -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\r\n project defines none; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - change: autocomplete fields take the toolkit string keyboard unless option.keyboardType\r\n names another one ('native' keeps the system keyboard)\r\n - change: the date filter uses the shared picker markup (picker-field, DateTimePickerService) -->\r\n\r\n<!-- shuaib 2026-09-24\r\n - change: card field switches use renderFieldType so legacy checkbox fields render as boolean. -->\r\n\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: a @contract comment naming the types this container does not render. It carries four\r\n ngSwitch blocks, so the gap belongs to the container and not to one of them -->\r\n\r\n<!-- @contract gap: dateRange, quantity, credit card\r\n reality: a card renders string, number, image, date, time, dropdown, boolean and\r\n autocomplete. Legacy checkbox fields normalize to boolean. dropdown and boolean arrived on 2026-09-18; before that a card\r\n dropped them silently.\r\n failure: a field of a missing type renders empty, with no error.\r\n instead: quantity is grid-only by design; for a dateRange or a credit card use a modal.\r\n seen: this file carries four switch blocks (lines 23, 94, 267, 488) and none of them has\r\n a case for those three - the gap belongs to the container, not to one block -->\r\n\r\n<!-- Loader Spinner -->\r\n\r\n<div *ngIf=\"isLoading\" class=\"loader-overlay\">\r\n <div class=\"spinner\"></div>\r\n</div>\r\n<div class=\"vl-card-runtime\">\r\n <!-- top pagination on card -->\r\n <div *ngIf=\"\r\n itemInfo?.layoutSchema?.pagination?.visible &&\r\n itemInfo?.layoutSchema?.pagination?.top\r\n \" class=\"card-pagination-wrapper\">\r\n <vl-pagination [itemInfo]=\"itemInfo\"\r\n [selectedLanguageInfo]=\"selectedLanguageInfo\" [pageSize]=\"pageSize\"\r\n [currentPage]=\"currentPage\" [pages]=\"pages\"\r\n (pageSizeChange)=\"invokeOnPageSizeChange($event)\"\r\n (onGoToPage)=\"invokeGoToPage($event)\">\r\n </vl-pagination>\r\n </div>\r\n <div *ngIf=\"itemInfo?.header?.elements?.length\" [ngStyle]=\"getHeaderStyle()\"\r\n [class]=\"getStyleWithoutClasses(\r\n itemInfo?.header?.style\r\n ).classes\">\r\n <div *ngFor=\"let element of itemInfo.header?.elements; let c = index\"\r\n [ngStyle]=\"getHeaderElementWrapperStyle(element)\"\r\n [class]=\"element.option?.innerAction && element.option?.innerAction === 'saveAll' && !displayedRecords?.length ? \r\n 'hidden' :getStyleWithoutClasses(element?.wrapperStyle).classes\">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(element.scope?.reference && itemInfo.propertySchema[getScope(element.scope?.reference)] ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.type : 'default')\">\r\n\r\n <!-- String Control -->\r\n <div *ngSwitchCase=\"'string'\"\r\n [class]=\"element.option?.visible ? 'card-field-wrapper' : 'hidden'\">\r\n\r\n <!-- LABEL -->\r\n <span *ngIf=\"element.label?.caption\"\r\n [style]=\"getStyleWithoutClasses(element.label?.style).style\"\r\n [class]=\"element.label?.style?.classes?.[0]\">\r\n {{ element.label.caption }}\r\n </span>\r\n </div>\r\n\r\n <!-- Date control -->\r\n <div *ngSwitchCase=\"'date'\" class=\"mb-3\">\r\n <span *ngIf=\"element.option.protected\"\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">\r\n <vl-string [formField]=\"element\"\r\n [modalItemInfo]=\"itemInfo\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!element.option.protected\"\r\n [class]=\"element?.layout == 'horizontal' ? 'horizontal-layout' : ''\"\r\n [ngClass]=\"element?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(element?.style).style\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">{{\r\n element.label?.value\r\n ?\r\n element.label?.value : element.label?.caption ?\r\n element.label?.caption :\r\n itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <div class=\"picker-field\">\r\n <p-datepicker #cardDatePicker\r\n (ngModelChange)=\"dateChangeEvent(element, $event)\"\r\n [dateFormat]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.format ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()].format : 'yy-mm-dd'\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()].value\"\r\n (onInput)=\"onDateTyping($event)\"\r\n [class]=\"element?.scope?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(element?.scope?.style).style\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\"\r\n appendTo=\"body\"\r\n [panelStyleClass]=\"picker.datePanelClass\"\r\n [motionOptions]=\"picker.dockedMotion\"\r\n [vlCustomKeyboard]=\"element?.option?.keyboardType ?? 'date'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, cardDatePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(cardDatePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\"\r\n aria-label=\"Open calendar\"\r\n (pointerdown)=\"picker.openFromIcon($event, cardDatePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-calendar\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\"\r\n [class]=\"element.option?.visible ? 'row' : 'hidden'\"\r\n (click)=\"invokeOnCardHeader(element)\">\r\n <button nz-button type=\"button\"\r\n [matTooltip]=\"element?.option?.tooltip || ''\"\r\n [matTooltipTouchGestures]=\"'auto'\"\r\n [ngStyle]=\"getStyleWithoutClasses(element.style).style\"\r\n [class]=\"element.option.protected ? 'disabled-button' : getStyleWithoutClasses(element.style).classes\">\r\n <ng-container\r\n *ngIf=\"!element.caption; else headerTextContent\">\r\n <i nz-icon [nzType]=\"element.icon\"\r\n nzTheme=\"outline\"></i>\r\n </ng-container>\r\n <ng-template #headerTextContent>{{ element.caption\r\n }}</ng-template>\r\n </button>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n <div class=\"card-responsive-grid\" [ngStyle]=\"getCardContainerStyle()\"\r\n [class]=\"getStyleWithoutClasses(\r\n itemInfo?.layoutSchema?.style\r\n ).classes\">\r\n <div *ngFor=\"let record of displayedRecords; let k = index\"\r\n class=\"card-grid-item\">\r\n <div [ngStyle]=\"getSingleCardStyle()\"\r\n [class]=\"getStyleWithoutClasses(itemInfo?.layoutSchema?.stylePerCard).classes\">\r\n <div *ngFor=\"let layout of itemInfo?.layoutSchema?.cards || []\"\r\n class=\"card-layout-item\" [ngStyle]=\"\r\n layout?.type === 'HorizontalLayout'\r\n ? {}\r\n : getStyleWithoutClasses(layout?.style).style\r\n \" [class]=\"\r\n layout?.type === 'HorizontalLayout'\r\n ? ''\r\n : getStyleWithoutClasses(layout?.style).classes\r\n \">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(layout?.scope?.reference && itemInfo.propertySchema[getScope(layout.scope?.reference)] ? itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.type : layout?.type == 'HorizontalLayout' ? 'HorizontalLayout' : 'default')\">\r\n <!-- Media Control -->\r\n <div\r\n *ngSwitchCase=\"'media'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <ng-container\r\n *ngTemplateOutlet=\"mediaContent; context: { layout: layout, record: record }\">\r\n </ng-container>\r\n </div>\r\n\r\n <!-- String Control -->\r\n <div *ngSwitchCase=\"'string'\"\r\n [class]=\"layout.option?.visible ? 'card-field-wrapper' : 'hidden'\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(layout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(layout.label?.style).classes\">\r\n {{ getHorizontalLayoutCaption(layout) }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n @if(layout.option && layout.option.control &&\r\n !layout.option.protected){\r\n @switch (layout.option.control) {\r\n @case ('textInput') {\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ??\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ??\r\n '--'\r\n }}\r\n </p>\r\n }\r\n @case ('password') {\r\n <span>Switch not supported yet</span>\r\n\r\n }\r\n @case ('combobox') {\r\n <span>Combobox not supported yet</span>\r\n }\r\n @case('textArea'){\r\n <span>TextArea not supported yet</span>\r\n }\r\n @case ('switch') {\r\n <span>Switch not supported yet</span>\r\n }\r\n\r\n @case('radiobutton') {\r\n <span>Radiobutton not supported yet</span>\r\n }\r\n\r\n @case ('dropdown') {\r\n <select id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(layout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style)?.style\"\r\n [name]=\"layout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)][\r\n layout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[\r\n layout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[layout?.option?.optionValue]\">\r\n {{ item[layout?.option?.optionLabel] }}\r\n </option>\r\n </select>\r\n }\r\n\r\n @case ('autocomplete') {\r\n <p-autocomplete\r\n [suggestions]=\"remoteSuggestions[layout.scope?.reference] ?? []\"\r\n (completeMethod)=\"completeRemote($event, layout)\"\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"layout.option.required\"\r\n [name]=\"layout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"layout?.scope?.style?.classes ? layout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"layout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: layout.scope?.reference\"\r\n [vlCustomKeyboard]=\"layout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n }\r\n }\r\n }@else {\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ??\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ??\r\n '--'\r\n }}\r\n </p>\r\n }\r\n\r\n </div>\r\n\r\n <!-- Date Control -->\r\n <div *ngSwitchCase=\"'date'\"\r\n [class]=\"layout.option?.visible ? 'card-field-wrapper' : 'hidden'\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <!-- LABEL -->\r\n <span *ngIf=\"layout?.label?.caption\"\r\n [class]=\"layout.label?.style?.classes?.[0]\">\r\n {{ layout.label.caption }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ?? itemInfo.propertySchema[\r\n getScope(layout.scope?.reference)\r\n ]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ?? '--'\r\n }}\r\n </p>\r\n </div>\r\n\r\n <!-- Time Control -->\r\n <div *ngSwitchCase=\"'time'\"\r\n [class]=\"layout.option?.visible ? 'card-field-wrapper' : 'hidden'\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span *ngIf=\"layout?.label?.caption\"\r\n [class]=\"layout.label?.style?.classes?.[0]\">\r\n {{ layout.label.caption }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <p\r\n *ngIf=\"layout.option?.protected && layout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n (click)=\"showLinkDetails(layout.option, record)\">\r\n {{\r\n record?.[layout.scope?.reference.split('/').pop()]\r\n ?? itemInfo.propertySchema[\r\n getScope(layout.scope?.reference)\r\n ]?.[layout.scope?.reference.split('/').pop()]?.default\r\n ?? '--'\r\n }}\r\n </p>\r\n </div>\r\n\r\n <!-- number Control -->\r\n <div *ngSwitchCase=\"'number'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <!-- VALUE -->\r\n @if(layout.option && layout.option.control &&\r\n !layout.option.protected){\r\n @switch (layout.option.control) {\r\n @case ('textInput') {\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n {{\r\n record[layout.scope?.reference.split('/').pop()]\r\n | currency: layout.option?.currency\r\n ?\r\n layout.option.currency\r\n :\r\n 'USD' }}\r\n </h3>\r\n }\r\n @case ('slider') {\r\n <p>Slider not Implemented yet</p>\r\n\r\n }\r\n @case ('spinner') {\r\n <p>Spinner not Implemented yet</p>\r\n }\r\n }\r\n } @else{\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n {{\r\n record[layout.scope?.reference.split('/').pop()]\r\n | currency: layout.option?.currency\r\n ?\r\n layout.option.currency\r\n :\r\n 'USD' }}\r\n </h3>\r\n }\r\n\r\n </div>\r\n\r\n <!-- dropdown Control -->\r\n <div *ngSwitchCase=\"'dropdown'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <select id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(layout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style)?.style\"\r\n [name]=\"layout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)][\r\n layout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(layout.scope?.reference)]?.[\r\n layout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[layout?.option?.optionValue]\">\r\n {{ item[layout?.option?.optionLabel]\r\n }}\r\n </option>\r\n </select>\r\n </h3>\r\n </div>\r\n\r\n <!-- autocomplete Control -->\r\n <div *ngSwitchCase=\"'autocomplete'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <p-autocomplete\r\n [suggestions]=\"remoteSuggestions[layout.scope?.reference] ?? []\"\r\n (completeMethod)=\"completeRemote($event, layout)\"\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"layout.option.required\"\r\n [name]=\"layout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [class]=\"layout?.scope?.style?.classes ? layout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"layout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: layout.scope?.reference\"\r\n [vlCustomKeyboard]=\"layout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n </h3>\r\n </div>\r\n\r\n <!-- checkbox Control -->\r\n <div *ngSwitchCase=\"'boolean'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(layout.scope?.reference)] && itemInfo.propertySchema[getScope(layout.scope?.reference)][layout.scope?.reference.split('/').pop()]\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(layout?.scope?.style).style\">\r\n <input type=\"checkbox\"\r\n class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(layout.scope?.reference)][\r\n layout.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [name]=\"layout.scope?.reference\" />\r\n </h3>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\"\r\n [class]=\"layout.option?.visible ? 'row' : 'hidden'\">\r\n <button\r\n [style]=\"getStyleWithoutClasses(layout?.style).style\"\r\n [class]=\"layout.option.protected ? 'disabled-button' : getStyleWithoutClasses(layout?.style).classes\"\r\n (click)=\"\r\n invokeButtonClick(\r\n layout,\r\n record,\r\n getRecordIndex(record)\r\n )\r\n\">\r\n <ng-container\r\n *ngIf=\"!layout.caption; else textContent\">\r\n <i nz-icon [nzType]=\"layout.icon\"\r\n nzTheme=\"outline\"></i>\r\n </ng-container>\r\n <ng-template #textContent>\r\n {{ layout.caption }}\r\n </ng-template>\r\n </button>\r\n </div>\r\n\r\n <!-- HorizontalLayout Control -->\r\n <div *ngSwitchCase=\"'HorizontalLayout'\">\r\n <div class=\"card-horizontal-layout\"\r\n [class]=\"getStyleWithoutClasses(\r\n layout?.style\r\n ).classes\" [ngStyle]=\"getHorizontalLayoutStyle(layout)\">\r\n <ng-container\r\n *ngFor=\"let innerLayout of layout.elements\">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(innerLayout.scope?.reference && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] ? itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.type : 'default')\">\r\n <!-- Media Control -->\r\n <div\r\n *ngSwitchCase=\"'media'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n\r\n <ng-container\r\n *ngTemplateOutlet=\"mediaContent; context: {\r\n layout: innerLayout,\r\n record: record\r\n }\">\r\n </ng-container>\r\n </div>\r\n\r\n <!-- String Control -->\r\n <div *ngSwitchCase=\"'string'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(innerLayout.label?.style).classes\">\r\n {{\r\n getHorizontalLayoutCaption(innerLayout)\r\n }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n @if(innerLayout.option &&\r\n innerLayout.option.control &&\r\n !innerLayout.option.protected){\r\n @switch\r\n (innerLayout.option.control)\r\n {\r\n @case ('textInput') {\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n\r\n <!-- DATE FORMAT -->\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.dateFormat; else checkInnerTime\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option.dateFormat)\r\n : '--' }}\r\n </ng-container>\r\n\r\n <!-- TIME FORMAT -->\r\n <ng-template\r\n #checkInnerTime>\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.timeFormat; else normalInnerValue\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n |\r\n date:\r\n innerLayout.option.timeFormat)\r\n : '--' }}\r\n </ng-container>\r\n </ng-template>\r\n\r\n <!-- NORMAL STRING -->\r\n <ng-template\r\n #normalInnerValue>\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ??\r\n '--' }}\r\n </ng-template>\r\n </h3>\r\n }\r\n @case ('password') {\r\n <span>Switch not supported\r\n yet</span>\r\n\r\n }\r\n @case ('combobox') {\r\n <span>Combobox not supported\r\n yet</span>\r\n }\r\n @case('textArea'){\r\n <span>TextArea not supported\r\n yet</span>\r\n }\r\n @case ('switch') {\r\n <span>Switch not supported\r\n yet</span>\r\n }\r\n\r\n @case('radiobutton') {\r\n <span>Radiobutton not supported\r\n yet</span>\r\n }\r\n\r\n @case ('dropdown') {\r\n <select id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.style\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][\r\n innerLayout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[\r\n innerLayout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[innerLayout?.option?.optionValue]\">\r\n {{\r\n item[innerLayout?.option?.optionLabel]\r\n }}\r\n </option>\r\n </select>\r\n }\r\n\r\n @case ('autocomplete') {\r\n\r\n <p-autocomplete\r\n [suggestions]=\"remoteSuggestions[innerLayout.scope?.reference] ?? []\"\r\n (completeMethod)=\"completeRemote($event, innerLayout)\"\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"innerLayout.option.required\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n [class]=\"innerLayout?.scope?.style?.classes ? innerLayout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"innerLayout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: innerLayout.scope?.reference\"\r\n [vlCustomKeyboard]=\"innerLayout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n }\r\n }\r\n }@else{\r\n\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n\r\n <!-- DATE FORMAT -->\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.dateFormat; else checkInnerTime\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option.dateFormat)\r\n : '--' }}\r\n </ng-container>\r\n\r\n <!-- TIME FORMAT -->\r\n <ng-template\r\n #checkInnerTime>\r\n <ng-container\r\n *ngIf=\"innerLayout.option?.timeFormat; else normalInnerValue\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n |\r\n date:\r\n innerLayout.option.timeFormat)\r\n : '--' }}\r\n </ng-container>\r\n </ng-template>\r\n\r\n <!-- NORMAL STRING -->\r\n <ng-template\r\n #normalInnerValue>\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ??\r\n '--' }}\r\n </ng-template>\r\n </h3>\r\n }\r\n\r\n </div>\r\n\r\n <!-- number Control -->\r\n <div *ngSwitchCase=\"'number'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n {{\r\n record[innerLayout.scope?.reference.split('/').pop()]\r\n | currency:\r\n innerLayout.option?.currency\r\n ?\r\n innerLayout.option.currency\r\n :\r\n 'USD'\r\n }}\r\n </h3>\r\n </div>\r\n\r\n <!-- Date Control -->\r\n <div *ngSwitchCase=\"'date'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(innerLayout.label?.style).classes\">\r\n {{\r\n getHorizontalLayoutCaption(innerLayout)\r\n }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option?.dateFormat\r\n ?\r\n innerLayout.option?.dateFormat\r\n :\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[innerLayout.scope?.reference.split('/').pop()]?.format\r\n || 'yyyy-MM-dd') : '--' }}\r\n </h3>\r\n </div>\r\n\r\n <!-- Time Control -->\r\n <div *ngSwitchCase=\"'time'\"\r\n [ngClass]=\"[innerLayout?.option?.visible ? 'card-field-wrapper' : 'hidden',getStyleWithoutClasses(innerLayout?.style).classes]\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.style).style\">\r\n\r\n <!-- LABEL -->\r\n <span\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout.label?.style).style\"\r\n [class]=\"getStyleWithoutClasses(innerLayout.label?.style).classes\">\r\n {{\r\n getHorizontalLayoutCaption(innerLayout)\r\n }}\r\n </span>\r\n\r\n <!-- VALUE -->\r\n <h3\r\n *ngIf=\"innerLayout.option?.protected && innerLayout.option?.visible\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n (click)=\"showLinkDetails(innerLayout.option, record)\">\r\n {{\r\n record?.[innerLayout.scope?.reference.split('/').pop()]\r\n ?\r\n (record[innerLayout.scope?.reference.split('/').pop()]\r\n | date:\r\n innerLayout.option?.timeFormat\r\n ?\r\n innerLayout.option?.timeFormat\r\n :itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[innerLayout.scope?.reference.split('/').pop()]?.format\r\n || 'HH:mm') : '--' }}\r\n </h3>\r\n </div>\r\n\r\n <!-- dropdown Control -->\r\n <div *ngSwitchCase=\"'dropdown'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n <select\r\n id=\"dynamic-dropdown\"\r\n #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(innerLayout?.scope?.style)?.style\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][\r\n innerLayout.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of itemInfo.propertySchema[getScope(innerLayout.scope?.reference)]?.[\r\n innerLayout.scope?.reference.split('/').pop()\r\n ]?.data\r\n \"\r\n [value]=\"item[innerLayout?.option?.optionValue]\">\r\n {{\r\n item[innerLayout?.option?.optionLabel]\r\n }}\r\n </option>\r\n </select>\r\n </h3>\r\n </div>\r\n\r\n <!-- autocomplete Control -->\r\n <div *ngSwitchCase=\"'autocomplete'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]?.format?.currency\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n <p-autocomplete\r\n [suggestions]=\"remoteSuggestions[innerLayout.scope?.reference] ?? []\"\r\n (completeMethod)=\"completeRemote($event, innerLayout)\"\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"innerLayout.option.required\"\r\n [name]=\"innerLayout.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\"\r\n [class]=\"innerLayout?.scope?.style?.classes ? innerLayout?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"innerLayout.option.autocomplete.response.label\"\r\n [placeholder]=\"itemInfo.propertySchema | vlPlaceholder: innerLayout.scope?.reference\"\r\n [vlCustomKeyboard]=\"innerLayout?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n </h3>\r\n </div>\r\n\r\n <!-- checkbox Control -->\r\n <div *ngSwitchCase=\"'boolean'\"\r\n [class]=\"innerLayout.option?.visible ? 'row' : 'hidden'\">\r\n <h3\r\n *ngIf=\"itemInfo.propertySchema[getScope(innerLayout.scope?.reference)] && itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][innerLayout.scope?.reference.split('/').pop()]\"\r\n [class]=\"getStyleWithoutClasses(innerLayout?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.scope?.style).style\">\r\n <input type=\"checkbox\"\r\n class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"\r\n itemInfo.propertySchema[getScope(innerLayout.scope?.reference)][\r\n innerLayout.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [name]=\"innerLayout.scope?.reference\" />\r\n </h3>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\">\r\n <button nz-button\r\n [matTooltip]=\"getHorizontalLayoutTooltip(innerLayout)\"\r\n [matTooltipTouchGestures]=\"'auto'\"\r\n [style]=\"getStyleWithoutClasses(innerLayout?.style).style\"\r\n [class]=\"innerLayout.option?.visible ? innerLayout.option.protected ? 'disabled-button' : getStyleWithoutClasses(innerLayout?.style).classes : 'hidden'\"\r\n (click)=\"\r\n invokeButtonClick(\r\n innerLayout,\r\n record,\r\n getRecordIndex(record)\r\n )\r\n\">\r\n <span\r\n class=\"schema-btn-content\">\r\n <i\r\n *ngIf=\"showButtonIcon(innerLayout)\"\r\n nz-icon\r\n [nzType]=\"innerLayout.icon\"\r\n nzTheme=\"outline\">\r\n </i>\r\n\r\n <span\r\n *ngIf=\"showButtonCaption(innerLayout)\">\r\n {{\r\n innerLayout.caption\r\n }}\r\n </span>\r\n\r\n <span *ngIf=\"\r\n !showButtonIcon(innerLayout) &&\r\n !showButtonCaption(innerLayout)\r\n \">\r\n Button\r\n </span>\r\n </span>\r\n </button>\r\n </div>\r\n\r\n </ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div\r\n *ngIf=\"itemInfo?.footer?.elements && itemInfo?.data && itemInfo?.data.length\"\r\n [class]=\"getStyleWithoutClasses(itemInfo?.footer?.style).classes\">\r\n <span\r\n *ngFor=\"let element of itemInfo.footer?.elements; let c = index\">\r\n <ng-container\r\n [ngSwitch]=\"renderFieldType(element.scope?.reference && itemInfo.propertySchema[getScope(element.scope?.reference)] ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.type : 'default')\">\r\n\r\n <!-- Date control -->\r\n <div *ngSwitchCase=\"'date'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"element.option.protected\"\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">\r\n <h5>{{\r\n itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.caption\r\n }}\r\n </h5>\r\n </span>\r\n <div *ngIf=\"!element.option.protected\">\r\n <div\r\n [class]=\"element?.layout == 'horizontal' ? 'horizontal-layout' : 'row'\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(element?.label?.style).style\"\r\n [class]=\"element?.label?.style?.classes[0]\">{{\r\n element.label?.value\r\n ?\r\n element.label?.value :\r\n element.label?.caption ?\r\n element.label?.caption :\r\n itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n\r\n <nz-date-picker\r\n [required]=\"element.option.required\"\r\n [name]=\"element.scope?.reference\"\r\n [nzDisabled]=\"element?.option?.disabled\"\r\n (ngModelChange)=\"dateChangeEvent(element, $event)\"\r\n [nzFormat]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.format ? itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()]?.format : 'yyyy-MM-dd'\"\r\n [(ngModel)]=\"itemInfo.propertySchema[getScope(element.scope?.reference)][element.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(element?.style).style\"></nz-date-picker>\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Button Control -->\r\n <div *ngSwitchCase=\"'default'\"\r\n [class]=\"element.option?.visible ? 'row' : 'hidden'\"\r\n (click)=\"invokeOnCardFooter(element)\">\r\n <button nz-button\r\n [style]=\"getStyleWithoutClasses(element.style).style\"\r\n [class]=\"element.option.protected ? 'disabled-button' : getStyleWithoutClasses(element.style).classes\">\r\n <ng-container\r\n *ngIf=\"!element.caption; else textContent\">\r\n <i nz-icon [nzType]=\"element.icon\"\r\n nzTheme=\"outline\"></i>\r\n </ng-container>\r\n <ng-template #textContent>\r\n {{ element.caption }}\r\n </ng-template>\r\n </button>\r\n </div>\r\n </ng-container>\r\n </span>\r\n </div>\r\n <!-- bottom pagination on card -->\r\n <div *ngIf=\"\r\n itemInfo?.data?.length && \r\n itemInfo?.layoutSchema?.pagination?.visible &&\r\n !itemInfo?.layoutSchema?.pagination?.top &&\r\n !scrollPagination\r\n \" class=\"card-pagination-wrapper\">\r\n <vl-pagination [itemInfo]=\"itemInfo\"\r\n [selectedLanguageInfo]=\"selectedLanguageInfo\"\r\n [pageSize]=\"pageSize\"\r\n [currentPage]=\"currentPage\" [pages]=\"pages\"\r\n (pageSizeChange)=\"invokeOnPageSizeChange($event)\"\r\n (onGoToPage)=\"invokeGoToPage($event)\">\r\n </vl-pagination>\r\n </div>\r\n\r\n @if (scrollPagination) {\r\n <div #scrollPaginationSentinel class=\"scroll-pagination-sentinel\"\r\n aria-hidden=\"true\">\r\n </div>\r\n }\r\n</div>\r\n<ng-template #mediaContent let-layout=\"layout\" let-record=\"record\">\r\n <ng-container *ngIf=\"getMediaSource(layout, record) as source\">\r\n <ng-container [ngSwitch]=\"getMediaKind(layout, record)\">\r\n\r\n <!-- Default: image. Keep your existing image-click behaviour. -->\r\n <img\r\n *ngSwitchDefault\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [src]=\"source\"\r\n [alt]=\"getMediaLabel(layout)\"\r\n loading=\"lazy\"\r\n (click)=\"showImageDetails(layout.option, record)\" />\r\n\r\n <video\r\n *ngSwitchCase=\"'video'\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [src]=\"source\"\r\n [attr.aria-label]=\"getMediaLabel(layout)\"\r\n controls\r\n playsinline\r\n preload=\"metadata\">\r\n Your browser does not support video playback.\r\n </video>\r\n\r\n <audio\r\n *ngSwitchCase=\"'audio'\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [src]=\"source\"\r\n [attr.aria-label]=\"getMediaLabel(layout)\"\r\n controls\r\n preload=\"metadata\">\r\n Your browser does not support audio playback.\r\n </audio>\r\n\r\n <a\r\n *ngSwitchCase=\"'pdf'\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [href]=\"source\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\">\r\n Open PDF \u2014 {{ getMediaLabel(layout) }}\r\n </a>\r\n\r\n <a\r\n *ngSwitchCase=\"'file'\"\r\n [class]=\"getStyleWithoutClasses(layout?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(layout?.scope?.style).style\"\r\n [href]=\"source\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\">\r\n Open file \u2014 {{ getMediaLabel(layout) }}\r\n </a>\r\n\r\n </ng-container>\r\n </ng-container>\r\n</ng-template>", styles: [":host{display:block;width:100%;min-width:0}.vl-card-runtime{min-width:0;box-sizing:border-box}.card-responsive-grid{width:100%;min-width:0;box-sizing:border-box}.scroll-pagination-sentinel{width:100%;height:1px}.card-grid-item,.card-layout-item,.card-horizontal-layout,.card-field-wrapper{width:100%;min-width:0;box-sizing:border-box}.card-field-wrapper{overflow-wrap:anywhere}.card-field-wrapper p,.card-field-wrapper h3{margin:0;min-width:0}.card-layout-item img{display:block;max-width:100%;height:auto}.card-layout-item button{max-width:100%;box-sizing:border-box}.schema-btn-content{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-width:0}.hidden{display:none!important}.card-pagination-wrapper{width:100%;min-width:0;box-sizing:border-box;padding:0 10px}\n"] }]
|
|
4445
|
+
}], ctorParameters: () => [{ type: UtilityService }, { type: i1.Platform }, { type: i1$2.HttpClient }, { type: ConfigService }, { type: i0.ChangeDetectorRef }, { type: i0.NgZone }, { type: i5.NzModalService }, { type: NotificationService }, { type: i7.OAuthService }, { type: RequestService }], propDecorators: { itemInfo: [{
|
|
4343
4446
|
type: Input
|
|
4344
4447
|
}], currentPage: [{
|
|
4345
4448
|
type: Input
|
|
@@ -4742,8 +4845,63 @@ class FormComponent {
|
|
|
4742
4845
|
timeChangeEvent(formField, event) { }
|
|
4743
4846
|
autocompChangeEvent(event, formfield) { }
|
|
4744
4847
|
autocompFocusOutEvent(event, formfield) { }
|
|
4848
|
+
// Media rendering only; does not modify the schema or field values.
|
|
4849
|
+
getMediaProperty(field) {
|
|
4850
|
+
const reference = field?.scope?.reference;
|
|
4851
|
+
const key = reference?.split('/').pop();
|
|
4852
|
+
return key ? this.form?.propertySchema?.[this.getScope(reference)]?.[key] : undefined;
|
|
4853
|
+
}
|
|
4854
|
+
getMediaSource(field) {
|
|
4855
|
+
const property = this.getMediaProperty(field);
|
|
4856
|
+
const value = field?.option?.src || property?.value || property?.default || '';
|
|
4857
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
4858
|
+
}
|
|
4859
|
+
getMediaLabel(field) {
|
|
4860
|
+
if (typeof field?.label === 'string' && field.label.trim())
|
|
4861
|
+
return field.label;
|
|
4862
|
+
return field?.label?.value || field?.label?.caption
|
|
4863
|
+
|| this.getMediaProperty(field)?.caption || 'Media';
|
|
4864
|
+
}
|
|
4865
|
+
getMediaKind(field) {
|
|
4866
|
+
const source = this.getMediaSource(field);
|
|
4867
|
+
const mime = /^data:([^;,]+)/i.exec(source)?.[1]?.toLowerCase();
|
|
4868
|
+
if (mime?.startsWith('image/'))
|
|
4869
|
+
return 'image';
|
|
4870
|
+
if (mime?.startsWith('video/'))
|
|
4871
|
+
return 'video';
|
|
4872
|
+
if (mime?.startsWith('audio/'))
|
|
4873
|
+
return 'audio';
|
|
4874
|
+
if (mime === 'application/pdf')
|
|
4875
|
+
return 'pdf';
|
|
4876
|
+
const property = this.getMediaProperty(field);
|
|
4877
|
+
const candidates = [source, field?.option?.fileName, property?.value, property?.default];
|
|
4878
|
+
const videos = ['mp4', 'm4v', 'webm', 'mov', 'ogv', 'avi', 'mkv'];
|
|
4879
|
+
const audios = ['mp3', 'wav', 'ogg', 'oga', 'm4a', 'aac', 'flac', 'opus'];
|
|
4880
|
+
const files = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'csv', 'zip', 'rar', '7z'];
|
|
4881
|
+
for (const candidate of candidates) {
|
|
4882
|
+
if (typeof candidate !== 'string' || /^(data|blob):/i.test(candidate))
|
|
4883
|
+
continue;
|
|
4884
|
+
let path = candidate.split(/[?#]/)[0];
|
|
4885
|
+
try {
|
|
4886
|
+
path = decodeURIComponent(path);
|
|
4887
|
+
}
|
|
4888
|
+
catch { /* Keep the original path. */ }
|
|
4889
|
+
const extension = /\.([a-z0-9]+)$/i.exec(path)?.[1]?.toLowerCase();
|
|
4890
|
+
if (!extension)
|
|
4891
|
+
continue;
|
|
4892
|
+
if (videos.includes(extension))
|
|
4893
|
+
return 'video';
|
|
4894
|
+
if (audios.includes(extension))
|
|
4895
|
+
return 'audio';
|
|
4896
|
+
if (extension === 'pdf')
|
|
4897
|
+
return 'pdf';
|
|
4898
|
+
if (files.includes(extension))
|
|
4899
|
+
return 'file';
|
|
4900
|
+
}
|
|
4901
|
+
return 'image';
|
|
4902
|
+
}
|
|
4745
4903
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: FormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4746
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: FormComponent, isStandalone: true, selector: "vl-form", inputs: { errorMessages: "errorMessages", creditCardError: "creditCardError", form: "form", selectedLanguageInfo: "selectedLanguageInfo" }, outputs: { formButtonClick: "formButtonClick", checkBoxChangeEvent: "checkBoxChangeEvent" }, usesOnChanges: true, ngImport: i0, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: autocompletes get their suggestions from RemoteListService (completeRemote) -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\r\n project defines none; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - change: autocomplete fields take the toolkit string keyboard unless option.keyboardType\r\n names another one ('native' keeps the system keyboard)\r\n - change: date and time fields use the shared picker markup (picker-field, DateTimePickerService)\r\n instead of PrimeNG's own icon with empty handlers -->\r\n\r\n<!-- Shuaib 2026-09-24\r\n - change: the form field switch uses renderFieldType so legacy checkbox fields render as boolean. -->\r\n\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - change: the credit card type is spelled creditCard. The rename of 2026-09-15 reached\r\n model.component.html and the whole Designer but not this file, so one data object rendered\r\n in a modal and stayed empty here\r\n - new: a @contract comment on the type switch and on the credit card case, so the rule sits\r\n next to the code that implements it -->\r\n<div class=\"form-renderer\"\r\n [ngClass]=\"getStyleWithoutClasses(form?.layoutSchema?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(form?.layoutSchema?.style)?.style\">\r\n <form *ngIf=\"form?.layoutSchema?.formSections?.length > 0\">\r\n <div\r\n *ngFor=\"let formSection of form?.layoutSchema?.formSections; let i = index\"\r\n class=\"section-card\">\r\n <div *ngIf=\"formSection?.label?.caption\" class=\"section-header\">\r\n <div class=\"section-title\"\r\n [ngClass]=\"getStyleWithoutClasses(formSection?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formSection?.label?.style)?.style\">\r\n {{ formSection.label.caption }}\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"formSection?.formFields?.length > 0\" class=\"section-body\"\r\n [ngClass]=\"getStyleWithoutClasses(formSection?.style)?.classes\"\r\n [ngStyle]=\"getSectionBodyStyle(formSection)\">\r\n <div *ngFor=\"let formField of formSection.formFields\" class=\"field-row\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.style)?.classes\"\r\n [ngStyle]=\"getFieldWrapperStyle(formField)\">\r\n <!-- @contract decision: this switch decides whether a field appears at all\r\n reality: renders string, number, image, date, time, dropdown, boolean,\r\n autocomplete, dateRange and the credit card type. Everything else\r\n falls into the default branch and the field renders empty - no error,\r\n nothing in the console either. Existing checkbox fields are normalized\r\n to boolean before this switch; new fields should use boolean.\r\n seen: measured 2026-09-19 with viewdefinition-contract/derive-capabilities.sh\r\n against this file; date, time, dropdown, boolean, autocomplete and\r\n dateRange arrived on 2026-09-18 -->\r\n <div [ngSwitch]=\"renderFieldType(\r\n formField?.scope?.reference &&\r\n form?.propertySchema?.[getScope(formField.scope?.reference)]\r\n ? form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.type\r\n : 'default'\r\n )\">\r\n <!-- image control -->\r\n <div *ngSwitchCase=\"'image'\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <img [src]=\"formField?.option?.src\" [alt]=\"'img'\" />\r\n </div>\r\n\r\n <!-- credit card control -->\r\n <!-- @contract decision: the credit card type is spelled creditCard\r\n reality: it was carditCard here until 2026-09-19. The rename of 2026-09-15\r\n reached model.component.html and the whole Designer, but not this\r\n file and not utility.service.ts, so the same data object rendered in\r\n one container and stayed empty in the other. All four places agree now.\r\n failure: a data object still declaring carditCard renders empty everywhere -\r\n that spelling is read nowhere any more.\r\n instead: declare the type as creditCard. -->\r\n <div *ngSwitchCase=\"'creditCard'\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <div id=\"card-element\"></div>\r\n <div id=\"card-errors\"\r\n [class]=\"creditCardError?.textContent !== '' ? 'text-red-400 m-0' : ''\"></div>\r\n </div>\r\n\r\n <!-- string control -->\r\n <span *ngSwitchCase=\"'string'\" class=\"form-group\">\r\n <span *ngIf=\"formField?.option?.protected\">\r\n <h5 style=\"display: flex; justify-content: space-between\">\r\n <span\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </span>\r\n\r\n <span>\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.value\r\n }}\r\n </span>\r\n </h5>\r\n </span>\r\n\r\n <span *ngIf=\"!formField?.option?.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n formField?.label?.value\r\n ? formField.label.value\r\n : formField?.label?.caption\r\n ? formField.label.caption\r\n :\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n\r\n <div [ngStyle]=\"\r\n formField?.option?.password\r\n ? {\r\n position: 'relative',\r\n display: 'flex',\r\n alignItems: 'center'\r\n }\r\n : {}\r\n \">\r\n <input [type]=\"\r\n formField?.option?.password\r\n ? !showPassword\r\n ? 'password'\r\n : 'text'\r\n : 'text'\r\n \" #inputField=\"ngModel\" [required]=\"formField?.required\"\r\n [attr.inputmode]=\"\r\n formField?.option?.email\r\n ? 'email'\r\n : formField?.option?.password\r\n ? 'numeric'\r\n : 'text'\r\n \" [pattern]=\"formField?.email ? customEmailPattern : ''\"\r\n [email]=\"formField?.email\"\r\n [minlength]=\"formField?.minlength\"\r\n [maxlength]=\"formField?.maxlength\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\" [placeholder]=\"form.propertySchema | vlPlaceholder: formField.scope?.reference\" />\r\n\r\n <i *ngIf=\"formField?.option?.password\" class=\"fa\"\r\n [ngClass]=\"showPassword ? 'fa-eye' : 'fa-eye-slash'\"\r\n (click)=\"togglePasswordVisibility()\"\r\n style=\"position: absolute; right: 10px; cursor: pointer\"></i>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"inputField?.touched && inputField?.invalid\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.validation?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.validation?.style)?.style\">\r\n <p [class]=\"\r\n formField?.layout === 'horizontal'\r\n ? 'text-red-400 m-0 horizontal-error'\r\n : 'text-red-400 m-0'\r\n \" *ngIf=\"inputField?.errors\">\r\n {{ setErroMessage(formField, errorMessages, inputField) }}\r\n </p>\r\n </div>\r\n </span>\r\n </span>\r\n\r\n <!-- number control -->\r\n <div *ngSwitchCase=\"'number'\" class=\"form-group\">\r\n <span *ngIf=\"formField?.option?.protected\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n <h5>\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.value\r\n | currency\r\n : formField?.option?.currency\r\n : true\r\n : formField?.option?.decimalPoint\r\n }}\r\n </h5>\r\n </span>\r\n\r\n <span *ngIf=\"!formField?.option?.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n formField?.label?.value\r\n ? formField.label.value\r\n :\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n\r\n <input type=\"number\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\" [placeholder]=\"form.propertySchema | vlPlaceholder: formField.scope?.reference\" />\r\n </div>\r\n </span>\r\n </div>\r\n\r\n <!-- checkbox control -->\r\n <div *ngSwitchCase=\"'boolean'\" class=\"form-group\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <span *ngIf=\"formField?.option?.postfixCaption\">\r\n <label class=\"form-check-label\">\r\n <input type=\"checkbox\" class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\"\r\n (ngModelChange)=\"setformData()\" />\r\n\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n </span>\r\n\r\n <div *ngIf=\"!formField?.option?.postfixCaption\"\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label class=\"form-check-label\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n\r\n <input type=\"checkbox\" class=\"form-check-input\" [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\"\r\n (ngModelChange)=\"setformData()\" />\r\n </label>\r\n </div>\r\n </div>\r\n\r\n <!-- DateRange control -->\r\n <div *ngSwitchCase=\"'dateRange'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{\r\n formField.label?.value\r\n ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <nz-range-picker #dateRangeField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [(ngModel)]=\"form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"></nz-range-picker>\r\n </div>\r\n <!-- Error Message -->\r\n <div *ngIf=\"dateRangeField?.touched && dateRangeField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateRangeField?.errors?.['required']\">\r\n {{formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n +\r\n ' ' + errorMessages.required}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateRangeField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Date control -->\r\n <div *ngSwitchCase=\"'date'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{getLabel(formField)}}</label>\r\n\r\n <span>\r\n <div class=\"picker-field\">\r\n <p-datepicker #datePicker #dateField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n [disabled]=\"formField?.option?.disabled\"\r\n [autoZIndex]=\"true\" [baseZIndex]=\"10000\"\r\n (ngModelChange)=\"dateChangeEvent(formField, $event)\"\r\n (onInput)=\"onDateTyping($event)\" [dateFormat]=\"\r\n form.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ]?.format || 'yy-mm-dd'\r\n \" [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [class]=\"formField?.scope?.style?.classes?.[0] || ''\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\" appendTo=\"body\"\r\n [panelStyleClass]=\"picker.datePanelClass\" [motionOptions]=\"picker.dockedMotion\"\r\n [vlCustomKeyboard]=\"formField?.option?.keyboardType ?? 'date'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, datePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(datePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\" aria-label=\"Open calendar\"\r\n (pointerdown)=\"picker.openFromIcon($event, datePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-calendar\"></i>\r\n </button>\r\n </div>\r\n <!-- Error Message -->\r\n <div *ngIf=\"dateField?.touched && dateField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages,\r\n \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Time control -->\r\n <div *ngSwitchCase=\"'time'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{getLabel(formField)}}</label>\r\n <span>\r\n <div class=\"picker-field\">\r\n <p-datepicker #timePicker #timeField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\" [timeOnly]=\"true\"\r\n hourFormat=\"24\" [disabled]=\"formField?.option?.disabled\" [autoZIndex]=\"true\" [baseZIndex]=\"10000\"\r\n (onInput)=\"onTimeTyping($event)\"\r\n (ngModelChange)=\"timeChangeEvent(formField, $event)\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [class]=\"formField?.scope?.style?.classes?.[0] || ''\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\" appendTo=\"body\"\r\n [panelStyleClass]=\"picker.timePanelClass\"\r\n [vlCustomKeyboard]=\"formField?.option?.keyboardType ?? 'time'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, timePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(timePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\" aria-label=\"Open time picker\"\r\n (pointerdown)=\"picker.openFromIcon($event, timePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-clock\"></i>\r\n </button>\r\n </div>\r\n\r\n <!-- Error Message -->\r\n <div *ngIf=\"timeField?.touched && timeField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"timeField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages,\r\n \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"timeField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Autocomplete control -->\r\n <div *ngSwitchCase=\"'autocomplete'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <vl-label [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-label>\r\n\r\n <span>\r\n <p-autocomplete [suggestions]=\"remoteSuggestions[formField.scope?.reference] ?? []\" (completeMethod)=\"completeRemote($event, formField)\" #autocompleteField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [class]=\"formField?.scope?.style?.classes ? formField?.scope?.style?.classes[0] : ''\" [dropdown]=\"true\"\r\n [optionLabel]=\"formField.option.autocomplete.response.label\"\r\n [placeholder]=\"form.propertySchema | vlPlaceholder: formField.scope?.reference\"\r\n (ngModelChange)=\"autocompFocusOutEvent($event, formField)\"\r\n [vlCustomKeyboard]=\"formField?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n <!-- Error Message -->\r\n <div\r\n *ngIf=\"autocompleteField?.touched && autocompleteField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages,\r\n \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- dropdown control -->\r\n <div *ngSwitchCase=\"'dropdown'\" class=\"form-group\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <span *ngIf=\"formField?.option?.postfixCaption\">\r\n <select id=\"dynamic-dropdown\" #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [name]=\"formField.scope?.reference\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.data\r\n \" [value]=\"item[formField?.option?.optionValue]\">\r\n {{ item[formField?.option?.optionLabel] }}\r\n </option>\r\n </select>\r\n\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n </span>\r\n\r\n <div *ngIf=\"!formField?.option?.postfixCaption\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n\r\n <nz-select nzShowSearch nzAllowClear nzPlaceHolder=\"Select\"\r\n #dropdownField=\"ngModel\"\r\n [required]=\"formField?.required\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [name]=\"formField.scope?.reference\" [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <nz-option *ngFor=\"\r\n let item of form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.data\r\n \" [nzLabel]=\"item[formField?.option?.optionLabel]\"\r\n [nzValue]=\"item[formField?.option?.optionValue]\"></nz-option>\r\n </nz-select>\r\n </div>\r\n\r\n <div *ngIf=\"dropdownField?.touched && dropdownField?.invalid\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.validation?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.validation?.style)?.style\">\r\n <p [class]=\"\r\n formField?.layout === 'horizontal'\r\n ? 'text-red-400 m-0 horizontal-error'\r\n : 'text-red-400 m-0'\r\n \" *ngIf=\"dropdownField?.errors\">\r\n {{ setErroMessage(formField, errorMessages, dropdownField)\r\n }}\r\n </p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- button/default control -->\r\n <div *ngSwitchCase=\"'default'\">\r\n <button type=\"submit\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.style)?.style\"\r\n (click)=\"invokeFormBtnClick(formField?.option, form.propertySchema)\">\r\n {{ formField?.label?.caption }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0}.form-renderer{width:100%;height:100%;min-height:0;overflow-y:auto;overflow-x:hidden;box-sizing:border-box;padding:12px}.section-card{border:1px solid #e5e7eb;border-radius:var(--radius-sm, 8px);padding:12px;margin-bottom:12px;background:#fff;box-sizing:border-box}.section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;font-weight:600}.section-title{font-size:14px;color:#0f172a}.section-body{position:relative;width:100%;min-height:78px;box-sizing:border-box}.field-row{position:relative;min-width:0;width:100%;box-sizing:border-box}.field-row input,.field-row select,.field-row nz-select,.field-row textarea,.field-row button{max-width:100%;box-sizing:border-box}@media(max-width:768px){.section-body{grid-template-columns:repeat(1,minmax(0,1fr))!important}.field-row{grid-column:span 1!important}}@media(min-width:769px)and (max-width:1024px){.section-body{grid-template-columns:repeat(6,minmax(0,1fr))!important}.field-row{grid-column:span 6!important}}:host-context(.mobile-frame) .section-body,:host-context(.device-mobile) .section-body{grid-template-columns:repeat(1,minmax(0,1fr))!important}:host-context(.mobile-frame) .field-row,:host-context(.device-mobile) .field-row{grid-column:span 1!important}:host-context(.tablet-frame) .section-body,:host-context(.device-tablet) .section-body{grid-template-columns:repeat(6,minmax(0,1fr))!important}:host-context(.tablet-frame) .field-row,:host-context(.device-tablet) .field-row{grid-column:span 6!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i5.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i5.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i5.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i5.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i5.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i2.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i2.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i2.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: NzSelectModule }, { kind: "component", type: i3.NzOptionComponent, selector: "nz-option", inputs: ["nzTitle", "nzLabel", "nzValue", "nzKey", "nzDisabled", "nzHide", "nzCustomContent"], exportAs: ["nzOption"] }, { kind: "component", type: i3.NzSelectComponent, selector: "nz-select", inputs: ["nzId", "nzSize", "nzStatus", "nzVariant", "nzOptionHeightPx", "nzOptionOverflowSize", "nzDropdownClassName", "nzDropdownMatchSelectWidth", "nzDropdownStyle", "nzNotFoundContent", "nzPlaceHolder", "nzPlacement", "nzMaxTagCount", "nzDropdownRender", "nzCustomTemplate", "nzPrefix", "nzSuffixIcon", "nzClearIcon", "nzRemoveIcon", "nzMenuItemSelectedIcon", "nzTokenSeparators", "nzMaxTagPlaceholder", "nzMaxMultipleCount", "nzMode", "nzFilterOption", "compareWith", "nzAllowClear", "nzShowSearch", "nzLoading", "nzAutoFocus", "nzAutoClearSearchValue", "nzServerSearch", "nzDisabled", "nzOpen", "nzSelectOnTab", "nzBackdrop", "nzOptions", "nzShowArrow"], outputs: ["nzOnSearch", "nzScrollToBottom", "nzOpenChange", "nzBlur", "nzFocus", "nzOnClear"], exportAs: ["nzSelect"] }, { kind: "component", type: String$1, selector: "vl-string", inputs: ["formField", "modalItemInfo"] }, { kind: "component", type: Label, selector: "vl-label", inputs: ["formField", "modalItemInfo"] }, { kind: "ngmodule", type: NzDatePickerModule }, { kind: "component", type: i4.NzDatePickerComponent, selector: "nz-date-picker,nz-week-picker,nz-month-picker,nz-quarter-picker,nz-year-picker,nz-range-picker", inputs: ["nzInline", "nzAllowClear", "nzAutoFocus", "nzDisabled", "nzInputReadOnly", "nzOpen", "nzDisabledDate", "nzLocale", "nzPlaceHolder", "nzPopupStyle", "nzDropdownClassName", "nzSize", "nzStatus", "nzFormat", "nzVariant", "nzDateRender", "nzDisabledTime", "nzRenderExtraFooter", "nzShowToday", "nzMode", "nzShowNow", "nzRanges", "nzDefaultPickerValue", "nzSeparator", "nzSuffixIcon", "nzBackdrop", "nzId", "nzPlacement", "nzShowWeekNumber", "nzShowTime"], outputs: ["nzOnPanelChange", "nzOnCalendarChange", "nzOnOk", "nzOnOpenChange"], exportAs: ["nzDatePicker"] }, { kind: "directive", type: i4.NzRangePickerComponent, selector: "nz-range-picker", exportAs: ["nzRangePicker"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i12.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i18.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: CustomKeyboardDirective, selector: "[vlCustomKeyboard]", inputs: ["vlCustomKeyboard"] }, { kind: "pipe", type: PlaceholderPipe, name: "vlPlaceholder" }, { kind: "pipe", type: i5.CurrencyPipe, name: "currency" }] });
|
|
4904
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: FormComponent, isStandalone: true, selector: "vl-form", inputs: { errorMessages: "errorMessages", creditCardError: "creditCardError", form: "form", selectedLanguageInfo: "selectedLanguageInfo" }, outputs: { formButtonClick: "formButtonClick", checkBoxChangeEvent: "checkBoxChangeEvent" }, usesOnChanges: true, ngImport: i0, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: autocompletes get their suggestions from RemoteListService (completeRemote) -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\r\n project defines none; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - change: autocomplete fields take the toolkit string keyboard unless option.keyboardType\r\n names another one ('native' keeps the system keyboard)\r\n - change: date and time fields use the shared picker markup (picker-field, DateTimePickerService)\r\n instead of PrimeNG's own icon with empty handlers -->\r\n\r\n<!-- Shuaib 2026-09-24\r\n - change: the form field switch uses renderFieldType so legacy checkbox fields render as boolean. -->\r\n\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - change: the credit card type is spelled creditCard. The rename of 2026-09-15 reached\r\n model.component.html and the whole Designer but not this file, so one data object rendered\r\n in a modal and stayed empty here\r\n - new: a @contract comment on the type switch and on the credit card case, so the rule sits\r\n next to the code that implements it -->\r\n<div class=\"form-renderer\"\r\n [ngClass]=\"getStyleWithoutClasses(form?.layoutSchema?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(form?.layoutSchema?.style)?.style\">\r\n <form *ngIf=\"form?.layoutSchema?.formSections?.length > 0\">\r\n <div\r\n *ngFor=\"let formSection of form?.layoutSchema?.formSections; let i = index\"\r\n class=\"section-card\">\r\n <div *ngIf=\"formSection?.label?.caption\" class=\"section-header\">\r\n <div class=\"section-title\"\r\n [ngClass]=\"getStyleWithoutClasses(formSection?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formSection?.label?.style)?.style\">\r\n {{ formSection.label.caption }}\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"formSection?.formFields?.length > 0\" class=\"section-body\"\r\n [ngClass]=\"getStyleWithoutClasses(formSection?.style)?.classes\"\r\n [ngStyle]=\"getSectionBodyStyle(formSection)\">\r\n <div *ngFor=\"let formField of formSection.formFields\" class=\"field-row\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.style)?.classes\"\r\n [ngStyle]=\"getFieldWrapperStyle(formField)\">\r\n <!-- @contract decision: this switch decides whether a field appears at all\r\n reality: renders string, number, image, date, time, dropdown, boolean,\r\n autocomplete, dateRange and the credit card type. Everything else\r\n falls into the default branch and the field renders empty - no error,\r\n nothing in the console either. Existing checkbox fields are normalized\r\n to boolean before this switch; new fields should use boolean.\r\n seen: measured 2026-09-19 with viewdefinition-contract/derive-capabilities.sh\r\n against this file; date, time, dropdown, boolean, autocomplete and\r\n dateRange arrived on 2026-09-18 -->\r\n <div [ngSwitch]=\"renderFieldType(\r\n formField?.scope?.reference &&\r\n form?.propertySchema?.[getScope(formField.scope?.reference)]\r\n ? form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.type\r\n : 'default'\r\n )\">\r\n <!-- Media control -->\r\n\r\n <div *ngSwitchCase=\"'media'\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <ng-container *ngTemplateOutlet=\"formMediaContent; context: { field: formField }\"></ng-container>\r\n </div>\r\n\r\n\r\n <!-- credit card control -->\r\n <!-- @contract decision: the credit card type is spelled creditCard\r\n reality: it was carditCard here until 2026-09-19. The rename of 2026-09-15\r\n reached model.component.html and the whole Designer, but not this\r\n file and not utility.service.ts, so the same data object rendered in\r\n one container and stayed empty in the other. All four places agree now.\r\n failure: a data object still declaring carditCard renders empty everywhere -\r\n that spelling is read nowhere any more.\r\n instead: declare the type as creditCard. -->\r\n <div *ngSwitchCase=\"'creditCard'\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <div id=\"card-element\"></div>\r\n <div id=\"card-errors\"\r\n [class]=\"creditCardError?.textContent !== '' ? 'text-red-400 m-0' : ''\"></div>\r\n </div>\r\n\r\n <!-- string control -->\r\n <span *ngSwitchCase=\"'string'\" class=\"form-group\">\r\n <span *ngIf=\"formField?.option?.protected\">\r\n <h5 style=\"display: flex; justify-content: space-between\">\r\n <span\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </span>\r\n\r\n <span>\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.value\r\n }}\r\n </span>\r\n </h5>\r\n </span>\r\n\r\n <span *ngIf=\"!formField?.option?.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n formField?.label?.value\r\n ? formField.label.value\r\n : formField?.label?.caption\r\n ? formField.label.caption\r\n :\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n\r\n <div [ngStyle]=\"\r\n formField?.option?.password\r\n ? {\r\n position: 'relative',\r\n display: 'flex',\r\n alignItems: 'center'\r\n }\r\n : {}\r\n \">\r\n <input [type]=\"\r\n formField?.option?.password\r\n ? !showPassword\r\n ? 'password'\r\n : 'text'\r\n : 'text'\r\n \" #inputField=\"ngModel\" [required]=\"formField?.required\"\r\n [attr.inputmode]=\"\r\n formField?.option?.email\r\n ? 'email'\r\n : formField?.option?.password\r\n ? 'numeric'\r\n : 'text'\r\n \" [pattern]=\"formField?.email ? customEmailPattern : ''\"\r\n [email]=\"formField?.email\"\r\n [minlength]=\"formField?.minlength\"\r\n [maxlength]=\"formField?.maxlength\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\" [placeholder]=\"form.propertySchema | vlPlaceholder: formField.scope?.reference\" />\r\n\r\n <i *ngIf=\"formField?.option?.password\" class=\"fa\"\r\n [ngClass]=\"showPassword ? 'fa-eye' : 'fa-eye-slash'\"\r\n (click)=\"togglePasswordVisibility()\"\r\n style=\"position: absolute; right: 10px; cursor: pointer\"></i>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"inputField?.touched && inputField?.invalid\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.validation?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.validation?.style)?.style\">\r\n <p [class]=\"\r\n formField?.layout === 'horizontal'\r\n ? 'text-red-400 m-0 horizontal-error'\r\n : 'text-red-400 m-0'\r\n \" *ngIf=\"inputField?.errors\">\r\n {{ setErroMessage(formField, errorMessages, inputField) }}\r\n </p>\r\n </div>\r\n </span>\r\n </span>\r\n\r\n <!-- number control -->\r\n <div *ngSwitchCase=\"'number'\" class=\"form-group\">\r\n <span *ngIf=\"formField?.option?.protected\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n <h5>\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.value\r\n | currency\r\n : formField?.option?.currency\r\n : true\r\n : formField?.option?.decimalPoint\r\n }}\r\n </h5>\r\n </span>\r\n\r\n <span *ngIf=\"!formField?.option?.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n formField?.label?.value\r\n ? formField.label.value\r\n :\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n\r\n <input type=\"number\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\" [placeholder]=\"form.propertySchema | vlPlaceholder: formField.scope?.reference\" />\r\n </div>\r\n </span>\r\n </div>\r\n\r\n <!-- checkbox control -->\r\n <div *ngSwitchCase=\"'boolean'\" class=\"form-group\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <span *ngIf=\"formField?.option?.postfixCaption\">\r\n <label class=\"form-check-label\">\r\n <input type=\"checkbox\" class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\"\r\n (ngModelChange)=\"setformData()\" />\r\n\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n </span>\r\n\r\n <div *ngIf=\"!formField?.option?.postfixCaption\"\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label class=\"form-check-label\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n\r\n <input type=\"checkbox\" class=\"form-check-input\" [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\"\r\n (ngModelChange)=\"setformData()\" />\r\n </label>\r\n </div>\r\n </div>\r\n\r\n <!-- DateRange control -->\r\n <div *ngSwitchCase=\"'dateRange'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{\r\n formField.label?.value\r\n ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <nz-range-picker #dateRangeField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [(ngModel)]=\"form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"></nz-range-picker>\r\n </div>\r\n <!-- Error Message -->\r\n <div *ngIf=\"dateRangeField?.touched && dateRangeField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateRangeField?.errors?.['required']\">\r\n {{formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n +\r\n ' ' + errorMessages.required}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateRangeField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Date control -->\r\n <div *ngSwitchCase=\"'date'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{getLabel(formField)}}</label>\r\n\r\n <span>\r\n <div class=\"picker-field\">\r\n <p-datepicker #datePicker #dateField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n [disabled]=\"formField?.option?.disabled\"\r\n [autoZIndex]=\"true\" [baseZIndex]=\"10000\"\r\n (ngModelChange)=\"dateChangeEvent(formField, $event)\"\r\n (onInput)=\"onDateTyping($event)\" [dateFormat]=\"\r\n form.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ]?.format || 'yy-mm-dd'\r\n \" [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [class]=\"formField?.scope?.style?.classes?.[0] || ''\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\" appendTo=\"body\"\r\n [panelStyleClass]=\"picker.datePanelClass\" [motionOptions]=\"picker.dockedMotion\"\r\n [vlCustomKeyboard]=\"formField?.option?.keyboardType ?? 'date'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, datePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(datePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\" aria-label=\"Open calendar\"\r\n (pointerdown)=\"picker.openFromIcon($event, datePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-calendar\"></i>\r\n </button>\r\n </div>\r\n <!-- Error Message -->\r\n <div *ngIf=\"dateField?.touched && dateField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages,\r\n \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Time control -->\r\n <div *ngSwitchCase=\"'time'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{getLabel(formField)}}</label>\r\n <span>\r\n <div class=\"picker-field\">\r\n <p-datepicker #timePicker #timeField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\" [timeOnly]=\"true\"\r\n hourFormat=\"24\" [disabled]=\"formField?.option?.disabled\" [autoZIndex]=\"true\" [baseZIndex]=\"10000\"\r\n (onInput)=\"onTimeTyping($event)\"\r\n (ngModelChange)=\"timeChangeEvent(formField, $event)\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [class]=\"formField?.scope?.style?.classes?.[0] || ''\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\" appendTo=\"body\"\r\n [panelStyleClass]=\"picker.timePanelClass\"\r\n [vlCustomKeyboard]=\"formField?.option?.keyboardType ?? 'time'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, timePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(timePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\" aria-label=\"Open time picker\"\r\n (pointerdown)=\"picker.openFromIcon($event, timePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-clock\"></i>\r\n </button>\r\n </div>\r\n\r\n <!-- Error Message -->\r\n <div *ngIf=\"timeField?.touched && timeField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"timeField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages,\r\n \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"timeField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Autocomplete control -->\r\n <div *ngSwitchCase=\"'autocomplete'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <vl-label [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-label>\r\n\r\n <span>\r\n <p-autocomplete [suggestions]=\"remoteSuggestions[formField.scope?.reference] ?? []\" (completeMethod)=\"completeRemote($event, formField)\" #autocompleteField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [class]=\"formField?.scope?.style?.classes ? formField?.scope?.style?.classes[0] : ''\" [dropdown]=\"true\"\r\n [optionLabel]=\"formField.option.autocomplete.response.label\"\r\n [placeholder]=\"form.propertySchema | vlPlaceholder: formField.scope?.reference\"\r\n (ngModelChange)=\"autocompFocusOutEvent($event, formField)\"\r\n [vlCustomKeyboard]=\"formField?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n <!-- Error Message -->\r\n <div\r\n *ngIf=\"autocompleteField?.touched && autocompleteField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages,\r\n \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- dropdown control -->\r\n <div *ngSwitchCase=\"'dropdown'\" class=\"form-group\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <span *ngIf=\"formField?.option?.postfixCaption\">\r\n <select id=\"dynamic-dropdown\" #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [name]=\"formField.scope?.reference\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.data\r\n \" [value]=\"item[formField?.option?.optionValue]\">\r\n {{ item[formField?.option?.optionLabel] }}\r\n </option>\r\n </select>\r\n\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n </span>\r\n\r\n <div *ngIf=\"!formField?.option?.postfixCaption\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n\r\n <nz-select nzShowSearch nzAllowClear nzPlaceHolder=\"Select\"\r\n #dropdownField=\"ngModel\"\r\n [required]=\"formField?.required\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [name]=\"formField.scope?.reference\" [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <nz-option *ngFor=\"\r\n let item of form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.data\r\n \" [nzLabel]=\"item[formField?.option?.optionLabel]\"\r\n [nzValue]=\"item[formField?.option?.optionValue]\"></nz-option>\r\n </nz-select>\r\n </div>\r\n\r\n <div *ngIf=\"dropdownField?.touched && dropdownField?.invalid\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.validation?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.validation?.style)?.style\">\r\n <p [class]=\"\r\n formField?.layout === 'horizontal'\r\n ? 'text-red-400 m-0 horizontal-error'\r\n : 'text-red-400 m-0'\r\n \" *ngIf=\"dropdownField?.errors\">\r\n {{ setErroMessage(formField, errorMessages, dropdownField)\r\n }}\r\n </p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- button/default control -->\r\n <div *ngSwitchCase=\"'default'\">\r\n <button type=\"submit\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.style)?.style\"\r\n (click)=\"invokeFormBtnClick(formField?.option, form.propertySchema)\">\r\n {{ formField?.label?.caption }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n</div>\r\n\r\n<ng-template #formMediaContent let-field=\"field\">\r\n <ng-container *ngIf=\"getMediaSource(field) as source\">\r\n <ng-container [ngSwitch]=\"getMediaKind(field)\">\r\n <img *ngSwitchDefault [src]=\"source\" [alt]=\"getMediaLabel(field)\" loading=\"lazy\" />\r\n <video *ngSwitchCase=\"'video'\" [src]=\"source\"\r\n [attr.aria-label]=\"getMediaLabel(field)\" controls playsinline preload=\"metadata\">\r\n Your browser does not support video playback.\r\n </video>\r\n <audio *ngSwitchCase=\"'audio'\" [src]=\"source\"\r\n [attr.aria-label]=\"getMediaLabel(field)\" controls preload=\"metadata\">\r\n Your browser does not support audio playback.\r\n </audio>\r\n <a *ngSwitchCase=\"'pdf'\" [href]=\"source\" target=\"_blank\" rel=\"noopener noreferrer\">\r\n Open PDF \u2014 {{ getMediaLabel(field) }}\r\n </a>\r\n <a *ngSwitchCase=\"'file'\" [href]=\"source\" target=\"_blank\" rel=\"noopener noreferrer\">\r\n Open file \u2014 {{ getMediaLabel(field) }}\r\n </a>\r\n </ng-container>\r\n </ng-container>\r\n</ng-template>\r\n\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0}.form-renderer{width:100%;height:100%;min-height:0;overflow-y:auto;overflow-x:hidden;box-sizing:border-box;padding:12px}.section-card{border:1px solid #e5e7eb;border-radius:var(--radius-sm, 8px);padding:12px;margin-bottom:12px;background:#fff;box-sizing:border-box}.section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;font-weight:600}.section-title{font-size:14px;color:#0f172a}.section-body{position:relative;width:100%;min-height:78px;box-sizing:border-box}.field-row{position:relative;min-width:0;width:100%;box-sizing:border-box}.field-row input,.field-row select,.field-row nz-select,.field-row textarea,.field-row button{max-width:100%;box-sizing:border-box}@media(max-width:768px){.section-body{grid-template-columns:repeat(1,minmax(0,1fr))!important}.field-row{grid-column:span 1!important}}@media(min-width:769px)and (max-width:1024px){.section-body{grid-template-columns:repeat(6,minmax(0,1fr))!important}.field-row{grid-column:span 6!important}}:host-context(.mobile-frame) .section-body,:host-context(.device-mobile) .section-body{grid-template-columns:repeat(1,minmax(0,1fr))!important}:host-context(.mobile-frame) .field-row,:host-context(.device-mobile) .field-row{grid-column:span 1!important}:host-context(.tablet-frame) .section-body,:host-context(.device-tablet) .section-body{grid-template-columns:repeat(6,minmax(0,1fr))!important}:host-context(.tablet-frame) .field-row,:host-context(.device-tablet) .field-row{grid-column:span 6!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1$1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1$1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i2.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i2.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i2.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: NzSelectModule }, { kind: "component", type: i3.NzOptionComponent, selector: "nz-option", inputs: ["nzTitle", "nzLabel", "nzValue", "nzKey", "nzDisabled", "nzHide", "nzCustomContent"], exportAs: ["nzOption"] }, { kind: "component", type: i3.NzSelectComponent, selector: "nz-select", inputs: ["nzId", "nzSize", "nzStatus", "nzVariant", "nzOptionHeightPx", "nzOptionOverflowSize", "nzDropdownClassName", "nzDropdownMatchSelectWidth", "nzDropdownStyle", "nzNotFoundContent", "nzPlaceHolder", "nzPlacement", "nzMaxTagCount", "nzDropdownRender", "nzCustomTemplate", "nzPrefix", "nzSuffixIcon", "nzClearIcon", "nzRemoveIcon", "nzMenuItemSelectedIcon", "nzTokenSeparators", "nzMaxTagPlaceholder", "nzMaxMultipleCount", "nzMode", "nzFilterOption", "compareWith", "nzAllowClear", "nzShowSearch", "nzLoading", "nzAutoFocus", "nzAutoClearSearchValue", "nzServerSearch", "nzDisabled", "nzOpen", "nzSelectOnTab", "nzBackdrop", "nzOptions", "nzShowArrow"], outputs: ["nzOnSearch", "nzScrollToBottom", "nzOpenChange", "nzBlur", "nzFocus", "nzOnClear"], exportAs: ["nzSelect"] }, { kind: "component", type: String$1, selector: "vl-string", inputs: ["formField", "modalItemInfo"] }, { kind: "component", type: Label, selector: "vl-label", inputs: ["formField", "modalItemInfo"] }, { kind: "ngmodule", type: NzDatePickerModule }, { kind: "component", type: i4.NzDatePickerComponent, selector: "nz-date-picker,nz-week-picker,nz-month-picker,nz-quarter-picker,nz-year-picker,nz-range-picker", inputs: ["nzInline", "nzAllowClear", "nzAutoFocus", "nzDisabled", "nzInputReadOnly", "nzOpen", "nzDisabledDate", "nzLocale", "nzPlaceHolder", "nzPopupStyle", "nzDropdownClassName", "nzSize", "nzStatus", "nzFormat", "nzVariant", "nzDateRender", "nzDisabledTime", "nzRenderExtraFooter", "nzShowToday", "nzMode", "nzShowNow", "nzRanges", "nzDefaultPickerValue", "nzSeparator", "nzSuffixIcon", "nzBackdrop", "nzId", "nzPlacement", "nzShowWeekNumber", "nzShowTime"], outputs: ["nzOnPanelChange", "nzOnCalendarChange", "nzOnOk", "nzOnOpenChange"], exportAs: ["nzDatePicker"] }, { kind: "directive", type: i4.NzRangePickerComponent, selector: "nz-range-picker", exportAs: ["nzRangePicker"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i12.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i18.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: CustomKeyboardDirective, selector: "[vlCustomKeyboard]", inputs: ["vlCustomKeyboard"] }, { kind: "pipe", type: PlaceholderPipe, name: "vlPlaceholder" }, { kind: "pipe", type: i1$1.CurrencyPipe, name: "currency" }] });
|
|
4747
4905
|
}
|
|
4748
4906
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: FormComponent, decorators: [{
|
|
4749
4907
|
type: Component,
|
|
@@ -4753,7 +4911,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
4753
4911
|
AutoCompleteModule,
|
|
4754
4912
|
InputTextModule,
|
|
4755
4913
|
CustomKeyboardDirective,
|
|
4756
|
-
], selector: 'vl-form', template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: autocompletes get their suggestions from RemoteListService (completeRemote) -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\r\n project defines none; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - change: autocomplete fields take the toolkit string keyboard unless option.keyboardType\r\n names another one ('native' keeps the system keyboard)\r\n - change: date and time fields use the shared picker markup (picker-field, DateTimePickerService)\r\n instead of PrimeNG's own icon with empty handlers -->\r\n\r\n<!-- Shuaib 2026-09-24\r\n - change: the form field switch uses renderFieldType so legacy checkbox fields render as boolean. -->\r\n\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - change: the credit card type is spelled creditCard. The rename of 2026-09-15 reached\r\n model.component.html and the whole Designer but not this file, so one data object rendered\r\n in a modal and stayed empty here\r\n - new: a @contract comment on the type switch and on the credit card case, so the rule sits\r\n next to the code that implements it -->\r\n<div class=\"form-renderer\"\r\n [ngClass]=\"getStyleWithoutClasses(form?.layoutSchema?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(form?.layoutSchema?.style)?.style\">\r\n <form *ngIf=\"form?.layoutSchema?.formSections?.length > 0\">\r\n <div\r\n *ngFor=\"let formSection of form?.layoutSchema?.formSections; let i = index\"\r\n class=\"section-card\">\r\n <div *ngIf=\"formSection?.label?.caption\" class=\"section-header\">\r\n <div class=\"section-title\"\r\n [ngClass]=\"getStyleWithoutClasses(formSection?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formSection?.label?.style)?.style\">\r\n {{ formSection.label.caption }}\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"formSection?.formFields?.length > 0\" class=\"section-body\"\r\n [ngClass]=\"getStyleWithoutClasses(formSection?.style)?.classes\"\r\n [ngStyle]=\"getSectionBodyStyle(formSection)\">\r\n <div *ngFor=\"let formField of formSection.formFields\" class=\"field-row\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.style)?.classes\"\r\n [ngStyle]=\"getFieldWrapperStyle(formField)\">\r\n <!-- @contract decision: this switch decides whether a field appears at all\r\n reality: renders string, number, image, date, time, dropdown, boolean,\r\n autocomplete, dateRange and the credit card type. Everything else\r\n falls into the default branch and the field renders empty - no error,\r\n nothing in the console either. Existing checkbox fields are normalized\r\n to boolean before this switch; new fields should use boolean.\r\n seen: measured 2026-09-19 with viewdefinition-contract/derive-capabilities.sh\r\n against this file; date, time, dropdown, boolean, autocomplete and\r\n dateRange arrived on 2026-09-18 -->\r\n <div [ngSwitch]=\"renderFieldType(\r\n formField?.scope?.reference &&\r\n form?.propertySchema?.[getScope(formField.scope?.reference)]\r\n ? form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.type\r\n : 'default'\r\n )\">\r\n <!-- image control -->\r\n <div *ngSwitchCase=\"'image'\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <img [src]=\"formField?.option?.src\" [alt]=\"'img'\" />\r\n </div>\r\n\r\n <!-- credit card control -->\r\n <!-- @contract decision: the credit card type is spelled creditCard\r\n reality: it was carditCard here until 2026-09-19. The rename of 2026-09-15\r\n reached model.component.html and the whole Designer, but not this\r\n file and not utility.service.ts, so the same data object rendered in\r\n one container and stayed empty in the other. All four places agree now.\r\n failure: a data object still declaring carditCard renders empty everywhere -\r\n that spelling is read nowhere any more.\r\n instead: declare the type as creditCard. -->\r\n <div *ngSwitchCase=\"'creditCard'\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <div id=\"card-element\"></div>\r\n <div id=\"card-errors\"\r\n [class]=\"creditCardError?.textContent !== '' ? 'text-red-400 m-0' : ''\"></div>\r\n </div>\r\n\r\n <!-- string control -->\r\n <span *ngSwitchCase=\"'string'\" class=\"form-group\">\r\n <span *ngIf=\"formField?.option?.protected\">\r\n <h5 style=\"display: flex; justify-content: space-between\">\r\n <span\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </span>\r\n\r\n <span>\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.value\r\n }}\r\n </span>\r\n </h5>\r\n </span>\r\n\r\n <span *ngIf=\"!formField?.option?.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n formField?.label?.value\r\n ? formField.label.value\r\n : formField?.label?.caption\r\n ? formField.label.caption\r\n :\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n\r\n <div [ngStyle]=\"\r\n formField?.option?.password\r\n ? {\r\n position: 'relative',\r\n display: 'flex',\r\n alignItems: 'center'\r\n }\r\n : {}\r\n \">\r\n <input [type]=\"\r\n formField?.option?.password\r\n ? !showPassword\r\n ? 'password'\r\n : 'text'\r\n : 'text'\r\n \" #inputField=\"ngModel\" [required]=\"formField?.required\"\r\n [attr.inputmode]=\"\r\n formField?.option?.email\r\n ? 'email'\r\n : formField?.option?.password\r\n ? 'numeric'\r\n : 'text'\r\n \" [pattern]=\"formField?.email ? customEmailPattern : ''\"\r\n [email]=\"formField?.email\"\r\n [minlength]=\"formField?.minlength\"\r\n [maxlength]=\"formField?.maxlength\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\" [placeholder]=\"form.propertySchema | vlPlaceholder: formField.scope?.reference\" />\r\n\r\n <i *ngIf=\"formField?.option?.password\" class=\"fa\"\r\n [ngClass]=\"showPassword ? 'fa-eye' : 'fa-eye-slash'\"\r\n (click)=\"togglePasswordVisibility()\"\r\n style=\"position: absolute; right: 10px; cursor: pointer\"></i>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"inputField?.touched && inputField?.invalid\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.validation?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.validation?.style)?.style\">\r\n <p [class]=\"\r\n formField?.layout === 'horizontal'\r\n ? 'text-red-400 m-0 horizontal-error'\r\n : 'text-red-400 m-0'\r\n \" *ngIf=\"inputField?.errors\">\r\n {{ setErroMessage(formField, errorMessages, inputField) }}\r\n </p>\r\n </div>\r\n </span>\r\n </span>\r\n\r\n <!-- number control -->\r\n <div *ngSwitchCase=\"'number'\" class=\"form-group\">\r\n <span *ngIf=\"formField?.option?.protected\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n <h5>\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.value\r\n | currency\r\n : formField?.option?.currency\r\n : true\r\n : formField?.option?.decimalPoint\r\n }}\r\n </h5>\r\n </span>\r\n\r\n <span *ngIf=\"!formField?.option?.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n formField?.label?.value\r\n ? formField.label.value\r\n :\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n\r\n <input type=\"number\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\" [placeholder]=\"form.propertySchema | vlPlaceholder: formField.scope?.reference\" />\r\n </div>\r\n </span>\r\n </div>\r\n\r\n <!-- checkbox control -->\r\n <div *ngSwitchCase=\"'boolean'\" class=\"form-group\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <span *ngIf=\"formField?.option?.postfixCaption\">\r\n <label class=\"form-check-label\">\r\n <input type=\"checkbox\" class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\"\r\n (ngModelChange)=\"setformData()\" />\r\n\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n </span>\r\n\r\n <div *ngIf=\"!formField?.option?.postfixCaption\"\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label class=\"form-check-label\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n\r\n <input type=\"checkbox\" class=\"form-check-input\" [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\"\r\n (ngModelChange)=\"setformData()\" />\r\n </label>\r\n </div>\r\n </div>\r\n\r\n <!-- DateRange control -->\r\n <div *ngSwitchCase=\"'dateRange'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{\r\n formField.label?.value\r\n ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <nz-range-picker #dateRangeField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [(ngModel)]=\"form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"></nz-range-picker>\r\n </div>\r\n <!-- Error Message -->\r\n <div *ngIf=\"dateRangeField?.touched && dateRangeField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateRangeField?.errors?.['required']\">\r\n {{formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n +\r\n ' ' + errorMessages.required}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateRangeField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Date control -->\r\n <div *ngSwitchCase=\"'date'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{getLabel(formField)}}</label>\r\n\r\n <span>\r\n <div class=\"picker-field\">\r\n <p-datepicker #datePicker #dateField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n [disabled]=\"formField?.option?.disabled\"\r\n [autoZIndex]=\"true\" [baseZIndex]=\"10000\"\r\n (ngModelChange)=\"dateChangeEvent(formField, $event)\"\r\n (onInput)=\"onDateTyping($event)\" [dateFormat]=\"\r\n form.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ]?.format || 'yy-mm-dd'\r\n \" [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [class]=\"formField?.scope?.style?.classes?.[0] || ''\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\" appendTo=\"body\"\r\n [panelStyleClass]=\"picker.datePanelClass\" [motionOptions]=\"picker.dockedMotion\"\r\n [vlCustomKeyboard]=\"formField?.option?.keyboardType ?? 'date'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, datePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(datePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\" aria-label=\"Open calendar\"\r\n (pointerdown)=\"picker.openFromIcon($event, datePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-calendar\"></i>\r\n </button>\r\n </div>\r\n <!-- Error Message -->\r\n <div *ngIf=\"dateField?.touched && dateField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages,\r\n \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Time control -->\r\n <div *ngSwitchCase=\"'time'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{getLabel(formField)}}</label>\r\n <span>\r\n <div class=\"picker-field\">\r\n <p-datepicker #timePicker #timeField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\" [timeOnly]=\"true\"\r\n hourFormat=\"24\" [disabled]=\"formField?.option?.disabled\" [autoZIndex]=\"true\" [baseZIndex]=\"10000\"\r\n (onInput)=\"onTimeTyping($event)\"\r\n (ngModelChange)=\"timeChangeEvent(formField, $event)\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [class]=\"formField?.scope?.style?.classes?.[0] || ''\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\" appendTo=\"body\"\r\n [panelStyleClass]=\"picker.timePanelClass\"\r\n [vlCustomKeyboard]=\"formField?.option?.keyboardType ?? 'time'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, timePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(timePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\" aria-label=\"Open time picker\"\r\n (pointerdown)=\"picker.openFromIcon($event, timePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-clock\"></i>\r\n </button>\r\n </div>\r\n\r\n <!-- Error Message -->\r\n <div *ngIf=\"timeField?.touched && timeField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"timeField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages,\r\n \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"timeField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Autocomplete control -->\r\n <div *ngSwitchCase=\"'autocomplete'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <vl-label [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-label>\r\n\r\n <span>\r\n <p-autocomplete [suggestions]=\"remoteSuggestions[formField.scope?.reference] ?? []\" (completeMethod)=\"completeRemote($event, formField)\" #autocompleteField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [class]=\"formField?.scope?.style?.classes ? formField?.scope?.style?.classes[0] : ''\" [dropdown]=\"true\"\r\n [optionLabel]=\"formField.option.autocomplete.response.label\"\r\n [placeholder]=\"form.propertySchema | vlPlaceholder: formField.scope?.reference\"\r\n (ngModelChange)=\"autocompFocusOutEvent($event, formField)\"\r\n [vlCustomKeyboard]=\"formField?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n <!-- Error Message -->\r\n <div\r\n *ngIf=\"autocompleteField?.touched && autocompleteField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages,\r\n \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- dropdown control -->\r\n <div *ngSwitchCase=\"'dropdown'\" class=\"form-group\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <span *ngIf=\"formField?.option?.postfixCaption\">\r\n <select id=\"dynamic-dropdown\" #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [name]=\"formField.scope?.reference\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.data\r\n \" [value]=\"item[formField?.option?.optionValue]\">\r\n {{ item[formField?.option?.optionLabel] }}\r\n </option>\r\n </select>\r\n\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n </span>\r\n\r\n <div *ngIf=\"!formField?.option?.postfixCaption\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n\r\n <nz-select nzShowSearch nzAllowClear nzPlaceHolder=\"Select\"\r\n #dropdownField=\"ngModel\"\r\n [required]=\"formField?.required\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [name]=\"formField.scope?.reference\" [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <nz-option *ngFor=\"\r\n let item of form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.data\r\n \" [nzLabel]=\"item[formField?.option?.optionLabel]\"\r\n [nzValue]=\"item[formField?.option?.optionValue]\"></nz-option>\r\n </nz-select>\r\n </div>\r\n\r\n <div *ngIf=\"dropdownField?.touched && dropdownField?.invalid\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.validation?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.validation?.style)?.style\">\r\n <p [class]=\"\r\n formField?.layout === 'horizontal'\r\n ? 'text-red-400 m-0 horizontal-error'\r\n : 'text-red-400 m-0'\r\n \" *ngIf=\"dropdownField?.errors\">\r\n {{ setErroMessage(formField, errorMessages, dropdownField)\r\n }}\r\n </p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- button/default control -->\r\n <div *ngSwitchCase=\"'default'\">\r\n <button type=\"submit\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.style)?.style\"\r\n (click)=\"invokeFormBtnClick(formField?.option, form.propertySchema)\">\r\n {{ formField?.label?.caption }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0}.form-renderer{width:100%;height:100%;min-height:0;overflow-y:auto;overflow-x:hidden;box-sizing:border-box;padding:12px}.section-card{border:1px solid #e5e7eb;border-radius:var(--radius-sm, 8px);padding:12px;margin-bottom:12px;background:#fff;box-sizing:border-box}.section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;font-weight:600}.section-title{font-size:14px;color:#0f172a}.section-body{position:relative;width:100%;min-height:78px;box-sizing:border-box}.field-row{position:relative;min-width:0;width:100%;box-sizing:border-box}.field-row input,.field-row select,.field-row nz-select,.field-row textarea,.field-row button{max-width:100%;box-sizing:border-box}@media(max-width:768px){.section-body{grid-template-columns:repeat(1,minmax(0,1fr))!important}.field-row{grid-column:span 1!important}}@media(min-width:769px)and (max-width:1024px){.section-body{grid-template-columns:repeat(6,minmax(0,1fr))!important}.field-row{grid-column:span 6!important}}:host-context(.mobile-frame) .section-body,:host-context(.device-mobile) .section-body{grid-template-columns:repeat(1,minmax(0,1fr))!important}:host-context(.mobile-frame) .field-row,:host-context(.device-mobile) .field-row{grid-column:span 1!important}:host-context(.tablet-frame) .section-body,:host-context(.device-tablet) .section-body{grid-template-columns:repeat(6,minmax(0,1fr))!important}:host-context(.tablet-frame) .field-row,:host-context(.device-tablet) .field-row{grid-column:span 6!important}\n"] }]
|
|
4914
|
+
], selector: 'vl-form', template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: autocompletes get their suggestions from RemoteListService (completeRemote) -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\r\n project defines none; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - change: autocomplete fields take the toolkit string keyboard unless option.keyboardType\r\n names another one ('native' keeps the system keyboard)\r\n - change: date and time fields use the shared picker markup (picker-field, DateTimePickerService)\r\n instead of PrimeNG's own icon with empty handlers -->\r\n\r\n<!-- Shuaib 2026-09-24\r\n - change: the form field switch uses renderFieldType so legacy checkbox fields render as boolean. -->\r\n\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - change: the credit card type is spelled creditCard. The rename of 2026-09-15 reached\r\n model.component.html and the whole Designer but not this file, so one data object rendered\r\n in a modal and stayed empty here\r\n - new: a @contract comment on the type switch and on the credit card case, so the rule sits\r\n next to the code that implements it -->\r\n<div class=\"form-renderer\"\r\n [ngClass]=\"getStyleWithoutClasses(form?.layoutSchema?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(form?.layoutSchema?.style)?.style\">\r\n <form *ngIf=\"form?.layoutSchema?.formSections?.length > 0\">\r\n <div\r\n *ngFor=\"let formSection of form?.layoutSchema?.formSections; let i = index\"\r\n class=\"section-card\">\r\n <div *ngIf=\"formSection?.label?.caption\" class=\"section-header\">\r\n <div class=\"section-title\"\r\n [ngClass]=\"getStyleWithoutClasses(formSection?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formSection?.label?.style)?.style\">\r\n {{ formSection.label.caption }}\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"formSection?.formFields?.length > 0\" class=\"section-body\"\r\n [ngClass]=\"getStyleWithoutClasses(formSection?.style)?.classes\"\r\n [ngStyle]=\"getSectionBodyStyle(formSection)\">\r\n <div *ngFor=\"let formField of formSection.formFields\" class=\"field-row\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.style)?.classes\"\r\n [ngStyle]=\"getFieldWrapperStyle(formField)\">\r\n <!-- @contract decision: this switch decides whether a field appears at all\r\n reality: renders string, number, image, date, time, dropdown, boolean,\r\n autocomplete, dateRange and the credit card type. Everything else\r\n falls into the default branch and the field renders empty - no error,\r\n nothing in the console either. Existing checkbox fields are normalized\r\n to boolean before this switch; new fields should use boolean.\r\n seen: measured 2026-09-19 with viewdefinition-contract/derive-capabilities.sh\r\n against this file; date, time, dropdown, boolean, autocomplete and\r\n dateRange arrived on 2026-09-18 -->\r\n <div [ngSwitch]=\"renderFieldType(\r\n formField?.scope?.reference &&\r\n form?.propertySchema?.[getScope(formField.scope?.reference)]\r\n ? form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.type\r\n : 'default'\r\n )\">\r\n <!-- Media control -->\r\n\r\n <div *ngSwitchCase=\"'media'\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <ng-container *ngTemplateOutlet=\"formMediaContent; context: { field: formField }\"></ng-container>\r\n </div>\r\n\r\n\r\n <!-- credit card control -->\r\n <!-- @contract decision: the credit card type is spelled creditCard\r\n reality: it was carditCard here until 2026-09-19. The rename of 2026-09-15\r\n reached model.component.html and the whole Designer, but not this\r\n file and not utility.service.ts, so the same data object rendered in\r\n one container and stayed empty in the other. All four places agree now.\r\n failure: a data object still declaring carditCard renders empty everywhere -\r\n that spelling is read nowhere any more.\r\n instead: declare the type as creditCard. -->\r\n <div *ngSwitchCase=\"'creditCard'\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <div id=\"card-element\"></div>\r\n <div id=\"card-errors\"\r\n [class]=\"creditCardError?.textContent !== '' ? 'text-red-400 m-0' : ''\"></div>\r\n </div>\r\n\r\n <!-- string control -->\r\n <span *ngSwitchCase=\"'string'\" class=\"form-group\">\r\n <span *ngIf=\"formField?.option?.protected\">\r\n <h5 style=\"display: flex; justify-content: space-between\">\r\n <span\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </span>\r\n\r\n <span>\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.value\r\n }}\r\n </span>\r\n </h5>\r\n </span>\r\n\r\n <span *ngIf=\"!formField?.option?.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n formField?.label?.value\r\n ? formField.label.value\r\n : formField?.label?.caption\r\n ? formField.label.caption\r\n :\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n\r\n <div [ngStyle]=\"\r\n formField?.option?.password\r\n ? {\r\n position: 'relative',\r\n display: 'flex',\r\n alignItems: 'center'\r\n }\r\n : {}\r\n \">\r\n <input [type]=\"\r\n formField?.option?.password\r\n ? !showPassword\r\n ? 'password'\r\n : 'text'\r\n : 'text'\r\n \" #inputField=\"ngModel\" [required]=\"formField?.required\"\r\n [attr.inputmode]=\"\r\n formField?.option?.email\r\n ? 'email'\r\n : formField?.option?.password\r\n ? 'numeric'\r\n : 'text'\r\n \" [pattern]=\"formField?.email ? customEmailPattern : ''\"\r\n [email]=\"formField?.email\"\r\n [minlength]=\"formField?.minlength\"\r\n [maxlength]=\"formField?.maxlength\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\" [placeholder]=\"form.propertySchema | vlPlaceholder: formField.scope?.reference\" />\r\n\r\n <i *ngIf=\"formField?.option?.password\" class=\"fa\"\r\n [ngClass]=\"showPassword ? 'fa-eye' : 'fa-eye-slash'\"\r\n (click)=\"togglePasswordVisibility()\"\r\n style=\"position: absolute; right: 10px; cursor: pointer\"></i>\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"inputField?.touched && inputField?.invalid\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.validation?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.validation?.style)?.style\">\r\n <p [class]=\"\r\n formField?.layout === 'horizontal'\r\n ? 'text-red-400 m-0 horizontal-error'\r\n : 'text-red-400 m-0'\r\n \" *ngIf=\"inputField?.errors\">\r\n {{ setErroMessage(formField, errorMessages, inputField) }}\r\n </p>\r\n </div>\r\n </span>\r\n </span>\r\n\r\n <!-- number control -->\r\n <div *ngSwitchCase=\"'number'\" class=\"form-group\">\r\n <span *ngIf=\"formField?.option?.protected\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n <h5>\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.value\r\n | currency\r\n : formField?.option?.currency\r\n : true\r\n : formField?.option?.decimalPoint\r\n }}\r\n </h5>\r\n </span>\r\n\r\n <span *ngIf=\"!formField?.option?.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n formField?.label?.value\r\n ? formField.label.value\r\n :\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n\r\n <input type=\"number\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\" [placeholder]=\"form.propertySchema | vlPlaceholder: formField.scope?.reference\" />\r\n </div>\r\n </span>\r\n </div>\r\n\r\n <!-- checkbox control -->\r\n <div *ngSwitchCase=\"'boolean'\" class=\"form-group\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <span *ngIf=\"formField?.option?.postfixCaption\">\r\n <label class=\"form-check-label\">\r\n <input type=\"checkbox\" class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\"\r\n (ngModelChange)=\"setformData()\" />\r\n\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n </span>\r\n\r\n <div *ngIf=\"!formField?.option?.postfixCaption\"\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label class=\"form-check-label\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n\r\n <input type=\"checkbox\" class=\"form-check-input\" [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [name]=\"formField.scope?.reference\"\r\n (ngModelChange)=\"setformData()\" />\r\n </label>\r\n </div>\r\n </div>\r\n\r\n <!-- DateRange control -->\r\n <div *ngSwitchCase=\"'dateRange'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{\r\n formField.label?.value\r\n ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <nz-range-picker #dateRangeField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [(ngModel)]=\"form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"></nz-range-picker>\r\n </div>\r\n <!-- Error Message -->\r\n <div *ngIf=\"dateRangeField?.touched && dateRangeField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateRangeField?.errors?.['required']\">\r\n {{formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n +\r\n ' ' + errorMessages.required}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateRangeField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Date control -->\r\n <div *ngSwitchCase=\"'date'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{getLabel(formField)}}</label>\r\n\r\n <span>\r\n <div class=\"picker-field\">\r\n <p-datepicker #datePicker #dateField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n [disabled]=\"formField?.option?.disabled\"\r\n [autoZIndex]=\"true\" [baseZIndex]=\"10000\"\r\n (ngModelChange)=\"dateChangeEvent(formField, $event)\"\r\n (onInput)=\"onDateTyping($event)\" [dateFormat]=\"\r\n form.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ]?.format || 'yy-mm-dd'\r\n \" [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \" [class]=\"formField?.scope?.style?.classes?.[0] || ''\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\" appendTo=\"body\"\r\n [panelStyleClass]=\"picker.datePanelClass\" [motionOptions]=\"picker.dockedMotion\"\r\n [vlCustomKeyboard]=\"formField?.option?.keyboardType ?? 'date'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, datePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(datePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\" aria-label=\"Open calendar\"\r\n (pointerdown)=\"picker.openFromIcon($event, datePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-calendar\"></i>\r\n </button>\r\n </div>\r\n <!-- Error Message -->\r\n <div *ngIf=\"dateField?.touched && dateField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages,\r\n \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Time control -->\r\n <div *ngSwitchCase=\"'time'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{getLabel(formField)}}</label>\r\n <span>\r\n <div class=\"picker-field\">\r\n <p-datepicker #timePicker #timeField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\" [timeOnly]=\"true\"\r\n hourFormat=\"24\" [disabled]=\"formField?.option?.disabled\" [autoZIndex]=\"true\" [baseZIndex]=\"10000\"\r\n (onInput)=\"onTimeTyping($event)\"\r\n (ngModelChange)=\"timeChangeEvent(formField, $event)\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [class]=\"formField?.scope?.style?.classes?.[0] || ''\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\" appendTo=\"body\"\r\n [panelStyleClass]=\"picker.timePanelClass\"\r\n [vlCustomKeyboard]=\"formField?.option?.keyboardType ?? 'time'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, timePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(timePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\" aria-label=\"Open time picker\"\r\n (pointerdown)=\"picker.openFromIcon($event, timePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-clock\"></i>\r\n </button>\r\n </div>\r\n\r\n <!-- Error Message -->\r\n <div *ngIf=\"timeField?.touched && timeField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"timeField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages,\r\n \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"timeField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n\r\n </div>\r\n\r\n </div>\r\n </div>\r\n\r\n <!-- Autocomplete control -->\r\n <div *ngSwitchCase=\"'autocomplete'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <vl-label [formField]=\"formField\" [modalItemInfo]=\"form\">\r\n </vl-label>\r\n\r\n <span>\r\n <p-autocomplete [suggestions]=\"remoteSuggestions[formField.scope?.reference] ?? []\" (completeMethod)=\"completeRemote($event, formField)\" #autocompleteField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [class]=\"formField?.scope?.style?.classes ? formField?.scope?.style?.classes[0] : ''\" [dropdown]=\"true\"\r\n [optionLabel]=\"formField.option.autocomplete.response.label\"\r\n [placeholder]=\"form.propertySchema | vlPlaceholder: formField.scope?.reference\"\r\n (ngModelChange)=\"autocompFocusOutEvent($event, formField)\"\r\n [vlCustomKeyboard]=\"formField?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n <!-- Error Message -->\r\n <div\r\n *ngIf=\"autocompleteField?.touched && autocompleteField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages,\r\n \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n form.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- dropdown control -->\r\n <div *ngSwitchCase=\"'dropdown'\" class=\"form-group\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\">\r\n <span *ngIf=\"formField?.option?.postfixCaption\">\r\n <select id=\"dynamic-dropdown\" #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [name]=\"formField.scope?.reference\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.data\r\n \" [value]=\"item[formField?.option?.optionValue]\">\r\n {{ item[formField?.option?.optionLabel] }}\r\n </option>\r\n </select>\r\n\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n </span>\r\n\r\n <div *ngIf=\"!formField?.option?.postfixCaption\">\r\n <div [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [ngClass]=\"getStyleWithoutClasses(formField?.label?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.label?.style)?.style\">\r\n {{\r\n form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption\r\n }}\r\n </label>\r\n\r\n <nz-select nzShowSearch nzAllowClear nzPlaceHolder=\"Select\"\r\n #dropdownField=\"ngModel\"\r\n [required]=\"formField?.required\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.scope?.style)?.style\"\r\n [name]=\"formField.scope?.reference\" [(ngModel)]=\"\r\n form.propertySchema[getScope(formField.scope?.reference)][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <nz-option *ngFor=\"\r\n let item of form.propertySchema[getScope(formField.scope?.reference)]?.[\r\n formField.scope?.reference.split('/').pop()\r\n ]?.data\r\n \" [nzLabel]=\"item[formField?.option?.optionLabel]\"\r\n [nzValue]=\"item[formField?.option?.optionValue]\"></nz-option>\r\n </nz-select>\r\n </div>\r\n\r\n <div *ngIf=\"dropdownField?.touched && dropdownField?.invalid\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.validation?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.validation?.style)?.style\">\r\n <p [class]=\"\r\n formField?.layout === 'horizontal'\r\n ? 'text-red-400 m-0 horizontal-error'\r\n : 'text-red-400 m-0'\r\n \" *ngIf=\"dropdownField?.errors\">\r\n {{ setErroMessage(formField, errorMessages, dropdownField)\r\n }}\r\n </p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- button/default control -->\r\n <div *ngSwitchCase=\"'default'\">\r\n <button type=\"submit\"\r\n [ngClass]=\"getStyleWithoutClasses(formField?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField?.style)?.style\"\r\n (click)=\"invokeFormBtnClick(formField?.option, form.propertySchema)\">\r\n {{ formField?.label?.caption }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n</div>\r\n\r\n<ng-template #formMediaContent let-field=\"field\">\r\n <ng-container *ngIf=\"getMediaSource(field) as source\">\r\n <ng-container [ngSwitch]=\"getMediaKind(field)\">\r\n <img *ngSwitchDefault [src]=\"source\" [alt]=\"getMediaLabel(field)\" loading=\"lazy\" />\r\n <video *ngSwitchCase=\"'video'\" [src]=\"source\"\r\n [attr.aria-label]=\"getMediaLabel(field)\" controls playsinline preload=\"metadata\">\r\n Your browser does not support video playback.\r\n </video>\r\n <audio *ngSwitchCase=\"'audio'\" [src]=\"source\"\r\n [attr.aria-label]=\"getMediaLabel(field)\" controls preload=\"metadata\">\r\n Your browser does not support audio playback.\r\n </audio>\r\n <a *ngSwitchCase=\"'pdf'\" [href]=\"source\" target=\"_blank\" rel=\"noopener noreferrer\">\r\n Open PDF \u2014 {{ getMediaLabel(field) }}\r\n </a>\r\n <a *ngSwitchCase=\"'file'\" [href]=\"source\" target=\"_blank\" rel=\"noopener noreferrer\">\r\n Open file \u2014 {{ getMediaLabel(field) }}\r\n </a>\r\n </ng-container>\r\n </ng-container>\r\n</ng-template>\r\n\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0}.form-renderer{width:100%;height:100%;min-height:0;overflow-y:auto;overflow-x:hidden;box-sizing:border-box;padding:12px}.section-card{border:1px solid #e5e7eb;border-radius:var(--radius-sm, 8px);padding:12px;margin-bottom:12px;background:#fff;box-sizing:border-box}.section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;font-weight:600}.section-title{font-size:14px;color:#0f172a}.section-body{position:relative;width:100%;min-height:78px;box-sizing:border-box}.field-row{position:relative;min-width:0;width:100%;box-sizing:border-box}.field-row input,.field-row select,.field-row nz-select,.field-row textarea,.field-row button{max-width:100%;box-sizing:border-box}@media(max-width:768px){.section-body{grid-template-columns:repeat(1,minmax(0,1fr))!important}.field-row{grid-column:span 1!important}}@media(min-width:769px)and (max-width:1024px){.section-body{grid-template-columns:repeat(6,minmax(0,1fr))!important}.field-row{grid-column:span 6!important}}:host-context(.mobile-frame) .section-body,:host-context(.device-mobile) .section-body{grid-template-columns:repeat(1,minmax(0,1fr))!important}:host-context(.mobile-frame) .field-row,:host-context(.device-mobile) .field-row{grid-column:span 1!important}:host-context(.tablet-frame) .section-body,:host-context(.device-tablet) .section-body{grid-template-columns:repeat(6,minmax(0,1fr))!important}:host-context(.tablet-frame) .field-row,:host-context(.device-tablet) .field-row{grid-column:span 6!important}\n"] }]
|
|
4757
4915
|
}], propDecorators: { errorMessages: [{
|
|
4758
4916
|
type: Input
|
|
4759
4917
|
}], creditCardError: [{
|
|
@@ -5276,8 +5434,82 @@ class GridComponent {
|
|
|
5276
5434
|
return source.includes(search);
|
|
5277
5435
|
}
|
|
5278
5436
|
}
|
|
5437
|
+
isMediaColumn(col) {
|
|
5438
|
+
const reference = col?.scope?.reference;
|
|
5439
|
+
if (typeof reference !== 'string')
|
|
5440
|
+
return false;
|
|
5441
|
+
const key = reference.split('/').pop();
|
|
5442
|
+
const type = this.tableitemInfo?.propertySchema?.[this.getScope(reference)]?.[key]?.type;
|
|
5443
|
+
// Preserve existing image properties.
|
|
5444
|
+
return type === 'media' || type === 'image';
|
|
5445
|
+
}
|
|
5446
|
+
getMediaSource(col, data) {
|
|
5447
|
+
const key = col?.scope?.reference?.split('/').pop();
|
|
5448
|
+
const value = col?.option?.src || (key ? data?.[key] : '');
|
|
5449
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
5450
|
+
}
|
|
5451
|
+
getMediaLabel(col) {
|
|
5452
|
+
if (typeof col?.label === 'string' && col.label.trim()) {
|
|
5453
|
+
return col.label;
|
|
5454
|
+
}
|
|
5455
|
+
const reference = col?.scope?.reference;
|
|
5456
|
+
const key = reference?.split('/').pop();
|
|
5457
|
+
const property = reference && key
|
|
5458
|
+
? this.tableitemInfo?.propertySchema?.[this.getScope(reference)]?.[key]
|
|
5459
|
+
: undefined;
|
|
5460
|
+
return col?.label?.caption || property?.caption || 'Media';
|
|
5461
|
+
}
|
|
5462
|
+
getMediaKind(col, data) {
|
|
5463
|
+
const source = this.getMediaSource(col, data);
|
|
5464
|
+
const mime = /^data:([^;,]+)/i.exec(source)?.[1]?.toLowerCase();
|
|
5465
|
+
if (mime?.startsWith('image/'))
|
|
5466
|
+
return 'image';
|
|
5467
|
+
if (mime?.startsWith('video/'))
|
|
5468
|
+
return 'video';
|
|
5469
|
+
if (mime?.startsWith('audio/'))
|
|
5470
|
+
return 'audio';
|
|
5471
|
+
if (mime === 'application/pdf')
|
|
5472
|
+
return 'pdf';
|
|
5473
|
+
const key = col?.scope?.reference?.split('/').pop();
|
|
5474
|
+
const candidates = [
|
|
5475
|
+
source,
|
|
5476
|
+
col?.option?.fileName,
|
|
5477
|
+
key ? data?.[key] : undefined
|
|
5478
|
+
];
|
|
5479
|
+
const videos = ['mp4', 'm4v', 'webm', 'mov', 'ogv', 'avi', 'mkv'];
|
|
5480
|
+
const audios = ['mp3', 'wav', 'ogg', 'oga', 'm4a', 'aac', 'flac', 'opus'];
|
|
5481
|
+
const files = [
|
|
5482
|
+
'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
|
5483
|
+
'txt', 'csv', 'zip', 'rar', '7z'
|
|
5484
|
+
];
|
|
5485
|
+
for (const candidate of candidates) {
|
|
5486
|
+
if (typeof candidate !== 'string')
|
|
5487
|
+
continue;
|
|
5488
|
+
if (/^(data|blob):/i.test(candidate))
|
|
5489
|
+
continue;
|
|
5490
|
+
let path = candidate.split(/[?#]/)[0];
|
|
5491
|
+
try {
|
|
5492
|
+
path = decodeURIComponent(path);
|
|
5493
|
+
}
|
|
5494
|
+
catch {
|
|
5495
|
+
// Keep the original path.
|
|
5496
|
+
}
|
|
5497
|
+
const extension = /\.([a-z0-9]+)$/i.exec(path)?.[1]?.toLowerCase();
|
|
5498
|
+
if (!extension)
|
|
5499
|
+
continue;
|
|
5500
|
+
if (videos.includes(extension))
|
|
5501
|
+
return 'video';
|
|
5502
|
+
if (audios.includes(extension))
|
|
5503
|
+
return 'audio';
|
|
5504
|
+
if (extension === 'pdf')
|
|
5505
|
+
return 'pdf';
|
|
5506
|
+
if (files.includes(extension))
|
|
5507
|
+
return 'file';
|
|
5508
|
+
}
|
|
5509
|
+
return 'image';
|
|
5510
|
+
}
|
|
5279
5511
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: GridComponent, deps: [{ token: RequestService }, { token: UtilityService }, { token: NotificationService }, { token: i4$1.OktaAuthStateService }], target: i0.ɵɵFactoryTarget.Component });
|
|
5280
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: GridComponent, isStandalone: true, selector: "vl-grid", inputs: { errorMessages: "errorMessages", environment: "environment", selectedLanguageInfo: "selectedLanguageInfo", tableitemInfo: "tableitemInfo", form: "form", isTableDataLoad: "isTableDataLoad", currenTableOptions: "currenTableOptions", projectSeleted: "projectSeleted", pageSize: "pageSize", currentPage: "currentPage", pages: "pages", userInfo: "userInfo" }, outputs: { onPageChange: "onPageChange", onPageSizeChange: "onPageSizeChange", onGridScroll: "onGridScroll", showGridLink: "showGridLink", gridButtonClick: "gridButtonClick", gridIncreamentClick: "gridIncreamentClick", gridDecreamentClick: "gridDecreamentClick", gridImgClick: "gridImgClick" }, usesOnChanges: true, ngImport: i0, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: autocompletes get their suggestions from RemoteListService (completeRemote) -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\r\n project defines none; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - change: autocomplete fields take the toolkit string keyboard unless option.keyboardType\r\n names another one ('native' keeps the system keyboard)\r\n - change: date and time cells use the shared picker markup (picker-field, DateTimePickerService);\r\n their PrimeNG icon opened the calendar together with the system keyboard -->\r\n\r\n<!-- Shuaib 2026-09-24\r\n - change: grid cells render legacy checkbox fields through the boolean control. -->\r\n\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: @contract comments on the sorting, on the column filter and on the footer. The filter\r\n reads option.filter and never option.columnFilter, which is the mistake a layout author\r\n makes without being told -->\r\n<div *ngIf=\"!tableitemInfo.data?.length\" class=\"alert alert-warning col-md-12\"\r\n role=\"alert\">\r\n {{ tableitemInfo.layoutSchema?.noRecordMessage}}\r\n</div>\r\n<div *ngIf=\"tableitemInfo.data?.length\" class=\"reservation-container pt-5 px-3\">\r\n <nz-table #basicTable [nzData]=\"gridDisplayData\"\r\n [nzNoResult]=\"'No matching records found'\" [nzBordered]=\"true\"\r\n [nzFrontPagination]=\"currenTableOptions?.pagination?.tabs\"\r\n [style]=\"getStyleWithoutClasses(currenTableOptions?.style).style\"\r\n (scroll)=\"onScrollEvent($event)\"\r\n [nzLoading]=\"isTableDataLoad\">\r\n <thead>\r\n <!-- @contract decision: option.sortable is read here\r\n reality: nzSortFn is wired to getColumnSortFn since 2026-09-16; before that\r\n the key was decoration. The column also needs a scope.reference -\r\n without one getColumnSortFn has nothing to sort by.\r\n seen: grid.component.ts:338 -->\r\n <!-- Column headings and sorting -->\r\n <tr>\r\n <th *ngFor=\"let column of tableitemInfo.layoutSchema.elements\"\r\n [nzSortFn]=\"\r\n column?.option?.sortable\r\n ? getColumnSortFn(column)\r\n : false\r\n \">\r\n\r\n {{ column?.label?.caption }}\r\n </th>\r\n </tr>\r\n\r\n <!-- @contract decision: the column filter reads option.filter, NOT option.columnFilter\r\n reality: option.filter.filtering switches it on, option.filter.defaultOperator\r\n starts the comparison (the renderer falls back to contains), and\r\n option.filter.placeholder labels the input.\r\n failure: option.columnFilter is read nowhere, here or in the runtime app. A\r\n column carrying it looks filterable in the file and is not on screen.\r\n seen: grid.component.ts:450-475; 5 uses of columnFilter across the reference\r\n projects, all of them dead -->\r\n <!-- Column filters -->\r\n <tr *ngIf=\"hasAnyColumnFilter()\" class=\"grid-filter-row\">\r\n\r\n <th *ngFor=\"let column of tableitemInfo.layoutSchema.elements\"\r\n class=\"grid-filter-cell\">\r\n\r\n <ng-container *ngIf=\"\r\n column?.option?.filter?.filtering === true &&\r\n column?.scope?.reference\r\n \">\r\n\r\n <div class=\"grid-filter-input-wrapper\">\r\n <input type=\"text\" class=\"grid-filter-input\"\r\n [placeholder]=\"getColumnFilterPlaceholder(column)\"\r\n [ngModel]=\"getColumnFilterState(column).value\"\r\n (ngModelChange)=\"\r\n setColumnFilterValue(column, $event)\r\n \" />\r\n\r\n <!-- Operator selection button -->\r\n <button type=\"button\"\r\n class=\"grid-filter-operator-button\" nz-dropdown\r\n nzTrigger=\"click\"\r\n [nzDropdownMenu]=\"operatorMenu\"\r\n [class.grid-filter-operator-button--active]=\"\r\n hasActiveColumnFilter(column)\r\n \" [title]=\"\r\n 'Operator: ' +\r\n getFilterOperatorLabel(\r\n getColumnFilterState(column).operator\r\n )\r\n \">\r\n\r\n <span nz-icon nzType=\"filter\"></span>\r\n </button>\r\n\r\n <!-- Clear current column filter -->\r\n <button *ngIf=\"hasActiveColumnFilter(column)\"\r\n type=\"button\" class=\"grid-filter-clear-button\"\r\n aria-label=\"Clear column filter\"\r\n title=\"Clear filter\"\r\n (click)=\"clearColumnFilter(column)\">\r\n\r\n <span nz-icon nzType=\"close\"></span>\r\n </button>\r\n </div>\r\n\r\n <!-- Operator dropdown -->\r\n <nz-dropdown-menu #operatorMenu=\"nzDropdownMenu\">\r\n <ul nz-menu class=\"grid-filter-operator-menu\">\r\n <li *ngFor=\"let operator of filterOperators\"\r\n nz-menu-item [nzSelected]=\"\r\n getColumnFilterState(column).operator ===\r\n operator.value\r\n \" (click)=\"\r\n setColumnFilterOperator(\r\n column,\r\n operator.value\r\n )\r\n \">\r\n\r\n <span *ngIf=\"\r\n getColumnFilterState(column).operator ===\r\n operator.value\r\n \" nz-icon nzType=\"check\">\r\n </span>\r\n\r\n {{ operator.label }}\r\n </li>\r\n </ul>\r\n </nz-dropdown-menu>\r\n </ng-container>\r\n </th>\r\n </tr>\r\n </thead>\r\n <!-- @contract decision: the column chain below, and what it does not carry\r\n reality: string, number, image, quantity, date, time, dropdown, boolean,\r\n autocomplete and dateRange each have their own condition. There is no\r\n branch for the credit card type.\r\n failure: a column whose type has no branch stays empty, with no error.\r\n instead: format the value in the endpoint and send a string.\r\n\r\n @contract decision: columns without scope.reference are valid\r\n reality: with option.actionColumn they render save/delete icons, otherwise a\r\n button. That is why the conditions below all guard on the reference. -->\r\n <tbody>\r\n <tr *ngFor=\"let data of basicTable.data; let i = index\">\r\n <td *ngFor=\"let col of tableitemInfo.layoutSchema.elements\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\">\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'string'\"\r\n [class]=\"getStyleWithoutClasses(col?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\">\r\n <span *ngIf=\"col.option.protected\">\r\n <span *ngIf=\"col.option?.link\"\r\n (click)=\"showLinkDetails(col.option, data)\">\r\n {{data[col.scope?.reference.split('/').pop()]}}\r\n </span>\r\n <span *ngIf=\"!col.option?.link\">\r\n <ng-container\r\n *ngIf=\"col?.option?.dateFormat; else string\">\r\n {{\r\n data[col.scope?.reference.split('/').pop()]\r\n | date:'yyyy-MM-dd' }}\r\n </ng-container>\r\n <ng-template #string>\r\n {{\r\n data[col.scope?.reference.split('/').pop()]\r\n }}\r\n </ng-template>\r\n </span>\r\n </span>\r\n <span *ngIf=\"!col.option.protected\">\r\n <input\r\n [(ngModel)]=\"data[col.scope?.reference.split('/').pop()]\"\r\n class=\"p-inputtext p-component\" />\r\n </span>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'number'\">\r\n <span *ngIf=\"col.option.protected\">\r\n <span\r\n *ngIf=\"col.option?.innerAction?.action == 'multiply'\">\r\n {{\r\n (data[col.option?.innerAction?.from] *\r\n data[col.option?.innerAction?.to])\r\n | number:'1.2-2'\r\n }}\r\n </span>\r\n <span *ngIf=\"!col.option?.innerAction\">\r\n {{data[col.scope?.reference.split('/').pop()] |\r\n currency:\r\n tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.currency\r\n :true }}\r\n </span>\r\n </span>\r\n <span *ngIf=\"!col.option.protected\">\r\n <input\r\n [(ngModel)]=\"data[col.scope?.reference.split('/').pop()]\"\r\n type=\"number\"\r\n class=\"p-inputtext p-component\" />\r\n </span>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'image'\">\r\n <img *ngIf=\"data[col.scope?.reference.split('/').pop()]\"\r\n [src]=\"data[col.scope?.reference.split('/').pop()]\"\r\n alt=\"Image\"\r\n (click)=\"invokeTableImageClick(col, data)\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\" />\r\n </ng-container>\r\n\r\n <!-- @contract compatibility: existing checkbox fields render as boolean. -->\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && (tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'boolean' || isLegacyCheckboxFieldType(tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type))\">\r\n <input type=\"checkbox\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"data[col.scope?.reference.split('/').pop()]\"\r\n [name]=\"col.scope?.reference\" />\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'dateRange'\">\r\n <nz-range-picker #dateRangeField=\"ngModel\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\"\r\n [style]=\"getStyleWithoutClasses(col?.style).style\"\r\n [(ngModel)]=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)][col.scope?.reference.split('/').pop()].value\"></nz-range-picker>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'date'\">\r\n <div class=\"picker-field\">\r\n <p-datepicker #datePicker #dateField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\"\r\n [disabled]=\"col?.option?.disabled\"\r\n [autoZIndex]=\"true\" [baseZIndex]=\"10000\"\r\n [dateFormat]=\"\r\n tableitemInfo.propertySchema[\r\n getScope(col.scope?.reference)\r\n ][\r\n col.scope?.reference.split('/').pop()\r\n ]?.format || 'yy-mm-dd'\r\n \" [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(col.scope?.reference)\r\n ][\r\n col.scope?.reference.split('/').pop()\r\n ].value\r\n \" [class]=\"col?.scope?.style?.classes?.[0] || ''\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\" appendTo=\"body\"\r\n [panelStyleClass]=\"picker.datePanelClass\" [motionOptions]=\"picker.dockedMotion\"\r\n [vlCustomKeyboard]=\"col?.option?.keyboardType ?? 'date'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, datePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(datePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\" aria-label=\"Open calendar\"\r\n (pointerdown)=\"picker.openFromIcon($event, datePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-calendar\"></i>\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'time'\">\r\n <div class=\"picker-field\">\r\n <p-datepicker #timePicker #timeField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\" [timeOnly]=\"true\"\r\n hourFormat=\"24\" [disabled]=\"col?.option?.disabled\" [autoZIndex]=\"true\"\r\n [baseZIndex]=\"10000\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(col.scope?.reference)\r\n ][\r\n col.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [class]=\"col?.scope?.style?.classes?.[0] || ''\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\" appendTo=\"body\"\r\n [panelStyleClass]=\"picker.timePanelClass\"\r\n [vlCustomKeyboard]=\"col?.option?.keyboardType ?? 'time'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, timePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(timePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\" aria-label=\"Open time picker\"\r\n (pointerdown)=\"picker.openFromIcon($event, timePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-clock\"></i>\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'autocomplete'\">\r\n <p-autocomplete [suggestions]=\"remoteSuggestions[col.scope?.reference] ?? []\" (completeMethod)=\"completeRemote($event, col)\" #autocompleteField=\"ngModel\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"form.propertySchema[getScope(col.scope?.reference)][col.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n [class]=\"col?.scope?.style?.classes ? col?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"col.option.autocomplete.response.label\"\r\n [placeholder]=\"form.propertySchema | vlPlaceholder: col.scope?.reference\"\r\n [vlCustomKeyboard]=\"col?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'dropdown'\">\r\n <select id=\"dynamic-dropdown\" #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(col?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(col?.scope?.style)?.style\"\r\n [name]=\"col.scope?.reference\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(col.scope?.reference)][\r\n col.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of form.propertySchema[getScope(col.scope?.reference)]?.[\r\n col.scope?.reference.split('/').pop()\r\n ]?.data\r\n \" [value]=\"item[col?.option?.optionValue]\">\r\n {{ item[col?.option?.optionLabel] }}\r\n </option>\r\n </select>\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"!col?.scope?.reference\">\r\n <span *ngIf=\"col.option?.actionColumn\">\r\n <span nz-icon\r\n *ngIf=\"setActionColumn(col.option?.save, data)\"\r\n [nzType]=\"col.option?.save?.icon\"\r\n [nzTheme]=\"'twotone'\"\r\n [style]=\"col.option?.save?.style\"\r\n [nzTwotoneColor]=\"'#52c41a'\"\r\n [class]=\"col.option?.save?.style?.classes[0]\"\r\n (click)=\"upsertAction(col.option?.save, data)\">\r\n </span>\r\n <span nz-icon\r\n *ngIf=\"setActionColumn(col.option?.delete, data)\"\r\n [nzType]=\"col.option?.delete?.icon\"\r\n [nzTheme]=\"'twotone'\"\r\n [style]=\"col.option?.delete?.style\"\r\n [nzTwotoneColor]=\"'#FF0000'\"\r\n [class]=\"col.option?.delete?.style?.classes[0]\"\r\n (click)=\"upsertAction(col.option?.delete, data)\"></span>\r\n\r\n </span>\r\n <span *ngIf=\"!col.option?.actionColumn\">\r\n <button type=\"submit\"\r\n [style]=\"getStyleWithoutClasses(col?.label?.style).style\"\r\n (click)=\"invokeGridBtnClick(col.option, data)\"\r\n [class]=\"col?.label?.style?.classes[0]\">\r\n {{ col.label?.caption }}\r\n </button>\r\n </span>\r\n </ng-container>\r\n\r\n <!-- For quantity column, display increment/decrement buttons -->\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'quantity'\">\r\n <div style=\"display: flex; align-items: center;\">\r\n <button pButton icon=\"pi pi-minus\"\r\n (click)=\"decrementQuantity(i,col.scope?.reference.split('/').pop(), col.option)\"\r\n [disabled]=\"data.quantity <= 0\"\r\n style=\"margin-right: 8px; height: 10px; width: 10px;\"></button>\r\n <span>{{ data[col.scope?.reference.split('/').pop()]\r\n }}</span>\r\n <button pButton icon=\"pi pi-plus\"\r\n (click)=\"incrementQuantity(i, col.scope?.reference.split('/').pop(), col.option)\"\r\n style=\"margin-left: 8px; height: 10px; width: 10px;\"></button>\r\n </div>\r\n </ng-container>\r\n </td>\r\n </tr>\r\n </tbody>\r\n </nz-table>\r\n</div>\r\n\r\n<!-- table footer -->\r\n<div *ngIf=\"tableitemInfo?.footer?.formFields && tableitemInfo.data?.length\"\r\n class=\"table-footer flex flex-column align-items-end gap-2 pt-2 mx-4 border-top-1 border-cyan-500\">\r\n <!-- @contract decision: the footer renders string and number only\r\n reality: the switch below carries those two cases and a default. It did NOT change\r\n when the columns gained their types on 2026-09-18.\r\n failure: any other type in a footer field renders empty.\r\n instead: keep aggregates numeric, or pre-format them as text in the endpoint. -->\r\n <div *ngFor=\"let tableField of tableitemInfo?.footer?.formFields\">\r\n <div\r\n [ngSwitch]=\"tableField?.scope?.reference && tableitemInfo.propertySchema[getScope(tableField.scope?.reference)] && tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()] ?\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.type : 'default'\">\r\n\r\n <!-- string control -->\r\n <div *ngSwitchCase=\"'string'\" class=\"form-group\">\r\n <span *ngIf=\"tableField.option.protected\">\r\n <h5 style=\"display: flex; justify-content: space-between;\">\r\n <span\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">\r\n {{\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }}\r\n </span>\r\n <span>\r\n {{\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.value\r\n }}\r\n </span>\r\n </h5>\r\n </span>\r\n <div *ngIf=\"!tableField.option.protected\">\r\n <div\r\n [class]=\"tableField?.layout == 'horizontal' ? 'horizontal-layout' : 'row'\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">{{\r\n tableField.label?.value ?\r\n tableField.label?.value :\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <input type=\"text\" #inputField=\"ngModel\"\r\n [required]=\"tableField.option.required\"\r\n [email]=\"tableField.option.email\"\r\n [class]=\"tableField.scope?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(tableField?.scope?.style).style\"\r\n [(ngModel)]=\"tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()].value\"\r\n [name]=\"tableField.scope?.reference\"\r\n [placeholder]=\"tableitemInfo.propertySchema | vlPlaceholder: tableField.scope?.reference\" />\r\n </div>\r\n <!-- Error Message -->\r\n <div *ngIf=\"inputField?.touched && inputField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(tableField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"inputField?.errors?.['required']\">{{tableField.label?.value\r\n ?\r\n tableField.label?.value :\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n +\r\n ' ' + errorMessages.required}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"inputField?.errors?.['email']\">{{(tableField.label?.value\r\n ?\r\n tableField.label?.value :\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- number control -->\r\n <div *ngSwitchCase=\"'number'\" class=\"form-group\">\r\n <span *ngIf=\"tableField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">\r\n <h5>{{\r\n form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }} {{\r\n form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.value\r\n |\r\n currency:tableField.option.currency :true:\r\n tableField.option?.decimalPoint}}\r\n </h5>\r\n </span>\r\n <span *ngIf=\"!tableField.option.protected\">\r\n <div\r\n [class]=\"tableField?.layout == 'horizontal' ? 'horizontal-layout' : 'row'\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">{{\r\n tableField.label?.value ?\r\n tableField.label?.value :\r\n form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <input type=\"number\"\r\n [class]=\"tableField.scope?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(tableField?.scope?.style).style\"\r\n [(ngModel)]=\"form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()].value\"\r\n [name]=\"tableField.scope?.reference\"\r\n [placeholder]=\"form.propertySchema | vlPlaceholder: tableField.scope?.reference\" />\r\n </div>\r\n </span>\r\n </div>\r\n\r\n <!-- button control -->\r\n <div *ngSwitchCase=\"'default'\">\r\n <button type=\"submit\"\r\n [style]=\"getStyleWithoutClasses(tableField.label.style).style\"\r\n (click)=\"invokeGridBtnClick(tableField.option, tableitemInfo.propertySchema)\">\r\n {{ tableField.label?.caption }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<!-- bottom pagination on grid -->\r\n<div\r\n *ngIf=\"tableitemInfo.data?.length && !tableitemInfo?.layoutSchema?.pagination?.top && tableitemInfo?.layoutSchema?.pagination?.bottom\">\r\n <vl-pagination [itemInfo]=\"tableitemInfo\" [pageSize]=\"pageSize\"\r\n [currentPage]=\"currentPage\" [pages]=\"pages\"\r\n (pageSizeChange)=\"invokeOnPageSizeChange($event)\"\r\n (onGoToPage)=\"invokeGoToPage($event)\">\r\n </vl-pagination>\r\n</div>\r\n", styles: [".grid-filter-row th{padding:8px;vertical-align:middle;background:#fafafa}.grid-filter-cell{min-width:150px}.grid-filter-input-wrapper{display:flex;align-items:stretch;width:100%}.grid-filter-input{width:100%;min-width:0;min-height:34px;padding:5px 8px;border:1px solid #d9d9d9;border-right:0;border-radius:4px 0 0 4px;outline:none}.grid-filter-input:focus{border-color:#1677ff}.grid-filter-operator-button,.grid-filter-clear-button{display:inline-flex;align-items:center;justify-content:center;min-width:36px;min-height:34px;padding:0;cursor:pointer;background:#fff;border:1px solid #d9d9d9}.grid-filter-operator-button{color:#595959;border-radius:0 4px 4px 0}.grid-filter-operator-button:hover,.grid-filter-operator-button--active{color:#1677ff;border-color:#1677ff}.grid-filter-clear-button{margin-left:5px;color:#ff4d4f;border-radius:4px}.grid-filter-clear-button:hover{color:#fff;background:#ff4d4f;border-color:#ff4d4f}.grid-filter-operator-menu{min-width:190px}:host ::ng-deep .ant-table-content.has-more-right{-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 36px),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 36px),transparent)}:host ::ng-deep .ant-table-content.has-more-left{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 36px),transparent);mask-image:linear-gradient(to left,#000 calc(100% - 36px),transparent)}:host ::ng-deep .ant-table-content.has-more-left.has-more-right{-webkit-mask-image:linear-gradient(to right,transparent,#000 36px,#000 calc(100% - 36px),transparent);mask-image:linear-gradient(to right,transparent,#000 36px,#000 calc(100% - 36px),transparent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i5.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i5.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i5.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i5.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i5.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: NzTableModule }, { kind: "component", type: i7$1.NzTableComponent, selector: "nz-table", inputs: ["nzTableLayout", "nzShowTotal", "nzItemRender", "nzTitle", "nzFooter", "nzNoResult", "nzPageSizeOptions", "nzVirtualItemSize", "nzVirtualMaxBufferPx", "nzVirtualMinBufferPx", "nzVirtualForTrackBy", "nzLoadingDelay", "nzPageIndex", "nzPageSize", "nzTotal", "nzWidthConfig", "nzData", "nzCustomColumn", "nzPaginationPosition", "nzScroll", "noDataVirtualHeight", "nzPaginationType", "nzFrontPagination", "nzTemplateMode", "nzShowPagination", "nzLoading", "nzOuterBordered", "nzLoadingIndicator", "nzBordered", "nzSize", "nzShowSizeChanger", "nzHideOnSinglePage", "nzShowQuickJumper", "nzSimple"], outputs: ["nzPageSizeChange", "nzPageIndexChange", "nzQueryParams", "nzCurrentPageDataChange", "nzCustomColumnChange"], exportAs: ["nzTable"] }, { kind: "component", type: i7$1.NzThAddOnComponent, selector: "th[nzColumnKey], th[nzSortFn], th[nzSortOrder], th[nzFilters], th[nzShowSort], th[nzShowFilter], th[nzCustomFilter]", inputs: ["nzColumnKey", "nzFilterMultiple", "nzSortOrder", "nzSortPriority", "nzSortDirections", "nzFilters", "nzSortFn", "nzFilterFn", "nzShowSort", "nzShowFilter", "nzCustomFilter"], outputs: ["nzCheckedChange", "nzSortOrderChange", "nzFilterChange"] }, { kind: "directive", type: i7$1.NzTableCellDirective, selector: "th:not(.nz-disable-th), td:not(.nz-disable-td)" }, { kind: "directive", type: i7$1.NzThMeasureDirective, selector: "th", inputs: ["nzWidth", "colspan", "colSpan", "rowspan", "rowSpan"] }, { kind: "component", type: i7$1.NzTheadComponent, selector: "thead:not(.ant-table-thead)", outputs: ["nzSortOrderChange"] }, { kind: "component", type: i7$1.NzTbodyComponent, selector: "tbody" }, { kind: "directive", type: i7$1.NzTrDirective, selector: "tr:not([nz-table-measure-row]):not([nzExpand]):not([nz-table-fixed-row])" }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i11.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i9.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "ngmodule", type: NzDropDownModule }, { kind: "directive", type: i11$1.NzMenuDirective, selector: "[nz-menu]", inputs: ["nzInlineIndent", "nzTheme", "nzMode", "nzInlineCollapsed", "nzSelectable"], outputs: ["nzClick"], exportAs: ["nzMenu"] }, { kind: "component", type: i11$1.NzMenuItemComponent, selector: "[nz-menu-item]", inputs: ["nzPaddingLeft", "nzDisabled", "nzSelected", "nzDanger", "nzMatchRouterExact", "nzMatchRouter"], exportAs: ["nzMenuItem"] }, { kind: "directive", type: i11$2.NzDropdownDirective, selector: "[nz-dropdown]", inputs: ["nzDropdownMenu", "nzTrigger", "nzMatchWidthElement", "nzBackdrop", "nzClickHide", "nzDisabled", "nzVisible", "nzArrow", "nzOverlayClassName", "nzOverlayStyle", "nzPlacement"], outputs: ["nzVisibleChange"], exportAs: ["nzDropdown"] }, { kind: "component", type: i11$2.NzDropdownMenuComponent, selector: "nz-dropdown-menu", exportAs: ["nzDropdownMenu"] }, { kind: "ngmodule", type: NzMenuModule }, { kind: "component", type: PaginationComponent, selector: "vl-pagination", inputs: ["itemInfo", "pageSize", "currentPage", "selectedLanguageInfo", "pages"], outputs: ["pageSizeChange", "onGoToPage"] }, { kind: "ngmodule", type: NzDatePickerModule }, { kind: "component", type: i4.NzDatePickerComponent, selector: "nz-date-picker,nz-week-picker,nz-month-picker,nz-quarter-picker,nz-year-picker,nz-range-picker", inputs: ["nzInline", "nzAllowClear", "nzAutoFocus", "nzDisabled", "nzInputReadOnly", "nzOpen", "nzDisabledDate", "nzLocale", "nzPlaceHolder", "nzPopupStyle", "nzDropdownClassName", "nzSize", "nzStatus", "nzFormat", "nzVariant", "nzDateRender", "nzDisabledTime", "nzRenderExtraFooter", "nzShowToday", "nzMode", "nzShowNow", "nzRanges", "nzDefaultPickerValue", "nzSeparator", "nzSuffixIcon", "nzBackdrop", "nzId", "nzPlacement", "nzShowWeekNumber", "nzShowTime"], outputs: ["nzOnPanelChange", "nzOnCalendarChange", "nzOnOk", "nzOnOpenChange"], exportAs: ["nzDatePicker"] }, { kind: "directive", type: i4.NzRangePickerComponent, selector: "nz-range-picker", exportAs: ["nzRangePicker"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i12.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i18.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: CustomKeyboardDirective, selector: "[vlCustomKeyboard]", inputs: ["vlCustomKeyboard"] }, { kind: "pipe", type: PlaceholderPipe, name: "vlPlaceholder" }, { kind: "pipe", type: i5.DecimalPipe, name: "number" }, { kind: "pipe", type: i5.CurrencyPipe, name: "currency" }, { kind: "pipe", type: i5.DatePipe, name: "date" }] });
|
|
5512
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: GridComponent, isStandalone: true, selector: "vl-grid", inputs: { errorMessages: "errorMessages", environment: "environment", selectedLanguageInfo: "selectedLanguageInfo", tableitemInfo: "tableitemInfo", form: "form", isTableDataLoad: "isTableDataLoad", currenTableOptions: "currenTableOptions", projectSeleted: "projectSeleted", pageSize: "pageSize", currentPage: "currentPage", pages: "pages", userInfo: "userInfo" }, outputs: { onPageChange: "onPageChange", onPageSizeChange: "onPageSizeChange", onGridScroll: "onGridScroll", showGridLink: "showGridLink", gridButtonClick: "gridButtonClick", gridIncreamentClick: "gridIncreamentClick", gridDecreamentClick: "gridDecreamentClick", gridImgClick: "gridImgClick" }, usesOnChanges: true, ngImport: i0, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: autocompletes get their suggestions from RemoteListService (completeRemote) -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\r\n project defines none; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - change: autocomplete fields take the toolkit string keyboard unless option.keyboardType\r\n names another one ('native' keeps the system keyboard)\r\n - change: date and time cells use the shared picker markup (picker-field, DateTimePickerService);\r\n their PrimeNG icon opened the calendar together with the system keyboard -->\r\n\r\n<!-- Shuaib 2026-09-24\r\n - change: grid cells render legacy checkbox fields through the boolean control. -->\r\n\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: @contract comments on the sorting, on the column filter and on the footer. The filter\r\n reads option.filter and never option.columnFilter, which is the mistake a layout author\r\n makes without being told -->\r\n<div *ngIf=\"!tableitemInfo.data?.length\" class=\"alert alert-warning col-md-12\"\r\n role=\"alert\">\r\n {{ tableitemInfo.layoutSchema?.noRecordMessage}}\r\n</div>\r\n<div *ngIf=\"tableitemInfo.data?.length\" class=\"reservation-container pt-5 px-3\">\r\n <nz-table #basicTable [nzData]=\"gridDisplayData\"\r\n [nzNoResult]=\"'No matching records found'\" [nzBordered]=\"true\"\r\n [nzFrontPagination]=\"currenTableOptions?.pagination?.tabs\"\r\n [style]=\"getStyleWithoutClasses(currenTableOptions?.style).style\"\r\n (scroll)=\"onScrollEvent($event)\"\r\n [nzLoading]=\"isTableDataLoad\">\r\n <thead>\r\n <!-- @contract decision: option.sortable is read here\r\n reality: nzSortFn is wired to getColumnSortFn since 2026-09-16; before that\r\n the key was decoration. The column also needs a scope.reference -\r\n without one getColumnSortFn has nothing to sort by.\r\n seen: grid.component.ts:338 -->\r\n <!-- Column headings and sorting -->\r\n <tr>\r\n <th *ngFor=\"let column of tableitemInfo.layoutSchema.elements\"\r\n [nzSortFn]=\"\r\n column?.option?.sortable\r\n ? getColumnSortFn(column)\r\n : false\r\n \">\r\n\r\n {{ column?.label?.caption }}\r\n </th>\r\n </tr>\r\n\r\n <!-- @contract decision: the column filter reads option.filter, NOT option.columnFilter\r\n reality: option.filter.filtering switches it on, option.filter.defaultOperator\r\n starts the comparison (the renderer falls back to contains), and\r\n option.filter.placeholder labels the input.\r\n failure: option.columnFilter is read nowhere, here or in the runtime app. A\r\n column carrying it looks filterable in the file and is not on screen.\r\n seen: grid.component.ts:450-475; 5 uses of columnFilter across the reference\r\n projects, all of them dead -->\r\n <!-- Column filters -->\r\n <tr *ngIf=\"hasAnyColumnFilter()\" class=\"grid-filter-row\">\r\n\r\n <th *ngFor=\"let column of tableitemInfo.layoutSchema.elements\"\r\n class=\"grid-filter-cell\">\r\n\r\n <ng-container *ngIf=\"\r\n column?.option?.filter?.filtering === true &&\r\n column?.scope?.reference\r\n \">\r\n\r\n <div class=\"grid-filter-input-wrapper\">\r\n <input type=\"text\" class=\"grid-filter-input\"\r\n [placeholder]=\"getColumnFilterPlaceholder(column)\"\r\n [ngModel]=\"getColumnFilterState(column).value\"\r\n (ngModelChange)=\"\r\n setColumnFilterValue(column, $event)\r\n \" />\r\n\r\n <!-- Operator selection button -->\r\n <button type=\"button\"\r\n class=\"grid-filter-operator-button\" nz-dropdown\r\n nzTrigger=\"click\"\r\n [nzDropdownMenu]=\"operatorMenu\"\r\n [class.grid-filter-operator-button--active]=\"\r\n hasActiveColumnFilter(column)\r\n \" [title]=\"\r\n 'Operator: ' +\r\n getFilterOperatorLabel(\r\n getColumnFilterState(column).operator\r\n )\r\n \">\r\n\r\n <span nz-icon nzType=\"filter\"></span>\r\n </button>\r\n\r\n <!-- Clear current column filter -->\r\n <button *ngIf=\"hasActiveColumnFilter(column)\"\r\n type=\"button\" class=\"grid-filter-clear-button\"\r\n aria-label=\"Clear column filter\"\r\n title=\"Clear filter\"\r\n (click)=\"clearColumnFilter(column)\">\r\n\r\n <span nz-icon nzType=\"close\"></span>\r\n </button>\r\n </div>\r\n\r\n <!-- Operator dropdown -->\r\n <nz-dropdown-menu #operatorMenu=\"nzDropdownMenu\">\r\n <ul nz-menu class=\"grid-filter-operator-menu\">\r\n <li *ngFor=\"let operator of filterOperators\"\r\n nz-menu-item [nzSelected]=\"\r\n getColumnFilterState(column).operator ===\r\n operator.value\r\n \" (click)=\"\r\n setColumnFilterOperator(\r\n column,\r\n operator.value\r\n )\r\n \">\r\n\r\n <span *ngIf=\"\r\n getColumnFilterState(column).operator ===\r\n operator.value\r\n \" nz-icon nzType=\"check\">\r\n </span>\r\n\r\n {{ operator.label }}\r\n </li>\r\n </ul>\r\n </nz-dropdown-menu>\r\n </ng-container>\r\n </th>\r\n </tr>\r\n </thead>\r\n <!-- @contract decision: the column chain below, and what it does not carry\r\n reality: string, number, image, quantity, date, time, dropdown, boolean,\r\n autocomplete and dateRange each have their own condition. There is no\r\n branch for the credit card type.\r\n failure: a column whose type has no branch stays empty, with no error.\r\n instead: format the value in the endpoint and send a string.\r\n\r\n @contract decision: columns without scope.reference are valid\r\n reality: with option.actionColumn they render save/delete icons, otherwise a\r\n button. That is why the conditions below all guard on the reference. -->\r\n <tbody>\r\n <tr *ngFor=\"let data of basicTable.data; let i = index\">\r\n <td *ngFor=\"let col of tableitemInfo.layoutSchema.elements\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\">\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'string'\"\r\n [class]=\"getStyleWithoutClasses(col?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\">\r\n <span *ngIf=\"col.option.protected\">\r\n <span *ngIf=\"col.option?.link\"\r\n (click)=\"showLinkDetails(col.option, data)\">\r\n {{data[col.scope?.reference.split('/').pop()]}}\r\n </span>\r\n <span *ngIf=\"!col.option?.link\">\r\n <ng-container\r\n *ngIf=\"col?.option?.dateFormat; else string\">\r\n {{\r\n data[col.scope?.reference.split('/').pop()]\r\n | date:'yyyy-MM-dd' }}\r\n </ng-container>\r\n <ng-template #string>\r\n {{\r\n data[col.scope?.reference.split('/').pop()]\r\n }}\r\n </ng-template>\r\n </span>\r\n </span>\r\n <span *ngIf=\"!col.option.protected\">\r\n <input\r\n [(ngModel)]=\"data[col.scope?.reference.split('/').pop()]\"\r\n class=\"p-inputtext p-component\" />\r\n </span>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'number'\">\r\n <span *ngIf=\"col.option.protected\">\r\n <span\r\n *ngIf=\"col.option?.innerAction?.action == 'multiply'\">\r\n {{\r\n (data[col.option?.innerAction?.from] *\r\n data[col.option?.innerAction?.to])\r\n | number:'1.2-2'\r\n }}\r\n </span>\r\n <span *ngIf=\"!col.option?.innerAction\">\r\n {{data[col.scope?.reference.split('/').pop()] |\r\n currency:\r\n tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.currency\r\n :true }}\r\n </span>\r\n </span>\r\n <span *ngIf=\"!col.option.protected\">\r\n <input\r\n [(ngModel)]=\"data[col.scope?.reference.split('/').pop()]\"\r\n type=\"number\"\r\n class=\"p-inputtext p-component\" />\r\n </span>\r\n </ng-container>\r\n\r\n <!-- Media control: also supports existing image properties -->\r\n <ng-container *ngIf=\"isMediaColumn(col)\">\r\n <ng-container\r\n *ngIf=\"getMediaSource(col, data) as source\">\r\n <ng-container [ngSwitch]=\"getMediaKind(col, data)\">\r\n\r\n <img\r\n *ngSwitchDefault\r\n [src]=\"source\"\r\n [alt]=\"getMediaLabel(col)\"\r\n [class]=\"getStyleWithoutClasses(col?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n loading=\"lazy\"\r\n (click)=\"invokeTableImageClick(col, data)\" />\r\n\r\n <video\r\n *ngSwitchCase=\"'video'\"\r\n [src]=\"source\"\r\n [attr.aria-label]=\"getMediaLabel(col)\"\r\n [class]=\"getStyleWithoutClasses(col?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n controls\r\n playsinline\r\n preload=\"metadata\">\r\n Your browser does not support video\r\n playback.\r\n </video>\r\n\r\n <audio\r\n *ngSwitchCase=\"'audio'\"\r\n [src]=\"source\"\r\n [attr.aria-label]=\"getMediaLabel(col)\"\r\n [class]=\"getStyleWithoutClasses(col?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n controls\r\n preload=\"metadata\">\r\n Your browser does not support audio\r\n playback.\r\n </audio>\r\n\r\n <a\r\n *ngSwitchCase=\"'pdf'\"\r\n [href]=\"source\"\r\n [class]=\"getStyleWithoutClasses(col?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\">\r\n Open PDF \u2014 {{ getMediaLabel(col) }}\r\n </a>\r\n\r\n <a\r\n *ngSwitchCase=\"'file'\"\r\n [href]=\"source\"\r\n [class]=\"getStyleWithoutClasses(col?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\">\r\n Open file \u2014 {{ getMediaLabel(col) }}\r\n </a>\r\n\r\n </ng-container>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- @contract compatibility: existing checkbox fields render as boolean. -->\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && (tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'boolean' || isLegacyCheckboxFieldType(tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type))\">\r\n <input type=\"checkbox\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"data[col.scope?.reference.split('/').pop()]\"\r\n [name]=\"col.scope?.reference\" />\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'dateRange'\">\r\n <nz-range-picker #dateRangeField=\"ngModel\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\"\r\n [style]=\"getStyleWithoutClasses(col?.style).style\"\r\n [(ngModel)]=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)][col.scope?.reference.split('/').pop()].value\"></nz-range-picker>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'date'\">\r\n <div class=\"picker-field\">\r\n <p-datepicker #datePicker #dateField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\"\r\n [disabled]=\"col?.option?.disabled\"\r\n [autoZIndex]=\"true\" [baseZIndex]=\"10000\"\r\n [dateFormat]=\"\r\n tableitemInfo.propertySchema[\r\n getScope(col.scope?.reference)\r\n ][\r\n col.scope?.reference.split('/').pop()\r\n ]?.format || 'yy-mm-dd'\r\n \" [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(col.scope?.reference)\r\n ][\r\n col.scope?.reference.split('/').pop()\r\n ].value\r\n \" [class]=\"col?.scope?.style?.classes?.[0] || ''\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\"\r\n appendTo=\"body\"\r\n [panelStyleClass]=\"picker.datePanelClass\"\r\n [motionOptions]=\"picker.dockedMotion\"\r\n [vlCustomKeyboard]=\"col?.option?.keyboardType ?? 'date'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, datePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(datePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\"\r\n aria-label=\"Open calendar\"\r\n (pointerdown)=\"picker.openFromIcon($event, datePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-calendar\"></i>\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'time'\">\r\n <div class=\"picker-field\">\r\n <p-datepicker #timePicker #timeField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\" [timeOnly]=\"true\"\r\n hourFormat=\"24\"\r\n [disabled]=\"col?.option?.disabled\"\r\n [autoZIndex]=\"true\"\r\n [baseZIndex]=\"10000\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(col.scope?.reference)\r\n ][\r\n col.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [class]=\"col?.scope?.style?.classes?.[0] || ''\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\"\r\n appendTo=\"body\"\r\n [panelStyleClass]=\"picker.timePanelClass\"\r\n [vlCustomKeyboard]=\"col?.option?.keyboardType ?? 'time'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, timePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(timePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\"\r\n aria-label=\"Open time picker\"\r\n (pointerdown)=\"picker.openFromIcon($event, timePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-clock\"></i>\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'autocomplete'\">\r\n <p-autocomplete\r\n [suggestions]=\"remoteSuggestions[col.scope?.reference] ?? []\"\r\n (completeMethod)=\"completeRemote($event, col)\"\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"form.propertySchema[getScope(col.scope?.reference)][col.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n [class]=\"col?.scope?.style?.classes ? col?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"col.option.autocomplete.response.label\"\r\n [placeholder]=\"form.propertySchema | vlPlaceholder: col.scope?.reference\"\r\n [vlCustomKeyboard]=\"col?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'dropdown'\">\r\n <select id=\"dynamic-dropdown\" #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(col?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(col?.scope?.style)?.style\"\r\n [name]=\"col.scope?.reference\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(col.scope?.reference)][\r\n col.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of form.propertySchema[getScope(col.scope?.reference)]?.[\r\n col.scope?.reference.split('/').pop()\r\n ]?.data\r\n \" [value]=\"item[col?.option?.optionValue]\">\r\n {{ item[col?.option?.optionLabel] }}\r\n </option>\r\n </select>\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"!col?.scope?.reference\">\r\n <span *ngIf=\"col.option?.actionColumn\">\r\n <span nz-icon\r\n *ngIf=\"setActionColumn(col.option?.save, data)\"\r\n [nzType]=\"col.option?.save?.icon\"\r\n [nzTheme]=\"'twotone'\"\r\n [style]=\"col.option?.save?.style\"\r\n [nzTwotoneColor]=\"'#52c41a'\"\r\n [class]=\"col.option?.save?.style?.classes[0]\"\r\n (click)=\"upsertAction(col.option?.save, data)\">\r\n </span>\r\n <span nz-icon\r\n *ngIf=\"setActionColumn(col.option?.delete, data)\"\r\n [nzType]=\"col.option?.delete?.icon\"\r\n [nzTheme]=\"'twotone'\"\r\n [style]=\"col.option?.delete?.style\"\r\n [nzTwotoneColor]=\"'#FF0000'\"\r\n [class]=\"col.option?.delete?.style?.classes[0]\"\r\n (click)=\"upsertAction(col.option?.delete, data)\"></span>\r\n\r\n </span>\r\n <span *ngIf=\"!col.option?.actionColumn\">\r\n <button type=\"submit\"\r\n [style]=\"getStyleWithoutClasses(col?.label?.style).style\"\r\n (click)=\"invokeGridBtnClick(col.option, data)\"\r\n [class]=\"col?.label?.style?.classes[0]\">\r\n {{ col.label?.caption }}\r\n </button>\r\n </span>\r\n </ng-container>\r\n\r\n <!-- For quantity column, display increment/decrement buttons -->\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'quantity'\">\r\n <div style=\"display: flex; align-items: center;\">\r\n <button pButton icon=\"pi pi-minus\"\r\n (click)=\"decrementQuantity(i,col.scope?.reference.split('/').pop(), col.option)\"\r\n [disabled]=\"data.quantity <= 0\"\r\n style=\"margin-right: 8px; height: 10px; width: 10px;\"></button>\r\n <span>{{ data[col.scope?.reference.split('/').pop()]\r\n }}</span>\r\n <button pButton icon=\"pi pi-plus\"\r\n (click)=\"incrementQuantity(i, col.scope?.reference.split('/').pop(), col.option)\"\r\n style=\"margin-left: 8px; height: 10px; width: 10px;\"></button>\r\n </div>\r\n </ng-container>\r\n </td>\r\n </tr>\r\n </tbody>\r\n </nz-table>\r\n</div>\r\n\r\n<!-- table footer -->\r\n<div *ngIf=\"tableitemInfo?.footer?.formFields && tableitemInfo.data?.length\"\r\n class=\"table-footer flex flex-column align-items-end gap-2 pt-2 mx-4 border-top-1 border-cyan-500\">\r\n <!-- @contract decision: the footer renders string and number only\r\n reality: the switch below carries those two cases and a default. It did NOT change\r\n when the columns gained their types on 2026-09-18.\r\n failure: any other type in a footer field renders empty.\r\n instead: keep aggregates numeric, or pre-format them as text in the endpoint. -->\r\n <div *ngFor=\"let tableField of tableitemInfo?.footer?.formFields\">\r\n <div\r\n [ngSwitch]=\"tableField?.scope?.reference && tableitemInfo.propertySchema[getScope(tableField.scope?.reference)] && tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()] ?\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.type : 'default'\">\r\n\r\n <!-- string control -->\r\n <div *ngSwitchCase=\"'string'\" class=\"form-group\">\r\n <span *ngIf=\"tableField.option.protected\">\r\n <h5 style=\"display: flex; justify-content: space-between;\">\r\n <span\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">\r\n {{\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }}\r\n </span>\r\n <span>\r\n {{\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.value\r\n }}\r\n </span>\r\n </h5>\r\n </span>\r\n <div *ngIf=\"!tableField.option.protected\">\r\n <div\r\n [class]=\"tableField?.layout == 'horizontal' ? 'horizontal-layout' : 'row'\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">{{\r\n tableField.label?.value ?\r\n tableField.label?.value :\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <input type=\"text\" #inputField=\"ngModel\"\r\n [required]=\"tableField.option.required\"\r\n [email]=\"tableField.option.email\"\r\n [class]=\"tableField.scope?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(tableField?.scope?.style).style\"\r\n [(ngModel)]=\"tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()].value\"\r\n [name]=\"tableField.scope?.reference\"\r\n [placeholder]=\"tableitemInfo.propertySchema | vlPlaceholder: tableField.scope?.reference\" />\r\n </div>\r\n <!-- Error Message -->\r\n <div *ngIf=\"inputField?.touched && inputField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(tableField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"inputField?.errors?.['required']\">{{tableField.label?.value\r\n ?\r\n tableField.label?.value :\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n +\r\n ' ' + errorMessages.required}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"inputField?.errors?.['email']\">{{(tableField.label?.value\r\n ?\r\n tableField.label?.value :\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- number control -->\r\n <div *ngSwitchCase=\"'number'\" class=\"form-group\">\r\n <span *ngIf=\"tableField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">\r\n <h5>{{\r\n form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }} {{\r\n form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.value\r\n |\r\n currency:tableField.option.currency :true:\r\n tableField.option?.decimalPoint}}\r\n </h5>\r\n </span>\r\n <span *ngIf=\"!tableField.option.protected\">\r\n <div\r\n [class]=\"tableField?.layout == 'horizontal' ? 'horizontal-layout' : 'row'\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">{{\r\n tableField.label?.value ?\r\n tableField.label?.value :\r\n form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <input type=\"number\"\r\n [class]=\"tableField.scope?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(tableField?.scope?.style).style\"\r\n [(ngModel)]=\"form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()].value\"\r\n [name]=\"tableField.scope?.reference\"\r\n [placeholder]=\"form.propertySchema | vlPlaceholder: tableField.scope?.reference\" />\r\n </div>\r\n </span>\r\n </div>\r\n\r\n <!-- button control -->\r\n <div *ngSwitchCase=\"'default'\">\r\n <button type=\"submit\"\r\n [style]=\"getStyleWithoutClasses(tableField.label.style).style\"\r\n (click)=\"invokeGridBtnClick(tableField.option, tableitemInfo.propertySchema)\">\r\n {{ tableField.label?.caption }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<!-- bottom pagination on grid -->\r\n<div\r\n *ngIf=\"tableitemInfo.data?.length && !tableitemInfo?.layoutSchema?.pagination?.top && tableitemInfo?.layoutSchema?.pagination?.bottom\">\r\n <vl-pagination [itemInfo]=\"tableitemInfo\" [pageSize]=\"pageSize\"\r\n [currentPage]=\"currentPage\" [pages]=\"pages\"\r\n (pageSizeChange)=\"invokeOnPageSizeChange($event)\"\r\n (onGoToPage)=\"invokeGoToPage($event)\">\r\n </vl-pagination>\r\n</div>\r\n", styles: [".grid-filter-row th{padding:8px;vertical-align:middle;background:#fafafa}.grid-filter-cell{min-width:150px}.grid-filter-input-wrapper{display:flex;align-items:stretch;width:100%}.grid-filter-input{width:100%;min-width:0;min-height:34px;padding:5px 8px;border:1px solid #d9d9d9;border-right:0;border-radius:4px 0 0 4px;outline:none}.grid-filter-input:focus{border-color:#1677ff}.grid-filter-operator-button,.grid-filter-clear-button{display:inline-flex;align-items:center;justify-content:center;min-width:36px;min-height:34px;padding:0;cursor:pointer;background:#fff;border:1px solid #d9d9d9}.grid-filter-operator-button{color:#595959;border-radius:0 4px 4px 0}.grid-filter-operator-button:hover,.grid-filter-operator-button--active{color:#1677ff;border-color:#1677ff}.grid-filter-clear-button{margin-left:5px;color:#ff4d4f;border-radius:4px}.grid-filter-clear-button:hover{color:#fff;background:#ff4d4f;border-color:#ff4d4f}.grid-filter-operator-menu{min-width:190px}:host ::ng-deep .ant-table-content.has-more-right{-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 36px),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 36px),transparent)}:host ::ng-deep .ant-table-content.has-more-left{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 36px),transparent);mask-image:linear-gradient(to left,#000 calc(100% - 36px),transparent)}:host ::ng-deep .ant-table-content.has-more-left.has-more-right{-webkit-mask-image:linear-gradient(to right,transparent,#000 36px,#000 calc(100% - 36px),transparent);mask-image:linear-gradient(to right,transparent,#000 36px,#000 calc(100% - 36px),transparent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1$1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1$1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: NzTableModule }, { kind: "component", type: i7$1.NzTableComponent, selector: "nz-table", inputs: ["nzTableLayout", "nzShowTotal", "nzItemRender", "nzTitle", "nzFooter", "nzNoResult", "nzPageSizeOptions", "nzVirtualItemSize", "nzVirtualMaxBufferPx", "nzVirtualMinBufferPx", "nzVirtualForTrackBy", "nzLoadingDelay", "nzPageIndex", "nzPageSize", "nzTotal", "nzWidthConfig", "nzData", "nzCustomColumn", "nzPaginationPosition", "nzScroll", "noDataVirtualHeight", "nzPaginationType", "nzFrontPagination", "nzTemplateMode", "nzShowPagination", "nzLoading", "nzOuterBordered", "nzLoadingIndicator", "nzBordered", "nzSize", "nzShowSizeChanger", "nzHideOnSinglePage", "nzShowQuickJumper", "nzSimple"], outputs: ["nzPageSizeChange", "nzPageIndexChange", "nzQueryParams", "nzCurrentPageDataChange", "nzCustomColumnChange"], exportAs: ["nzTable"] }, { kind: "component", type: i7$1.NzThAddOnComponent, selector: "th[nzColumnKey], th[nzSortFn], th[nzSortOrder], th[nzFilters], th[nzShowSort], th[nzShowFilter], th[nzCustomFilter]", inputs: ["nzColumnKey", "nzFilterMultiple", "nzSortOrder", "nzSortPriority", "nzSortDirections", "nzFilters", "nzSortFn", "nzFilterFn", "nzShowSort", "nzShowFilter", "nzCustomFilter"], outputs: ["nzCheckedChange", "nzSortOrderChange", "nzFilterChange"] }, { kind: "directive", type: i7$1.NzTableCellDirective, selector: "th:not(.nz-disable-th), td:not(.nz-disable-td)" }, { kind: "directive", type: i7$1.NzThMeasureDirective, selector: "th", inputs: ["nzWidth", "colspan", "colSpan", "rowspan", "rowSpan"] }, { kind: "component", type: i7$1.NzTheadComponent, selector: "thead:not(.ant-table-thead)", outputs: ["nzSortOrderChange"] }, { kind: "component", type: i7$1.NzTbodyComponent, selector: "tbody" }, { kind: "directive", type: i7$1.NzTrDirective, selector: "tr:not([nz-table-measure-row]):not([nzExpand]):not([nz-table-fixed-row])" }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i11.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i9.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "ngmodule", type: NzDropDownModule }, { kind: "directive", type: i11$1.NzMenuDirective, selector: "[nz-menu]", inputs: ["nzInlineIndent", "nzTheme", "nzMode", "nzInlineCollapsed", "nzSelectable"], outputs: ["nzClick"], exportAs: ["nzMenu"] }, { kind: "component", type: i11$1.NzMenuItemComponent, selector: "[nz-menu-item]", inputs: ["nzPaddingLeft", "nzDisabled", "nzSelected", "nzDanger", "nzMatchRouterExact", "nzMatchRouter"], exportAs: ["nzMenuItem"] }, { kind: "directive", type: i11$2.NzDropdownDirective, selector: "[nz-dropdown]", inputs: ["nzDropdownMenu", "nzTrigger", "nzMatchWidthElement", "nzBackdrop", "nzClickHide", "nzDisabled", "nzVisible", "nzArrow", "nzOverlayClassName", "nzOverlayStyle", "nzPlacement"], outputs: ["nzVisibleChange"], exportAs: ["nzDropdown"] }, { kind: "component", type: i11$2.NzDropdownMenuComponent, selector: "nz-dropdown-menu", exportAs: ["nzDropdownMenu"] }, { kind: "ngmodule", type: NzMenuModule }, { kind: "component", type: PaginationComponent, selector: "vl-pagination", inputs: ["itemInfo", "pageSize", "currentPage", "selectedLanguageInfo", "pages"], outputs: ["pageSizeChange", "onGoToPage"] }, { kind: "ngmodule", type: NzDatePickerModule }, { kind: "component", type: i4.NzDatePickerComponent, selector: "nz-date-picker,nz-week-picker,nz-month-picker,nz-quarter-picker,nz-year-picker,nz-range-picker", inputs: ["nzInline", "nzAllowClear", "nzAutoFocus", "nzDisabled", "nzInputReadOnly", "nzOpen", "nzDisabledDate", "nzLocale", "nzPlaceHolder", "nzPopupStyle", "nzDropdownClassName", "nzSize", "nzStatus", "nzFormat", "nzVariant", "nzDateRender", "nzDisabledTime", "nzRenderExtraFooter", "nzShowToday", "nzMode", "nzShowNow", "nzRanges", "nzDefaultPickerValue", "nzSeparator", "nzSuffixIcon", "nzBackdrop", "nzId", "nzPlacement", "nzShowWeekNumber", "nzShowTime"], outputs: ["nzOnPanelChange", "nzOnCalendarChange", "nzOnOk", "nzOnOpenChange"], exportAs: ["nzDatePicker"] }, { kind: "directive", type: i4.NzRangePickerComponent, selector: "nz-range-picker", exportAs: ["nzRangePicker"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i12.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i18.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: CustomKeyboardDirective, selector: "[vlCustomKeyboard]", inputs: ["vlCustomKeyboard"] }, { kind: "pipe", type: PlaceholderPipe, name: "vlPlaceholder" }, { kind: "pipe", type: i1$1.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$1.CurrencyPipe, name: "currency" }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }] });
|
|
5281
5513
|
}
|
|
5282
5514
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: GridComponent, decorators: [{
|
|
5283
5515
|
type: Component,
|
|
@@ -5295,7 +5527,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
5295
5527
|
AutoCompleteModule,
|
|
5296
5528
|
InputTextModule,
|
|
5297
5529
|
CustomKeyboardDirective,
|
|
5298
|
-
], selector: 'vl-grid', template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: autocompletes get their suggestions from RemoteListService (completeRemote) -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\r\n project defines none; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - change: autocomplete fields take the toolkit string keyboard unless option.keyboardType\r\n names another one ('native' keeps the system keyboard)\r\n - change: date and time cells use the shared picker markup (picker-field, DateTimePickerService);\r\n their PrimeNG icon opened the calendar together with the system keyboard -->\r\n\r\n<!-- Shuaib 2026-09-24\r\n - change: grid cells render legacy checkbox fields through the boolean control. -->\r\n\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: @contract comments on the sorting, on the column filter and on the footer. The filter\r\n reads option.filter and never option.columnFilter, which is the mistake a layout author\r\n makes without being told -->\r\n<div *ngIf=\"!tableitemInfo.data?.length\" class=\"alert alert-warning col-md-12\"\r\n role=\"alert\">\r\n {{ tableitemInfo.layoutSchema?.noRecordMessage}}\r\n</div>\r\n<div *ngIf=\"tableitemInfo.data?.length\" class=\"reservation-container pt-5 px-3\">\r\n <nz-table #basicTable [nzData]=\"gridDisplayData\"\r\n [nzNoResult]=\"'No matching records found'\" [nzBordered]=\"true\"\r\n [nzFrontPagination]=\"currenTableOptions?.pagination?.tabs\"\r\n [style]=\"getStyleWithoutClasses(currenTableOptions?.style).style\"\r\n (scroll)=\"onScrollEvent($event)\"\r\n [nzLoading]=\"isTableDataLoad\">\r\n <thead>\r\n <!-- @contract decision: option.sortable is read here\r\n reality: nzSortFn is wired to getColumnSortFn since 2026-09-16; before that\r\n the key was decoration. The column also needs a scope.reference -\r\n without one getColumnSortFn has nothing to sort by.\r\n seen: grid.component.ts:338 -->\r\n <!-- Column headings and sorting -->\r\n <tr>\r\n <th *ngFor=\"let column of tableitemInfo.layoutSchema.elements\"\r\n [nzSortFn]=\"\r\n column?.option?.sortable\r\n ? getColumnSortFn(column)\r\n : false\r\n \">\r\n\r\n {{ column?.label?.caption }}\r\n </th>\r\n </tr>\r\n\r\n <!-- @contract decision: the column filter reads option.filter, NOT option.columnFilter\r\n reality: option.filter.filtering switches it on, option.filter.defaultOperator\r\n starts the comparison (the renderer falls back to contains), and\r\n option.filter.placeholder labels the input.\r\n failure: option.columnFilter is read nowhere, here or in the runtime app. A\r\n column carrying it looks filterable in the file and is not on screen.\r\n seen: grid.component.ts:450-475; 5 uses of columnFilter across the reference\r\n projects, all of them dead -->\r\n <!-- Column filters -->\r\n <tr *ngIf=\"hasAnyColumnFilter()\" class=\"grid-filter-row\">\r\n\r\n <th *ngFor=\"let column of tableitemInfo.layoutSchema.elements\"\r\n class=\"grid-filter-cell\">\r\n\r\n <ng-container *ngIf=\"\r\n column?.option?.filter?.filtering === true &&\r\n column?.scope?.reference\r\n \">\r\n\r\n <div class=\"grid-filter-input-wrapper\">\r\n <input type=\"text\" class=\"grid-filter-input\"\r\n [placeholder]=\"getColumnFilterPlaceholder(column)\"\r\n [ngModel]=\"getColumnFilterState(column).value\"\r\n (ngModelChange)=\"\r\n setColumnFilterValue(column, $event)\r\n \" />\r\n\r\n <!-- Operator selection button -->\r\n <button type=\"button\"\r\n class=\"grid-filter-operator-button\" nz-dropdown\r\n nzTrigger=\"click\"\r\n [nzDropdownMenu]=\"operatorMenu\"\r\n [class.grid-filter-operator-button--active]=\"\r\n hasActiveColumnFilter(column)\r\n \" [title]=\"\r\n 'Operator: ' +\r\n getFilterOperatorLabel(\r\n getColumnFilterState(column).operator\r\n )\r\n \">\r\n\r\n <span nz-icon nzType=\"filter\"></span>\r\n </button>\r\n\r\n <!-- Clear current column filter -->\r\n <button *ngIf=\"hasActiveColumnFilter(column)\"\r\n type=\"button\" class=\"grid-filter-clear-button\"\r\n aria-label=\"Clear column filter\"\r\n title=\"Clear filter\"\r\n (click)=\"clearColumnFilter(column)\">\r\n\r\n <span nz-icon nzType=\"close\"></span>\r\n </button>\r\n </div>\r\n\r\n <!-- Operator dropdown -->\r\n <nz-dropdown-menu #operatorMenu=\"nzDropdownMenu\">\r\n <ul nz-menu class=\"grid-filter-operator-menu\">\r\n <li *ngFor=\"let operator of filterOperators\"\r\n nz-menu-item [nzSelected]=\"\r\n getColumnFilterState(column).operator ===\r\n operator.value\r\n \" (click)=\"\r\n setColumnFilterOperator(\r\n column,\r\n operator.value\r\n )\r\n \">\r\n\r\n <span *ngIf=\"\r\n getColumnFilterState(column).operator ===\r\n operator.value\r\n \" nz-icon nzType=\"check\">\r\n </span>\r\n\r\n {{ operator.label }}\r\n </li>\r\n </ul>\r\n </nz-dropdown-menu>\r\n </ng-container>\r\n </th>\r\n </tr>\r\n </thead>\r\n <!-- @contract decision: the column chain below, and what it does not carry\r\n reality: string, number, image, quantity, date, time, dropdown, boolean,\r\n autocomplete and dateRange each have their own condition. There is no\r\n branch for the credit card type.\r\n failure: a column whose type has no branch stays empty, with no error.\r\n instead: format the value in the endpoint and send a string.\r\n\r\n @contract decision: columns without scope.reference are valid\r\n reality: with option.actionColumn they render save/delete icons, otherwise a\r\n button. That is why the conditions below all guard on the reference. -->\r\n <tbody>\r\n <tr *ngFor=\"let data of basicTable.data; let i = index\">\r\n <td *ngFor=\"let col of tableitemInfo.layoutSchema.elements\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\">\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'string'\"\r\n [class]=\"getStyleWithoutClasses(col?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\">\r\n <span *ngIf=\"col.option.protected\">\r\n <span *ngIf=\"col.option?.link\"\r\n (click)=\"showLinkDetails(col.option, data)\">\r\n {{data[col.scope?.reference.split('/').pop()]}}\r\n </span>\r\n <span *ngIf=\"!col.option?.link\">\r\n <ng-container\r\n *ngIf=\"col?.option?.dateFormat; else string\">\r\n {{\r\n data[col.scope?.reference.split('/').pop()]\r\n | date:'yyyy-MM-dd' }}\r\n </ng-container>\r\n <ng-template #string>\r\n {{\r\n data[col.scope?.reference.split('/').pop()]\r\n }}\r\n </ng-template>\r\n </span>\r\n </span>\r\n <span *ngIf=\"!col.option.protected\">\r\n <input\r\n [(ngModel)]=\"data[col.scope?.reference.split('/').pop()]\"\r\n class=\"p-inputtext p-component\" />\r\n </span>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'number'\">\r\n <span *ngIf=\"col.option.protected\">\r\n <span\r\n *ngIf=\"col.option?.innerAction?.action == 'multiply'\">\r\n {{\r\n (data[col.option?.innerAction?.from] *\r\n data[col.option?.innerAction?.to])\r\n | number:'1.2-2'\r\n }}\r\n </span>\r\n <span *ngIf=\"!col.option?.innerAction\">\r\n {{data[col.scope?.reference.split('/').pop()] |\r\n currency:\r\n tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.currency\r\n :true }}\r\n </span>\r\n </span>\r\n <span *ngIf=\"!col.option.protected\">\r\n <input\r\n [(ngModel)]=\"data[col.scope?.reference.split('/').pop()]\"\r\n type=\"number\"\r\n class=\"p-inputtext p-component\" />\r\n </span>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'image'\">\r\n <img *ngIf=\"data[col.scope?.reference.split('/').pop()]\"\r\n [src]=\"data[col.scope?.reference.split('/').pop()]\"\r\n alt=\"Image\"\r\n (click)=\"invokeTableImageClick(col, data)\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\" />\r\n </ng-container>\r\n\r\n <!-- @contract compatibility: existing checkbox fields render as boolean. -->\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && (tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'boolean' || isLegacyCheckboxFieldType(tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type))\">\r\n <input type=\"checkbox\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"data[col.scope?.reference.split('/').pop()]\"\r\n [name]=\"col.scope?.reference\" />\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'dateRange'\">\r\n <nz-range-picker #dateRangeField=\"ngModel\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\"\r\n [style]=\"getStyleWithoutClasses(col?.style).style\"\r\n [(ngModel)]=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)][col.scope?.reference.split('/').pop()].value\"></nz-range-picker>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'date'\">\r\n <div class=\"picker-field\">\r\n <p-datepicker #datePicker #dateField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\"\r\n [disabled]=\"col?.option?.disabled\"\r\n [autoZIndex]=\"true\" [baseZIndex]=\"10000\"\r\n [dateFormat]=\"\r\n tableitemInfo.propertySchema[\r\n getScope(col.scope?.reference)\r\n ][\r\n col.scope?.reference.split('/').pop()\r\n ]?.format || 'yy-mm-dd'\r\n \" [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(col.scope?.reference)\r\n ][\r\n col.scope?.reference.split('/').pop()\r\n ].value\r\n \" [class]=\"col?.scope?.style?.classes?.[0] || ''\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\" appendTo=\"body\"\r\n [panelStyleClass]=\"picker.datePanelClass\" [motionOptions]=\"picker.dockedMotion\"\r\n [vlCustomKeyboard]=\"col?.option?.keyboardType ?? 'date'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, datePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(datePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\" aria-label=\"Open calendar\"\r\n (pointerdown)=\"picker.openFromIcon($event, datePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-calendar\"></i>\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'time'\">\r\n <div class=\"picker-field\">\r\n <p-datepicker #timePicker #timeField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\" [timeOnly]=\"true\"\r\n hourFormat=\"24\" [disabled]=\"col?.option?.disabled\" [autoZIndex]=\"true\"\r\n [baseZIndex]=\"10000\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(col.scope?.reference)\r\n ][\r\n col.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [class]=\"col?.scope?.style?.classes?.[0] || ''\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\" appendTo=\"body\"\r\n [panelStyleClass]=\"picker.timePanelClass\"\r\n [vlCustomKeyboard]=\"col?.option?.keyboardType ?? 'time'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, timePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(timePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\" aria-label=\"Open time picker\"\r\n (pointerdown)=\"picker.openFromIcon($event, timePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-clock\"></i>\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'autocomplete'\">\r\n <p-autocomplete [suggestions]=\"remoteSuggestions[col.scope?.reference] ?? []\" (completeMethod)=\"completeRemote($event, col)\" #autocompleteField=\"ngModel\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"form.propertySchema[getScope(col.scope?.reference)][col.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n [class]=\"col?.scope?.style?.classes ? col?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"col.option.autocomplete.response.label\"\r\n [placeholder]=\"form.propertySchema | vlPlaceholder: col.scope?.reference\"\r\n [vlCustomKeyboard]=\"col?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'dropdown'\">\r\n <select id=\"dynamic-dropdown\" #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(col?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(col?.scope?.style)?.style\"\r\n [name]=\"col.scope?.reference\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(col.scope?.reference)][\r\n col.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of form.propertySchema[getScope(col.scope?.reference)]?.[\r\n col.scope?.reference.split('/').pop()\r\n ]?.data\r\n \" [value]=\"item[col?.option?.optionValue]\">\r\n {{ item[col?.option?.optionLabel] }}\r\n </option>\r\n </select>\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"!col?.scope?.reference\">\r\n <span *ngIf=\"col.option?.actionColumn\">\r\n <span nz-icon\r\n *ngIf=\"setActionColumn(col.option?.save, data)\"\r\n [nzType]=\"col.option?.save?.icon\"\r\n [nzTheme]=\"'twotone'\"\r\n [style]=\"col.option?.save?.style\"\r\n [nzTwotoneColor]=\"'#52c41a'\"\r\n [class]=\"col.option?.save?.style?.classes[0]\"\r\n (click)=\"upsertAction(col.option?.save, data)\">\r\n </span>\r\n <span nz-icon\r\n *ngIf=\"setActionColumn(col.option?.delete, data)\"\r\n [nzType]=\"col.option?.delete?.icon\"\r\n [nzTheme]=\"'twotone'\"\r\n [style]=\"col.option?.delete?.style\"\r\n [nzTwotoneColor]=\"'#FF0000'\"\r\n [class]=\"col.option?.delete?.style?.classes[0]\"\r\n (click)=\"upsertAction(col.option?.delete, data)\"></span>\r\n\r\n </span>\r\n <span *ngIf=\"!col.option?.actionColumn\">\r\n <button type=\"submit\"\r\n [style]=\"getStyleWithoutClasses(col?.label?.style).style\"\r\n (click)=\"invokeGridBtnClick(col.option, data)\"\r\n [class]=\"col?.label?.style?.classes[0]\">\r\n {{ col.label?.caption }}\r\n </button>\r\n </span>\r\n </ng-container>\r\n\r\n <!-- For quantity column, display increment/decrement buttons -->\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'quantity'\">\r\n <div style=\"display: flex; align-items: center;\">\r\n <button pButton icon=\"pi pi-minus\"\r\n (click)=\"decrementQuantity(i,col.scope?.reference.split('/').pop(), col.option)\"\r\n [disabled]=\"data.quantity <= 0\"\r\n style=\"margin-right: 8px; height: 10px; width: 10px;\"></button>\r\n <span>{{ data[col.scope?.reference.split('/').pop()]\r\n }}</span>\r\n <button pButton icon=\"pi pi-plus\"\r\n (click)=\"incrementQuantity(i, col.scope?.reference.split('/').pop(), col.option)\"\r\n style=\"margin-left: 8px; height: 10px; width: 10px;\"></button>\r\n </div>\r\n </ng-container>\r\n </td>\r\n </tr>\r\n </tbody>\r\n </nz-table>\r\n</div>\r\n\r\n<!-- table footer -->\r\n<div *ngIf=\"tableitemInfo?.footer?.formFields && tableitemInfo.data?.length\"\r\n class=\"table-footer flex flex-column align-items-end gap-2 pt-2 mx-4 border-top-1 border-cyan-500\">\r\n <!-- @contract decision: the footer renders string and number only\r\n reality: the switch below carries those two cases and a default. It did NOT change\r\n when the columns gained their types on 2026-09-18.\r\n failure: any other type in a footer field renders empty.\r\n instead: keep aggregates numeric, or pre-format them as text in the endpoint. -->\r\n <div *ngFor=\"let tableField of tableitemInfo?.footer?.formFields\">\r\n <div\r\n [ngSwitch]=\"tableField?.scope?.reference && tableitemInfo.propertySchema[getScope(tableField.scope?.reference)] && tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()] ?\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.type : 'default'\">\r\n\r\n <!-- string control -->\r\n <div *ngSwitchCase=\"'string'\" class=\"form-group\">\r\n <span *ngIf=\"tableField.option.protected\">\r\n <h5 style=\"display: flex; justify-content: space-between;\">\r\n <span\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">\r\n {{\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }}\r\n </span>\r\n <span>\r\n {{\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.value\r\n }}\r\n </span>\r\n </h5>\r\n </span>\r\n <div *ngIf=\"!tableField.option.protected\">\r\n <div\r\n [class]=\"tableField?.layout == 'horizontal' ? 'horizontal-layout' : 'row'\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">{{\r\n tableField.label?.value ?\r\n tableField.label?.value :\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <input type=\"text\" #inputField=\"ngModel\"\r\n [required]=\"tableField.option.required\"\r\n [email]=\"tableField.option.email\"\r\n [class]=\"tableField.scope?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(tableField?.scope?.style).style\"\r\n [(ngModel)]=\"tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()].value\"\r\n [name]=\"tableField.scope?.reference\"\r\n [placeholder]=\"tableitemInfo.propertySchema | vlPlaceholder: tableField.scope?.reference\" />\r\n </div>\r\n <!-- Error Message -->\r\n <div *ngIf=\"inputField?.touched && inputField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(tableField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"inputField?.errors?.['required']\">{{tableField.label?.value\r\n ?\r\n tableField.label?.value :\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n +\r\n ' ' + errorMessages.required}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"inputField?.errors?.['email']\">{{(tableField.label?.value\r\n ?\r\n tableField.label?.value :\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- number control -->\r\n <div *ngSwitchCase=\"'number'\" class=\"form-group\">\r\n <span *ngIf=\"tableField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">\r\n <h5>{{\r\n form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }} {{\r\n form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.value\r\n |\r\n currency:tableField.option.currency :true:\r\n tableField.option?.decimalPoint}}\r\n </h5>\r\n </span>\r\n <span *ngIf=\"!tableField.option.protected\">\r\n <div\r\n [class]=\"tableField?.layout == 'horizontal' ? 'horizontal-layout' : 'row'\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">{{\r\n tableField.label?.value ?\r\n tableField.label?.value :\r\n form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <input type=\"number\"\r\n [class]=\"tableField.scope?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(tableField?.scope?.style).style\"\r\n [(ngModel)]=\"form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()].value\"\r\n [name]=\"tableField.scope?.reference\"\r\n [placeholder]=\"form.propertySchema | vlPlaceholder: tableField.scope?.reference\" />\r\n </div>\r\n </span>\r\n </div>\r\n\r\n <!-- button control -->\r\n <div *ngSwitchCase=\"'default'\">\r\n <button type=\"submit\"\r\n [style]=\"getStyleWithoutClasses(tableField.label.style).style\"\r\n (click)=\"invokeGridBtnClick(tableField.option, tableitemInfo.propertySchema)\">\r\n {{ tableField.label?.caption }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<!-- bottom pagination on grid -->\r\n<div\r\n *ngIf=\"tableitemInfo.data?.length && !tableitemInfo?.layoutSchema?.pagination?.top && tableitemInfo?.layoutSchema?.pagination?.bottom\">\r\n <vl-pagination [itemInfo]=\"tableitemInfo\" [pageSize]=\"pageSize\"\r\n [currentPage]=\"currentPage\" [pages]=\"pages\"\r\n (pageSizeChange)=\"invokeOnPageSizeChange($event)\"\r\n (onGoToPage)=\"invokeGoToPage($event)\">\r\n </vl-pagination>\r\n</div>\r\n", styles: [".grid-filter-row th{padding:8px;vertical-align:middle;background:#fafafa}.grid-filter-cell{min-width:150px}.grid-filter-input-wrapper{display:flex;align-items:stretch;width:100%}.grid-filter-input{width:100%;min-width:0;min-height:34px;padding:5px 8px;border:1px solid #d9d9d9;border-right:0;border-radius:4px 0 0 4px;outline:none}.grid-filter-input:focus{border-color:#1677ff}.grid-filter-operator-button,.grid-filter-clear-button{display:inline-flex;align-items:center;justify-content:center;min-width:36px;min-height:34px;padding:0;cursor:pointer;background:#fff;border:1px solid #d9d9d9}.grid-filter-operator-button{color:#595959;border-radius:0 4px 4px 0}.grid-filter-operator-button:hover,.grid-filter-operator-button--active{color:#1677ff;border-color:#1677ff}.grid-filter-clear-button{margin-left:5px;color:#ff4d4f;border-radius:4px}.grid-filter-clear-button:hover{color:#fff;background:#ff4d4f;border-color:#ff4d4f}.grid-filter-operator-menu{min-width:190px}:host ::ng-deep .ant-table-content.has-more-right{-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 36px),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 36px),transparent)}:host ::ng-deep .ant-table-content.has-more-left{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 36px),transparent);mask-image:linear-gradient(to left,#000 calc(100% - 36px),transparent)}:host ::ng-deep .ant-table-content.has-more-left.has-more-right{-webkit-mask-image:linear-gradient(to right,transparent,#000 36px,#000 calc(100% - 36px),transparent);mask-image:linear-gradient(to right,transparent,#000 36px,#000 calc(100% - 36px),transparent)}\n"] }]
|
|
5530
|
+
], selector: 'vl-grid', template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: autocompletes get their suggestions from RemoteListService (completeRemote) -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\r\n project defines none; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - change: autocomplete fields take the toolkit string keyboard unless option.keyboardType\r\n names another one ('native' keeps the system keyboard)\r\n - change: date and time cells use the shared picker markup (picker-field, DateTimePickerService);\r\n their PrimeNG icon opened the calendar together with the system keyboard -->\r\n\r\n<!-- Shuaib 2026-09-24\r\n - change: grid cells render legacy checkbox fields through the boolean control. -->\r\n\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: @contract comments on the sorting, on the column filter and on the footer. The filter\r\n reads option.filter and never option.columnFilter, which is the mistake a layout author\r\n makes without being told -->\r\n<div *ngIf=\"!tableitemInfo.data?.length\" class=\"alert alert-warning col-md-12\"\r\n role=\"alert\">\r\n {{ tableitemInfo.layoutSchema?.noRecordMessage}}\r\n</div>\r\n<div *ngIf=\"tableitemInfo.data?.length\" class=\"reservation-container pt-5 px-3\">\r\n <nz-table #basicTable [nzData]=\"gridDisplayData\"\r\n [nzNoResult]=\"'No matching records found'\" [nzBordered]=\"true\"\r\n [nzFrontPagination]=\"currenTableOptions?.pagination?.tabs\"\r\n [style]=\"getStyleWithoutClasses(currenTableOptions?.style).style\"\r\n (scroll)=\"onScrollEvent($event)\"\r\n [nzLoading]=\"isTableDataLoad\">\r\n <thead>\r\n <!-- @contract decision: option.sortable is read here\r\n reality: nzSortFn is wired to getColumnSortFn since 2026-09-16; before that\r\n the key was decoration. The column also needs a scope.reference -\r\n without one getColumnSortFn has nothing to sort by.\r\n seen: grid.component.ts:338 -->\r\n <!-- Column headings and sorting -->\r\n <tr>\r\n <th *ngFor=\"let column of tableitemInfo.layoutSchema.elements\"\r\n [nzSortFn]=\"\r\n column?.option?.sortable\r\n ? getColumnSortFn(column)\r\n : false\r\n \">\r\n\r\n {{ column?.label?.caption }}\r\n </th>\r\n </tr>\r\n\r\n <!-- @contract decision: the column filter reads option.filter, NOT option.columnFilter\r\n reality: option.filter.filtering switches it on, option.filter.defaultOperator\r\n starts the comparison (the renderer falls back to contains), and\r\n option.filter.placeholder labels the input.\r\n failure: option.columnFilter is read nowhere, here or in the runtime app. A\r\n column carrying it looks filterable in the file and is not on screen.\r\n seen: grid.component.ts:450-475; 5 uses of columnFilter across the reference\r\n projects, all of them dead -->\r\n <!-- Column filters -->\r\n <tr *ngIf=\"hasAnyColumnFilter()\" class=\"grid-filter-row\">\r\n\r\n <th *ngFor=\"let column of tableitemInfo.layoutSchema.elements\"\r\n class=\"grid-filter-cell\">\r\n\r\n <ng-container *ngIf=\"\r\n column?.option?.filter?.filtering === true &&\r\n column?.scope?.reference\r\n \">\r\n\r\n <div class=\"grid-filter-input-wrapper\">\r\n <input type=\"text\" class=\"grid-filter-input\"\r\n [placeholder]=\"getColumnFilterPlaceholder(column)\"\r\n [ngModel]=\"getColumnFilterState(column).value\"\r\n (ngModelChange)=\"\r\n setColumnFilterValue(column, $event)\r\n \" />\r\n\r\n <!-- Operator selection button -->\r\n <button type=\"button\"\r\n class=\"grid-filter-operator-button\" nz-dropdown\r\n nzTrigger=\"click\"\r\n [nzDropdownMenu]=\"operatorMenu\"\r\n [class.grid-filter-operator-button--active]=\"\r\n hasActiveColumnFilter(column)\r\n \" [title]=\"\r\n 'Operator: ' +\r\n getFilterOperatorLabel(\r\n getColumnFilterState(column).operator\r\n )\r\n \">\r\n\r\n <span nz-icon nzType=\"filter\"></span>\r\n </button>\r\n\r\n <!-- Clear current column filter -->\r\n <button *ngIf=\"hasActiveColumnFilter(column)\"\r\n type=\"button\" class=\"grid-filter-clear-button\"\r\n aria-label=\"Clear column filter\"\r\n title=\"Clear filter\"\r\n (click)=\"clearColumnFilter(column)\">\r\n\r\n <span nz-icon nzType=\"close\"></span>\r\n </button>\r\n </div>\r\n\r\n <!-- Operator dropdown -->\r\n <nz-dropdown-menu #operatorMenu=\"nzDropdownMenu\">\r\n <ul nz-menu class=\"grid-filter-operator-menu\">\r\n <li *ngFor=\"let operator of filterOperators\"\r\n nz-menu-item [nzSelected]=\"\r\n getColumnFilterState(column).operator ===\r\n operator.value\r\n \" (click)=\"\r\n setColumnFilterOperator(\r\n column,\r\n operator.value\r\n )\r\n \">\r\n\r\n <span *ngIf=\"\r\n getColumnFilterState(column).operator ===\r\n operator.value\r\n \" nz-icon nzType=\"check\">\r\n </span>\r\n\r\n {{ operator.label }}\r\n </li>\r\n </ul>\r\n </nz-dropdown-menu>\r\n </ng-container>\r\n </th>\r\n </tr>\r\n </thead>\r\n <!-- @contract decision: the column chain below, and what it does not carry\r\n reality: string, number, image, quantity, date, time, dropdown, boolean,\r\n autocomplete and dateRange each have their own condition. There is no\r\n branch for the credit card type.\r\n failure: a column whose type has no branch stays empty, with no error.\r\n instead: format the value in the endpoint and send a string.\r\n\r\n @contract decision: columns without scope.reference are valid\r\n reality: with option.actionColumn they render save/delete icons, otherwise a\r\n button. That is why the conditions below all guard on the reference. -->\r\n <tbody>\r\n <tr *ngFor=\"let data of basicTable.data; let i = index\">\r\n <td *ngFor=\"let col of tableitemInfo.layoutSchema.elements\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\">\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'string'\"\r\n [class]=\"getStyleWithoutClasses(col?.scope?.style).classes\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\">\r\n <span *ngIf=\"col.option.protected\">\r\n <span *ngIf=\"col.option?.link\"\r\n (click)=\"showLinkDetails(col.option, data)\">\r\n {{data[col.scope?.reference.split('/').pop()]}}\r\n </span>\r\n <span *ngIf=\"!col.option?.link\">\r\n <ng-container\r\n *ngIf=\"col?.option?.dateFormat; else string\">\r\n {{\r\n data[col.scope?.reference.split('/').pop()]\r\n | date:'yyyy-MM-dd' }}\r\n </ng-container>\r\n <ng-template #string>\r\n {{\r\n data[col.scope?.reference.split('/').pop()]\r\n }}\r\n </ng-template>\r\n </span>\r\n </span>\r\n <span *ngIf=\"!col.option.protected\">\r\n <input\r\n [(ngModel)]=\"data[col.scope?.reference.split('/').pop()]\"\r\n class=\"p-inputtext p-component\" />\r\n </span>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'number'\">\r\n <span *ngIf=\"col.option.protected\">\r\n <span\r\n *ngIf=\"col.option?.innerAction?.action == 'multiply'\">\r\n {{\r\n (data[col.option?.innerAction?.from] *\r\n data[col.option?.innerAction?.to])\r\n | number:'1.2-2'\r\n }}\r\n </span>\r\n <span *ngIf=\"!col.option?.innerAction\">\r\n {{data[col.scope?.reference.split('/').pop()] |\r\n currency:\r\n tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.currency\r\n :true }}\r\n </span>\r\n </span>\r\n <span *ngIf=\"!col.option.protected\">\r\n <input\r\n [(ngModel)]=\"data[col.scope?.reference.split('/').pop()]\"\r\n type=\"number\"\r\n class=\"p-inputtext p-component\" />\r\n </span>\r\n </ng-container>\r\n\r\n <!-- Media control: also supports existing image properties -->\r\n <ng-container *ngIf=\"isMediaColumn(col)\">\r\n <ng-container\r\n *ngIf=\"getMediaSource(col, data) as source\">\r\n <ng-container [ngSwitch]=\"getMediaKind(col, data)\">\r\n\r\n <img\r\n *ngSwitchDefault\r\n [src]=\"source\"\r\n [alt]=\"getMediaLabel(col)\"\r\n [class]=\"getStyleWithoutClasses(col?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n loading=\"lazy\"\r\n (click)=\"invokeTableImageClick(col, data)\" />\r\n\r\n <video\r\n *ngSwitchCase=\"'video'\"\r\n [src]=\"source\"\r\n [attr.aria-label]=\"getMediaLabel(col)\"\r\n [class]=\"getStyleWithoutClasses(col?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n controls\r\n playsinline\r\n preload=\"metadata\">\r\n Your browser does not support video\r\n playback.\r\n </video>\r\n\r\n <audio\r\n *ngSwitchCase=\"'audio'\"\r\n [src]=\"source\"\r\n [attr.aria-label]=\"getMediaLabel(col)\"\r\n [class]=\"getStyleWithoutClasses(col?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n controls\r\n preload=\"metadata\">\r\n Your browser does not support audio\r\n playback.\r\n </audio>\r\n\r\n <a\r\n *ngSwitchCase=\"'pdf'\"\r\n [href]=\"source\"\r\n [class]=\"getStyleWithoutClasses(col?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\">\r\n Open PDF \u2014 {{ getMediaLabel(col) }}\r\n </a>\r\n\r\n <a\r\n *ngSwitchCase=\"'file'\"\r\n [href]=\"source\"\r\n [class]=\"getStyleWithoutClasses(col?.scope?.style).classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\">\r\n Open file \u2014 {{ getMediaLabel(col) }}\r\n </a>\r\n\r\n </ng-container>\r\n </ng-container>\r\n </ng-container>\r\n\r\n <!-- @contract compatibility: existing checkbox fields render as boolean. -->\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && (tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'boolean' || isLegacyCheckboxFieldType(tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type))\">\r\n <input type=\"checkbox\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n class=\"form-check-input mr-2\"\r\n [(ngModel)]=\"data[col.scope?.reference.split('/').pop()]\"\r\n [name]=\"col.scope?.reference\" />\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'dateRange'\">\r\n <nz-range-picker #dateRangeField=\"ngModel\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\"\r\n [style]=\"getStyleWithoutClasses(col?.style).style\"\r\n [(ngModel)]=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)][col.scope?.reference.split('/').pop()].value\"></nz-range-picker>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'date'\">\r\n <div class=\"picker-field\">\r\n <p-datepicker #datePicker #dateField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\"\r\n [disabled]=\"col?.option?.disabled\"\r\n [autoZIndex]=\"true\" [baseZIndex]=\"10000\"\r\n [dateFormat]=\"\r\n tableitemInfo.propertySchema[\r\n getScope(col.scope?.reference)\r\n ][\r\n col.scope?.reference.split('/').pop()\r\n ]?.format || 'yy-mm-dd'\r\n \" [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(col.scope?.reference)\r\n ][\r\n col.scope?.reference.split('/').pop()\r\n ].value\r\n \" [class]=\"col?.scope?.style?.classes?.[0] || ''\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\"\r\n appendTo=\"body\"\r\n [panelStyleClass]=\"picker.datePanelClass\"\r\n [motionOptions]=\"picker.dockedMotion\"\r\n [vlCustomKeyboard]=\"col?.option?.keyboardType ?? 'date'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, datePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(datePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\"\r\n aria-label=\"Open calendar\"\r\n (pointerdown)=\"picker.openFromIcon($event, datePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-calendar\"></i>\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'time'\">\r\n <div class=\"picker-field\">\r\n <p-datepicker #timePicker #timeField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\" [timeOnly]=\"true\"\r\n hourFormat=\"24\"\r\n [disabled]=\"col?.option?.disabled\"\r\n [autoZIndex]=\"true\"\r\n [baseZIndex]=\"10000\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n [(ngModel)]=\"\r\n form.propertySchema[\r\n getScope(col.scope?.reference)\r\n ][\r\n col.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [class]=\"col?.scope?.style?.classes?.[0] || ''\"\r\n [showIcon]=\"false\" [showOnFocus]=\"false\"\r\n appendTo=\"body\"\r\n [panelStyleClass]=\"picker.timePanelClass\"\r\n [vlCustomKeyboard]=\"col?.option?.keyboardType ?? 'time'\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, timePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(timePicker)\">\r\n </p-datepicker>\r\n <button type=\"button\" class=\"picker-field__icon\"\r\n aria-label=\"Open time picker\"\r\n (pointerdown)=\"picker.openFromIcon($event, timePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-clock\"></i>\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'autocomplete'\">\r\n <p-autocomplete\r\n [suggestions]=\"remoteSuggestions[col.scope?.reference] ?? []\"\r\n (completeMethod)=\"completeRemote($event, col)\"\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"col.option.required\"\r\n [name]=\"col.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"form.propertySchema[getScope(col.scope?.reference)][col.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(col?.scope?.style).style\"\r\n [class]=\"col?.scope?.style?.classes ? col?.scope?.style?.classes[0] : ''\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"col.option.autocomplete.response.label\"\r\n [placeholder]=\"form.propertySchema | vlPlaceholder: col.scope?.reference\"\r\n [vlCustomKeyboard]=\"col?.option?.keyboardType ?? 'string'\">\r\n </p-autocomplete>\r\n </ng-container>\r\n\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'dropdown'\">\r\n <select id=\"dynamic-dropdown\" #dropdownField=\"ngModel\"\r\n [ngClass]=\"getStyleWithoutClasses(col?.scope?.style)?.classes\"\r\n [ngStyle]=\"getStyleWithoutClasses(col?.scope?.style)?.style\"\r\n [name]=\"col.scope?.reference\"\r\n [(ngModel)]=\"\r\n form.propertySchema[getScope(col.scope?.reference)][\r\n col.scope?.reference.split('/').pop()\r\n ].value\r\n \">\r\n <option *ngFor=\"\r\n let item of form.propertySchema[getScope(col.scope?.reference)]?.[\r\n col.scope?.reference.split('/').pop()\r\n ]?.data\r\n \" [value]=\"item[col?.option?.optionValue]\">\r\n {{ item[col?.option?.optionLabel] }}\r\n </option>\r\n </select>\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"!col?.scope?.reference\">\r\n <span *ngIf=\"col.option?.actionColumn\">\r\n <span nz-icon\r\n *ngIf=\"setActionColumn(col.option?.save, data)\"\r\n [nzType]=\"col.option?.save?.icon\"\r\n [nzTheme]=\"'twotone'\"\r\n [style]=\"col.option?.save?.style\"\r\n [nzTwotoneColor]=\"'#52c41a'\"\r\n [class]=\"col.option?.save?.style?.classes[0]\"\r\n (click)=\"upsertAction(col.option?.save, data)\">\r\n </span>\r\n <span nz-icon\r\n *ngIf=\"setActionColumn(col.option?.delete, data)\"\r\n [nzType]=\"col.option?.delete?.icon\"\r\n [nzTheme]=\"'twotone'\"\r\n [style]=\"col.option?.delete?.style\"\r\n [nzTwotoneColor]=\"'#FF0000'\"\r\n [class]=\"col.option?.delete?.style?.classes[0]\"\r\n (click)=\"upsertAction(col.option?.delete, data)\"></span>\r\n\r\n </span>\r\n <span *ngIf=\"!col.option?.actionColumn\">\r\n <button type=\"submit\"\r\n [style]=\"getStyleWithoutClasses(col?.label?.style).style\"\r\n (click)=\"invokeGridBtnClick(col.option, data)\"\r\n [class]=\"col?.label?.style?.classes[0]\">\r\n {{ col.label?.caption }}\r\n </button>\r\n </span>\r\n </ng-container>\r\n\r\n <!-- For quantity column, display increment/decrement buttons -->\r\n <ng-container\r\n *ngIf=\"tableitemInfo.propertySchema[getScope(col.scope?.reference)] && tableitemInfo.propertySchema[getScope(col.scope?.reference)][col?.scope?.reference?.split('/').pop()]?.type === 'quantity'\">\r\n <div style=\"display: flex; align-items: center;\">\r\n <button pButton icon=\"pi pi-minus\"\r\n (click)=\"decrementQuantity(i,col.scope?.reference.split('/').pop(), col.option)\"\r\n [disabled]=\"data.quantity <= 0\"\r\n style=\"margin-right: 8px; height: 10px; width: 10px;\"></button>\r\n <span>{{ data[col.scope?.reference.split('/').pop()]\r\n }}</span>\r\n <button pButton icon=\"pi pi-plus\"\r\n (click)=\"incrementQuantity(i, col.scope?.reference.split('/').pop(), col.option)\"\r\n style=\"margin-left: 8px; height: 10px; width: 10px;\"></button>\r\n </div>\r\n </ng-container>\r\n </td>\r\n </tr>\r\n </tbody>\r\n </nz-table>\r\n</div>\r\n\r\n<!-- table footer -->\r\n<div *ngIf=\"tableitemInfo?.footer?.formFields && tableitemInfo.data?.length\"\r\n class=\"table-footer flex flex-column align-items-end gap-2 pt-2 mx-4 border-top-1 border-cyan-500\">\r\n <!-- @contract decision: the footer renders string and number only\r\n reality: the switch below carries those two cases and a default. It did NOT change\r\n when the columns gained their types on 2026-09-18.\r\n failure: any other type in a footer field renders empty.\r\n instead: keep aggregates numeric, or pre-format them as text in the endpoint. -->\r\n <div *ngFor=\"let tableField of tableitemInfo?.footer?.formFields\">\r\n <div\r\n [ngSwitch]=\"tableField?.scope?.reference && tableitemInfo.propertySchema[getScope(tableField.scope?.reference)] && tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()] ?\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.type : 'default'\">\r\n\r\n <!-- string control -->\r\n <div *ngSwitchCase=\"'string'\" class=\"form-group\">\r\n <span *ngIf=\"tableField.option.protected\">\r\n <h5 style=\"display: flex; justify-content: space-between;\">\r\n <span\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">\r\n {{\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }}\r\n </span>\r\n <span>\r\n {{\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.value\r\n }}\r\n </span>\r\n </h5>\r\n </span>\r\n <div *ngIf=\"!tableField.option.protected\">\r\n <div\r\n [class]=\"tableField?.layout == 'horizontal' ? 'horizontal-layout' : 'row'\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">{{\r\n tableField.label?.value ?\r\n tableField.label?.value :\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <input type=\"text\" #inputField=\"ngModel\"\r\n [required]=\"tableField.option.required\"\r\n [email]=\"tableField.option.email\"\r\n [class]=\"tableField.scope?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(tableField?.scope?.style).style\"\r\n [(ngModel)]=\"tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()].value\"\r\n [name]=\"tableField.scope?.reference\"\r\n [placeholder]=\"tableitemInfo.propertySchema | vlPlaceholder: tableField.scope?.reference\" />\r\n </div>\r\n <!-- Error Message -->\r\n <div *ngIf=\"inputField?.touched && inputField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(tableField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"inputField?.errors?.['required']\">{{tableField.label?.value\r\n ?\r\n tableField.label?.value :\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n +\r\n ' ' + errorMessages.required}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"inputField?.errors?.['email']\">{{(tableField.label?.value\r\n ?\r\n tableField.label?.value :\r\n tableitemInfo.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- number control -->\r\n <div *ngSwitchCase=\"'number'\" class=\"form-group\">\r\n <span *ngIf=\"tableField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">\r\n <h5>{{\r\n form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }} {{\r\n form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.value\r\n |\r\n currency:tableField.option.currency :true:\r\n tableField.option?.decimalPoint}}\r\n </h5>\r\n </span>\r\n <span *ngIf=\"!tableField.option.protected\">\r\n <div\r\n [class]=\"tableField?.layout == 'horizontal' ? 'horizontal-layout' : 'row'\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(tableField?.label?.style).style\"\r\n [class]=\"tableField?.label?.style?.classes[0]\">{{\r\n tableField.label?.value ?\r\n tableField.label?.value :\r\n form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <input type=\"number\"\r\n [class]=\"tableField.scope?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(tableField?.scope?.style).style\"\r\n [(ngModel)]=\"form.propertySchema[getScope(tableField.scope?.reference)][tableField.scope?.reference.split('/').pop()].value\"\r\n [name]=\"tableField.scope?.reference\"\r\n [placeholder]=\"form.propertySchema | vlPlaceholder: tableField.scope?.reference\" />\r\n </div>\r\n </span>\r\n </div>\r\n\r\n <!-- button control -->\r\n <div *ngSwitchCase=\"'default'\">\r\n <button type=\"submit\"\r\n [style]=\"getStyleWithoutClasses(tableField.label.style).style\"\r\n (click)=\"invokeGridBtnClick(tableField.option, tableitemInfo.propertySchema)\">\r\n {{ tableField.label?.caption }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n\r\n<!-- bottom pagination on grid -->\r\n<div\r\n *ngIf=\"tableitemInfo.data?.length && !tableitemInfo?.layoutSchema?.pagination?.top && tableitemInfo?.layoutSchema?.pagination?.bottom\">\r\n <vl-pagination [itemInfo]=\"tableitemInfo\" [pageSize]=\"pageSize\"\r\n [currentPage]=\"currentPage\" [pages]=\"pages\"\r\n (pageSizeChange)=\"invokeOnPageSizeChange($event)\"\r\n (onGoToPage)=\"invokeGoToPage($event)\">\r\n </vl-pagination>\r\n</div>\r\n", styles: [".grid-filter-row th{padding:8px;vertical-align:middle;background:#fafafa}.grid-filter-cell{min-width:150px}.grid-filter-input-wrapper{display:flex;align-items:stretch;width:100%}.grid-filter-input{width:100%;min-width:0;min-height:34px;padding:5px 8px;border:1px solid #d9d9d9;border-right:0;border-radius:4px 0 0 4px;outline:none}.grid-filter-input:focus{border-color:#1677ff}.grid-filter-operator-button,.grid-filter-clear-button{display:inline-flex;align-items:center;justify-content:center;min-width:36px;min-height:34px;padding:0;cursor:pointer;background:#fff;border:1px solid #d9d9d9}.grid-filter-operator-button{color:#595959;border-radius:0 4px 4px 0}.grid-filter-operator-button:hover,.grid-filter-operator-button--active{color:#1677ff;border-color:#1677ff}.grid-filter-clear-button{margin-left:5px;color:#ff4d4f;border-radius:4px}.grid-filter-clear-button:hover{color:#fff;background:#ff4d4f;border-color:#ff4d4f}.grid-filter-operator-menu{min-width:190px}:host ::ng-deep .ant-table-content.has-more-right{-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 36px),transparent);mask-image:linear-gradient(to right,#000 calc(100% - 36px),transparent)}:host ::ng-deep .ant-table-content.has-more-left{-webkit-mask-image:linear-gradient(to left,#000 calc(100% - 36px),transparent);mask-image:linear-gradient(to left,#000 calc(100% - 36px),transparent)}:host ::ng-deep .ant-table-content.has-more-left.has-more-right{-webkit-mask-image:linear-gradient(to right,transparent,#000 36px,#000 calc(100% - 36px),transparent);mask-image:linear-gradient(to right,transparent,#000 36px,#000 calc(100% - 36px),transparent)}\n"] }]
|
|
5299
5531
|
}], ctorParameters: () => [{ type: RequestService }, { type: UtilityService }, { type: NotificationService }, { type: i4$1.OktaAuthStateService }], propDecorators: { errorMessages: [{
|
|
5300
5532
|
type: Input
|
|
5301
5533
|
}], environment: [{
|
|
@@ -5461,7 +5693,7 @@ class HeaderMenuComponent {
|
|
|
5461
5693
|
const logoUrl = item?.option?.src;
|
|
5462
5694
|
if (logoUrl && this.projectSeleted) {
|
|
5463
5695
|
let projectName = `${this.tenantName}_${this.projectSeleted}`;
|
|
5464
|
-
const image = await this.couchdbService.
|
|
5696
|
+
const image = await this.couchdbService.getImageSrcFromCouchDb(projectName, `${logoUrl}`);
|
|
5465
5697
|
item.option.src = this.utilityService.getMediaSrc(image);
|
|
5466
5698
|
}
|
|
5467
5699
|
}
|
|
@@ -5469,7 +5701,7 @@ class HeaderMenuComponent {
|
|
|
5469
5701
|
return items;
|
|
5470
5702
|
}
|
|
5471
5703
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: HeaderMenuComponent, deps: [{ token: i4$1.OktaAuthStateService }, { token: RequestService }, { token: UtilityService }, { token: Couchdb }, { token: i1.Platform }], target: i0.ɵɵFactoryTarget.Component });
|
|
5472
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.2", type: HeaderMenuComponent, isStandalone: true, selector: "vl-header-menu", inputs: { headerItems: "headerItems", headerLayout: "headerLayout", environment: "environment", tenantName: "tenantName", projectSeleted: "projectSeleted", cartInfo: "cartInfo" }, outputs: { buttonClick: "buttonClick", showTableContent: "showTableContent", searchItems: "searchItems" }, usesOnChanges: true, ngImport: i0, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: a field without a placeholder in the project stays empty; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: a @contract comment stating that this container resolves by field NAME, not by field\r\n type. A correctly typed field with another name renders nothing, silently -->\r\n<header *ngIf=\"headerItems.length > 0\" [ngStyle]=\"getStyle(headerLayout?.style)\">\r\n\r\n <!-- @contract decision: resolves by field NAME, not by field type\r\n reality: the switch reads the LAST segment of scope.reference. Only logo, cart and\r\n searchInput render; any other name produces an empty element.\r\n failure: a correctly typed field with a different name renders nothing, silently.\r\n instead: name the field logo, cart or searchInput, or put it in another container -->\r\n <ng-container *ngFor=\"let header of headerItems\"\r\n [ngSwitch]=\"header.scope?.reference ? header.scope?.reference.split('/').pop() : 'default'\">\r\n\r\n <!-- Logo -->\r\n <ng-container *ngSwitchCase=\"'logo'\">\r\n <div *ngIf=\"isVisible(header)\" [ngStyle]=\"getStyle(header?.option?.style)\">\r\n\r\n <img [src]=\"header.option.src\" [alt]=\"header.alt || 'Logo'\"\r\n [ngStyle]=\"getStyle(header?.scope?.style)\" />\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Search -->\r\n <ng-container *ngSwitchCase=\"'searchInput'\">\r\n <div *ngIf=\"isVisible(header)\" [ngStyle]=\"getStyle(header?.option?.style)\">\r\n\r\n <input #search type=\"text\" [placeholder]=\"header.option.placeholder ?? ''\"\r\n (keyup.enter)=\"invokeSearchedItems(header.option, search)\"\r\n [ngStyle]=\"getStyle(header?.scope?.style)\" />\r\n\r\n <button *ngIf=\"header.option.button !== false\" type=\"button\"\r\n (click)=\"invokeSearchedItems(header.option, search)\" [ngStyle]=\"getStyle(header?.buttonStyle)\">\r\n <ng-container *ngIf=\"!header.caption; else searchTextContent\">\r\n <i nz-icon [nzType]=\"header.icon\" nzTheme=\"outline\"></i>\r\n </ng-container>\r\n\r\n <ng-template #searchTextContent>\r\n {{ header.caption }}\r\n </ng-template>\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Cart -->\r\n <ng-container *ngSwitchCase=\"'cart'\">\r\n <div *ngIf=\"isVisible(header)\" [ngStyle]=\"getStyle(header?.option?.style)\"\r\n (click)=\"invokeShowtableContent(header.option)\">\r\n\r\n <span [ngStyle]=\"getStyle(header?.priceStyle)\">\r\n {{ cartInfo.totalPrice | currency : header.option?.currency ? header.option.currency : 'USD' }}\r\n </span>\r\n\r\n <span [ngStyle]=\"getStyle(header?.quantityStyle)\">\r\n {{ cartInfo.quantity }}\r\n </span>\r\n\r\n <i [class]=\"header.icon\" [ngStyle]=\"getStyle(header?.iconStyle)\">\r\n </i>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Default Button -->\r\n <ng-container *ngSwitchCase=\"'default'\">\r\n @if (header.option && header.option?.action && (header.option?.action == \"cart\")) {\r\n <div *ngIf=\"isVisible(header)\" [ngStyle]=\"getStyle(header?.option?.style)\"\r\n (click)=\"invokeShowtableContent(header.option)\">\r\n\r\n <span [ngStyle]=\"getStyle(header?.priceStyle)\">\r\n {{ cartInfo.totalPrice | currency : header.option?.currency ? header.option.currency : 'USD' }}\r\n </span>\r\n\r\n <span [ngStyle]=\"getStyle(header?.quantityStyle)\">\r\n {{ cartInfo.quantity }}\r\n </span>\r\n\r\n <i [class]=\"header.icon\" [ngStyle]=\"getStyle(header?.iconStyle)\">\r\n </i>\r\n </div>\r\n }@else {\r\n <div *ngIf=\"shouldShowDefaultButton(header)\" [ngStyle]=\"getStyle(header?.option?.style)\">\r\n\r\n <button type=\"button\" (click)=\"invokeHeaderButtonClick(header.option)\"\r\n [ngStyle]=\"getStyle(header?.buttonStyle)\">\r\n <ng-container *ngIf=\"!header.label; else buttonTextContent\">\r\n <i nz-icon [nzType]=\"header.icon\" nzTheme=\"outline\"></i>\r\n </ng-container>\r\n\r\n <ng-template #buttonTextContent>\r\n {{ header.label }}\r\n </ng-template>\r\n </button>\r\n </div>\r\n }\r\n </ng-container>\r\n\r\n </ng-container>\r\n</header>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type:
|
|
5704
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.2", type: HeaderMenuComponent, isStandalone: true, selector: "vl-header-menu", inputs: { headerItems: "headerItems", headerLayout: "headerLayout", environment: "environment", tenantName: "tenantName", projectSeleted: "projectSeleted", cartInfo: "cartInfo" }, outputs: { buttonClick: "buttonClick", showTableContent: "showTableContent", searchItems: "searchItems" }, usesOnChanges: true, ngImport: i0, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: a field without a placeholder in the project stays empty; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: a @contract comment stating that this container resolves by field NAME, not by field\r\n type. A correctly typed field with another name renders nothing, silently -->\r\n<header *ngIf=\"headerItems.length > 0\" [ngStyle]=\"getStyle(headerLayout?.style)\">\r\n\r\n <!-- @contract decision: resolves by field NAME, not by field type\r\n reality: the switch reads the LAST segment of scope.reference. Only logo, cart and\r\n searchInput render; any other name produces an empty element.\r\n failure: a correctly typed field with a different name renders nothing, silently.\r\n instead: name the field logo, cart or searchInput, or put it in another container -->\r\n <ng-container *ngFor=\"let header of headerItems\"\r\n [ngSwitch]=\"header.scope?.reference ? header.scope?.reference.split('/').pop() : 'default'\">\r\n\r\n <!-- Logo -->\r\n <ng-container *ngSwitchCase=\"'logo'\">\r\n <div *ngIf=\"isVisible(header)\" [ngStyle]=\"getStyle(header?.option?.style)\">\r\n\r\n <img [src]=\"header.option.src\" [alt]=\"header.alt || 'Logo'\"\r\n [ngStyle]=\"getStyle(header?.scope?.style)\" />\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Search -->\r\n <ng-container *ngSwitchCase=\"'searchInput'\">\r\n <div *ngIf=\"isVisible(header)\" [ngStyle]=\"getStyle(header?.option?.style)\">\r\n\r\n <input #search type=\"text\" [placeholder]=\"header.option.placeholder ?? ''\"\r\n (keyup.enter)=\"invokeSearchedItems(header.option, search)\"\r\n [ngStyle]=\"getStyle(header?.scope?.style)\" />\r\n\r\n <button *ngIf=\"header.option.button !== false\" type=\"button\"\r\n (click)=\"invokeSearchedItems(header.option, search)\" [ngStyle]=\"getStyle(header?.buttonStyle)\">\r\n <ng-container *ngIf=\"!header.caption; else searchTextContent\">\r\n <i nz-icon [nzType]=\"header.icon\" nzTheme=\"outline\"></i>\r\n </ng-container>\r\n\r\n <ng-template #searchTextContent>\r\n {{ header.caption }}\r\n </ng-template>\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Cart -->\r\n <ng-container *ngSwitchCase=\"'cart'\">\r\n <div *ngIf=\"isVisible(header)\" [ngStyle]=\"getStyle(header?.option?.style)\"\r\n (click)=\"invokeShowtableContent(header.option)\">\r\n\r\n <span [ngStyle]=\"getStyle(header?.priceStyle)\">\r\n {{ cartInfo.totalPrice | currency : header.option?.currency ? header.option.currency : 'USD' }}\r\n </span>\r\n\r\n <span [ngStyle]=\"getStyle(header?.quantityStyle)\">\r\n {{ cartInfo.quantity }}\r\n </span>\r\n\r\n <i [class]=\"header.icon\" [ngStyle]=\"getStyle(header?.iconStyle)\">\r\n </i>\r\n </div>\r\n </ng-container>\r\n\r\n <!-- Default Button -->\r\n <ng-container *ngSwitchCase=\"'default'\">\r\n @if (header.option && header.option?.action && (header.option?.action == \"cart\")) {\r\n <div *ngIf=\"isVisible(header)\" [ngStyle]=\"getStyle(header?.option?.style)\"\r\n (click)=\"invokeShowtableContent(header.option)\">\r\n\r\n <span [ngStyle]=\"getStyle(header?.priceStyle)\">\r\n {{ cartInfo.totalPrice | currency : header.option?.currency ? header.option.currency : 'USD' }}\r\n </span>\r\n\r\n <span [ngStyle]=\"getStyle(header?.quantityStyle)\">\r\n {{ cartInfo.quantity }}\r\n </span>\r\n\r\n <i [class]=\"header.icon\" [ngStyle]=\"getStyle(header?.iconStyle)\">\r\n </i>\r\n </div>\r\n }@else {\r\n <div *ngIf=\"shouldShowDefaultButton(header)\" [ngStyle]=\"getStyle(header?.option?.style)\">\r\n\r\n <button type=\"button\" (click)=\"invokeHeaderButtonClick(header.option)\"\r\n [ngStyle]=\"getStyle(header?.buttonStyle)\">\r\n <ng-container *ngIf=\"!header.label; else buttonTextContent\">\r\n <i nz-icon [nzType]=\"header.icon\" nzTheme=\"outline\"></i>\r\n </ng-container>\r\n\r\n <ng-template #buttonTextContent>\r\n {{ header.label }}\r\n </ng-template>\r\n </button>\r\n </div>\r\n }\r\n </ng-container>\r\n\r\n </ng-container>\r\n</header>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1$1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i11.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "pipe", type: i1$1.CurrencyPipe, name: "currency" }] });
|
|
5473
5705
|
}
|
|
5474
5706
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: HeaderMenuComponent, decorators: [{
|
|
5475
5707
|
type: Component,
|
|
@@ -5539,7 +5771,7 @@ class LogsComponent {
|
|
|
5539
5771
|
this.expandedRow = this.expandedRow === index ? null : index;
|
|
5540
5772
|
}
|
|
5541
5773
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: LogsComponent, deps: [{ token: NetworkLogService }], target: i0.ɵɵFactoryTarget.Component });
|
|
5542
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: LogsComponent, isStandalone: true, selector: "vl-logs", ngImport: i0, template: "<div class=\"network-log-wrapper\">\r\n\r\n <div class=\"network-log-table\">\r\n <table>\r\n <thead>\r\n <tr>\r\n <th>#</th>\r\n <th>Time</th>\r\n <th>Method</th>\r\n <th>URL</th>\r\n <th>Status</th>\r\n <th>Duration (ms)</th>\r\n <th>Size (KB)</th>\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n <ng-container *ngFor=\"let log of logs; let i = index\">\r\n\r\n <!-- MAIN ROW -->\r\n <tr class=\"log-row\" (click)=\"toggleRow(i)\">\r\n <td>{{ i + 1 }}</td>\r\n <td>{{ log.time }}</td>\r\n\r\n <td>\r\n <span class=\"method\" [class.get]=\"log.method === 'GET'\" [class.post]=\"log.method === 'POST'\"\r\n [class.put]=\"log.method === 'PUT'\" [class.delete]=\"log.method === 'DELETE'\">\r\n {{ log.method }}\r\n </span>\r\n </td>\r\n\r\n <td class=\"ellipsis\" [title]=\"log.url\">\r\n {{ log.url }}\r\n </td>\r\n\r\n <td [class.error]=\"log.status >= 400\">\r\n {{ log.status }}\r\n </td>\r\n\r\n <td>{{ log.duration }}</td>\r\n\r\n <td>{{ log.size | number:'1.0-2' }}</td>\r\n </tr>\r\n\r\n <!-- DETAILS ROW -->\r\n <tr *ngIf=\"expandedRow === i\" class=\"details-row\">\r\n <td colspan=\"7\">\r\n <div class=\"details-container\">\r\n\r\n <!-- META -->\r\n <div class=\"detail-block\">\r\n <h4>Meta</h4>\r\n <p><strong>Date:</strong> {{ log.date }}</p>\r\n <p><strong>Type:</strong> {{ log.type }}</p>\r\n </div>\r\n\r\n <!-- REQUEST -->\r\n <div class=\"detail-block\">\r\n <h4>Request</h4>\r\n <p><strong>Headers</strong></p>\r\n <pre>{{ log.requestHeaders | json }}</pre>\r\n\r\n <p><strong>Body</strong></p>\r\n <pre>{{ log.requestBody | json }}</pre>\r\n </div>\r\n\r\n <!-- RESPONSE -->\r\n <div class=\"detail-block\">\r\n <h4>Response</h4>\r\n <pre>{{ log.responseBody | json }}</pre>\r\n </div>\r\n\r\n <!-- PERFORMANCE -->\r\n <div class=\"detail-block\">\r\n <h4>Performance</h4>\r\n <p><strong>Duration:</strong> {{ log.duration }} ms</p>\r\n <p><strong>Size:</strong> {{ log.size | number:'1.0-2' }} KB</p>\r\n </div>\r\n\r\n </div>\r\n </td>\r\n </tr>\r\n\r\n </ng-container>\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n</div>", styles: [".network-log-wrapper{padding:12px}.network-log-table{width:100%;overflow-x:auto}table{width:100%;border-collapse:collapse;font-size:13px;min-width:900px}thead th{background:#06b6d4;color:#fff;padding:8px;text-align:left;position:sticky;top:0;z-index:2}tbody td{padding:8px;border-bottom:1px solid #e5e7eb}.log-row{cursor:pointer}.log-row:hover{background:#f1f5f9}.details-row{background:#f8fafc}.method{padding:2px 8px;border-radius:4px;font-weight:600;font-size:11px;color:#fff}.method.get{background:#16a34a}.method.post{background:#2563eb}.method.put{background:#d97706}.method.delete{background:#dc2626}td.error{color:#dc2626;font-weight:700}.ellipsis{max-width:320px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.details-container{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}.detail-block{background:#fff;border:1px solid #e5e7eb;border-radius:6px;padding:10px}.detail-block h4{margin:0 0 6px;font-size:14px;color:#0f172a}pre{background:#020617;color:#e5e7eb;padding:8px;border-radius:6px;max-height:260px;overflow:auto;font-size:12px;white-space:pre-wrap;word-break:break-word}@media(max-width:768px){table{min-width:100%;font-size:12px}.ellipsis{max-width:140px}.details-container{grid-template-columns:1fr}pre{max-height:200px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type:
|
|
5774
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: LogsComponent, isStandalone: true, selector: "vl-logs", ngImport: i0, template: "<div class=\"network-log-wrapper\">\r\n\r\n <div class=\"network-log-table\">\r\n <table>\r\n <thead>\r\n <tr>\r\n <th>#</th>\r\n <th>Time</th>\r\n <th>Method</th>\r\n <th>URL</th>\r\n <th>Status</th>\r\n <th>Duration (ms)</th>\r\n <th>Size (KB)</th>\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n <ng-container *ngFor=\"let log of logs; let i = index\">\r\n\r\n <!-- MAIN ROW -->\r\n <tr class=\"log-row\" (click)=\"toggleRow(i)\">\r\n <td>{{ i + 1 }}</td>\r\n <td>{{ log.time }}</td>\r\n\r\n <td>\r\n <span class=\"method\" [class.get]=\"log.method === 'GET'\" [class.post]=\"log.method === 'POST'\"\r\n [class.put]=\"log.method === 'PUT'\" [class.delete]=\"log.method === 'DELETE'\">\r\n {{ log.method }}\r\n </span>\r\n </td>\r\n\r\n <td class=\"ellipsis\" [title]=\"log.url\">\r\n {{ log.url }}\r\n </td>\r\n\r\n <td [class.error]=\"log.status >= 400\">\r\n {{ log.status }}\r\n </td>\r\n\r\n <td>{{ log.duration }}</td>\r\n\r\n <td>{{ log.size | number:'1.0-2' }}</td>\r\n </tr>\r\n\r\n <!-- DETAILS ROW -->\r\n <tr *ngIf=\"expandedRow === i\" class=\"details-row\">\r\n <td colspan=\"7\">\r\n <div class=\"details-container\">\r\n\r\n <!-- META -->\r\n <div class=\"detail-block\">\r\n <h4>Meta</h4>\r\n <p><strong>Date:</strong> {{ log.date }}</p>\r\n <p><strong>Type:</strong> {{ log.type }}</p>\r\n </div>\r\n\r\n <!-- REQUEST -->\r\n <div class=\"detail-block\">\r\n <h4>Request</h4>\r\n <p><strong>Headers</strong></p>\r\n <pre>{{ log.requestHeaders | json }}</pre>\r\n\r\n <p><strong>Body</strong></p>\r\n <pre>{{ log.requestBody | json }}</pre>\r\n </div>\r\n\r\n <!-- RESPONSE -->\r\n <div class=\"detail-block\">\r\n <h4>Response</h4>\r\n <pre>{{ log.responseBody | json }}</pre>\r\n </div>\r\n\r\n <!-- PERFORMANCE -->\r\n <div class=\"detail-block\">\r\n <h4>Performance</h4>\r\n <p><strong>Duration:</strong> {{ log.duration }} ms</p>\r\n <p><strong>Size:</strong> {{ log.size | number:'1.0-2' }} KB</p>\r\n </div>\r\n\r\n </div>\r\n </td>\r\n </tr>\r\n\r\n </ng-container>\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n</div>", styles: [".network-log-wrapper{padding:12px}.network-log-table{width:100%;overflow-x:auto}table{width:100%;border-collapse:collapse;font-size:13px;min-width:900px}thead th{background:#06b6d4;color:#fff;padding:8px;text-align:left;position:sticky;top:0;z-index:2}tbody td{padding:8px;border-bottom:1px solid #e5e7eb}.log-row{cursor:pointer}.log-row:hover{background:#f1f5f9}.details-row{background:#f8fafc}.method{padding:2px 8px;border-radius:4px;font-weight:600;font-size:11px;color:#fff}.method.get{background:#16a34a}.method.post{background:#2563eb}.method.put{background:#d97706}.method.delete{background:#dc2626}td.error{color:#dc2626;font-weight:700}.ellipsis{max-width:320px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.details-container{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}.detail-block{background:#fff;border:1px solid #e5e7eb;border-radius:6px;padding:10px}.detail-block h4{margin:0 0 6px;font-size:14px;color:#0f172a}pre{background:#020617;color:#e5e7eb;padding:8px;border-radius:6px;max-height:260px;overflow:auto;font-size:12px;white-space:pre-wrap;word-break:break-word}@media(max-width:768px){table{min-width:100%;font-size:12px}.ellipsis{max-width:140px}.details-container{grid-template-columns:1fr}pre{max-height:200px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.DecimalPipe, name: "number" }] });
|
|
5543
5775
|
}
|
|
5544
5776
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: LogsComponent, decorators: [{
|
|
5545
5777
|
type: Component,
|
|
@@ -5548,6 +5780,760 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
5548
5780
|
], selector: 'vl-logs', template: "<div class=\"network-log-wrapper\">\r\n\r\n <div class=\"network-log-table\">\r\n <table>\r\n <thead>\r\n <tr>\r\n <th>#</th>\r\n <th>Time</th>\r\n <th>Method</th>\r\n <th>URL</th>\r\n <th>Status</th>\r\n <th>Duration (ms)</th>\r\n <th>Size (KB)</th>\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n <ng-container *ngFor=\"let log of logs; let i = index\">\r\n\r\n <!-- MAIN ROW -->\r\n <tr class=\"log-row\" (click)=\"toggleRow(i)\">\r\n <td>{{ i + 1 }}</td>\r\n <td>{{ log.time }}</td>\r\n\r\n <td>\r\n <span class=\"method\" [class.get]=\"log.method === 'GET'\" [class.post]=\"log.method === 'POST'\"\r\n [class.put]=\"log.method === 'PUT'\" [class.delete]=\"log.method === 'DELETE'\">\r\n {{ log.method }}\r\n </span>\r\n </td>\r\n\r\n <td class=\"ellipsis\" [title]=\"log.url\">\r\n {{ log.url }}\r\n </td>\r\n\r\n <td [class.error]=\"log.status >= 400\">\r\n {{ log.status }}\r\n </td>\r\n\r\n <td>{{ log.duration }}</td>\r\n\r\n <td>{{ log.size | number:'1.0-2' }}</td>\r\n </tr>\r\n\r\n <!-- DETAILS ROW -->\r\n <tr *ngIf=\"expandedRow === i\" class=\"details-row\">\r\n <td colspan=\"7\">\r\n <div class=\"details-container\">\r\n\r\n <!-- META -->\r\n <div class=\"detail-block\">\r\n <h4>Meta</h4>\r\n <p><strong>Date:</strong> {{ log.date }}</p>\r\n <p><strong>Type:</strong> {{ log.type }}</p>\r\n </div>\r\n\r\n <!-- REQUEST -->\r\n <div class=\"detail-block\">\r\n <h4>Request</h4>\r\n <p><strong>Headers</strong></p>\r\n <pre>{{ log.requestHeaders | json }}</pre>\r\n\r\n <p><strong>Body</strong></p>\r\n <pre>{{ log.requestBody | json }}</pre>\r\n </div>\r\n\r\n <!-- RESPONSE -->\r\n <div class=\"detail-block\">\r\n <h4>Response</h4>\r\n <pre>{{ log.responseBody | json }}</pre>\r\n </div>\r\n\r\n <!-- PERFORMANCE -->\r\n <div class=\"detail-block\">\r\n <h4>Performance</h4>\r\n <p><strong>Duration:</strong> {{ log.duration }} ms</p>\r\n <p><strong>Size:</strong> {{ log.size | number:'1.0-2' }} KB</p>\r\n </div>\r\n\r\n </div>\r\n </td>\r\n </tr>\r\n\r\n </ng-container>\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n</div>", styles: [".network-log-wrapper{padding:12px}.network-log-table{width:100%;overflow-x:auto}table{width:100%;border-collapse:collapse;font-size:13px;min-width:900px}thead th{background:#06b6d4;color:#fff;padding:8px;text-align:left;position:sticky;top:0;z-index:2}tbody td{padding:8px;border-bottom:1px solid #e5e7eb}.log-row{cursor:pointer}.log-row:hover{background:#f1f5f9}.details-row{background:#f8fafc}.method{padding:2px 8px;border-radius:4px;font-weight:600;font-size:11px;color:#fff}.method.get{background:#16a34a}.method.post{background:#2563eb}.method.put{background:#d97706}.method.delete{background:#dc2626}td.error{color:#dc2626;font-weight:700}.ellipsis{max-width:320px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.details-container{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}.detail-block{background:#fff;border:1px solid #e5e7eb;border-radius:6px;padding:10px}.detail-block h4{margin:0 0 6px;font-size:14px;color:#0f172a}pre{background:#020617;color:#e5e7eb;padding:8px;border-radius:6px;max-height:260px;overflow:auto;font-size:12px;white-space:pre-wrap;word-break:break-word}@media(max-width:768px){table{min-width:100%;font-size:12px}.ellipsis{max-width:140px}.details-container{grid-template-columns:1fr}pre{max-height:200px}}\n"] }]
|
|
5549
5781
|
}], ctorParameters: () => [{ type: NetworkLogService }] });
|
|
5550
5782
|
|
|
5783
|
+
const CONTROL_MAP = {
|
|
5784
|
+
string: ['textInput', 'textArea', 'password', 'combobox', 'autocomplete', 'checkbox', 'switch', 'radiobutton', 'dropdown', 'email'],
|
|
5785
|
+
number: ['textInput', 'spinner', 'slider'], integer: ['textInput', 'spinner', 'slider'],
|
|
5786
|
+
bigdecimal: ['textInput', 'spinner'], boolean: ['checkbox', 'switch', 'radiobutton'],
|
|
5787
|
+
date: ['date'], time: ['time'], datetime: ['datetime'], media: ['media']
|
|
5788
|
+
};
|
|
5789
|
+
function basicType(property) {
|
|
5790
|
+
const value = `${property?.type || 'string'}`.toLowerCase();
|
|
5791
|
+
return { image: 'media', checkbox: 'boolean', datetime: 'datetime', 'date-time': 'datetime', 'date&time': 'datetime', decimal: 'bigdecimal' }[value] || value;
|
|
5792
|
+
}
|
|
5793
|
+
function controlFor(property, option) {
|
|
5794
|
+
const type = basicType(property);
|
|
5795
|
+
if (['date', 'time', 'datetime', 'media'].includes(type))
|
|
5796
|
+
return type;
|
|
5797
|
+
const explicit = option?.control;
|
|
5798
|
+
const control = { textfield: 'textInput', textarea: 'textArea', radioButton: 'radiobutton' }[explicit] || explicit;
|
|
5799
|
+
return control || (type === 'boolean' ? 'checkbox' : 'textInput');
|
|
5800
|
+
}
|
|
5801
|
+
function supported(property, option) {
|
|
5802
|
+
const choices = CONTROL_MAP[basicType(property)];
|
|
5803
|
+
return !!choices?.includes(controlFor(property, option));
|
|
5804
|
+
}
|
|
5805
|
+
function rulesFor(property, option) {
|
|
5806
|
+
const result = { ...(option?.fieldData?.validation || {}), ...(option?.validation || {}), ...(property?.validation || {}) };
|
|
5807
|
+
for (const key of ['minLength', 'maxLength', 'pattern', 'minimum', 'maximum', 'multipleOf', 'enum']) {
|
|
5808
|
+
if (property?.[key] !== undefined)
|
|
5809
|
+
result[key] = property[key];
|
|
5810
|
+
}
|
|
5811
|
+
if (result.minLength === undefined)
|
|
5812
|
+
result.minLength = property?.minlength;
|
|
5813
|
+
if (result.maxLength === undefined)
|
|
5814
|
+
result.maxLength = property?.maxlength;
|
|
5815
|
+
if (result.minimum === undefined)
|
|
5816
|
+
result.minimum = property?.min;
|
|
5817
|
+
if (result.maximum === undefined)
|
|
5818
|
+
result.maximum = property?.max;
|
|
5819
|
+
return result;
|
|
5820
|
+
}
|
|
5821
|
+
function keyboardFor(property, option) {
|
|
5822
|
+
if (['native', 'none'].includes(option?.keyboardType))
|
|
5823
|
+
return null;
|
|
5824
|
+
if (['email', 'numeric', 'alpha', 'string', 'date', 'time'].includes(option?.keyboardType))
|
|
5825
|
+
return option.keyboardType;
|
|
5826
|
+
const type = basicType(property), control = controlFor(property, option);
|
|
5827
|
+
if (control === 'email' || option?.email || property?.format === 'email')
|
|
5828
|
+
return 'email';
|
|
5829
|
+
if (['number', 'integer', 'bigdecimal'].includes(type))
|
|
5830
|
+
return 'numeric';
|
|
5831
|
+
if (type === 'time')
|
|
5832
|
+
return 'time';
|
|
5833
|
+
if (['date', 'datetime'].includes(type))
|
|
5834
|
+
return 'date';
|
|
5835
|
+
return type === 'string' ? 'string' : null;
|
|
5836
|
+
}
|
|
5837
|
+
function classes(style) {
|
|
5838
|
+
return Array.isArray(style?.classes) ? style.classes.filter(Boolean).join(' ') : style?.classes || '';
|
|
5839
|
+
}
|
|
5840
|
+
function styles(style) {
|
|
5841
|
+
const { classes: ignored, ...rest } = style || {};
|
|
5842
|
+
return rest;
|
|
5843
|
+
}
|
|
5844
|
+
function flagValues(property, option) {
|
|
5845
|
+
if (basicType(property) === 'boolean') {
|
|
5846
|
+
const on = option?.checkedBooleanValue !== false;
|
|
5847
|
+
return { on, off: !on };
|
|
5848
|
+
}
|
|
5849
|
+
const values = rulesFor(property, option).enum;
|
|
5850
|
+
return { on: option?.checkedValue ?? values?.[0] ?? '1', off: option?.uncheckedValue ?? values?.[1] ?? '0' };
|
|
5851
|
+
}
|
|
5852
|
+
function choicesFor(property, option, data) {
|
|
5853
|
+
const rules = rulesFor(property, option);
|
|
5854
|
+
const source = Array.isArray(data) ? data : option?.values ?? rules.enum ?? property?.data ?? [];
|
|
5855
|
+
if (!Array.isArray(source))
|
|
5856
|
+
return [];
|
|
5857
|
+
const valueKey = option?.optionValue || option?.optionvalue || option?.autocomplete?.response?.value;
|
|
5858
|
+
const labelKey = option?.optionLabel || option?.optionlabel || option?.autocomplete?.response?.label;
|
|
5859
|
+
return source.map((entry, index) => {
|
|
5860
|
+
const object = entry !== null && typeof entry === 'object';
|
|
5861
|
+
const value = object && valueKey ? entry[valueKey] : entry;
|
|
5862
|
+
const override = Array.isArray(option?.labels) ? option.labels[index] : option?.labels?.[value];
|
|
5863
|
+
const label = override ?? (object && labelKey ? entry[labelKey] : object ? entry.label ?? entry.caption ?? entry.value ?? '' : entry);
|
|
5864
|
+
return { value, label: `${label ?? ''}`, original: entry };
|
|
5865
|
+
});
|
|
5866
|
+
}
|
|
5867
|
+
/** Decimal arithmetic uses integer coefficients; no conversion through Number. */
|
|
5868
|
+
function decimalParts(value) {
|
|
5869
|
+
if (typeof value !== 'string' && typeof value !== 'number')
|
|
5870
|
+
return null;
|
|
5871
|
+
const match = /^([+-]?)(\d*)(?:\.(\d*))?(?:e([+-]?\d+))?$/i.exec(`${value}`.trim());
|
|
5872
|
+
if (!match || !(match[2] || match[3]))
|
|
5873
|
+
return null;
|
|
5874
|
+
const exponent = Number(match[4] || 0);
|
|
5875
|
+
if (!Number.isInteger(exponent) || Math.abs(exponent) > 10000)
|
|
5876
|
+
return null;
|
|
5877
|
+
let scale = (match[3]?.length || 0) - exponent;
|
|
5878
|
+
let coefficient = BigInt((match[1] === '-' ? '-' : '') + ((match[2] || '') + (match[3] || '') || '0'));
|
|
5879
|
+
if (scale < 0) {
|
|
5880
|
+
coefficient *= 10n ** BigInt(-scale);
|
|
5881
|
+
scale = 0;
|
|
5882
|
+
}
|
|
5883
|
+
return { coefficient, scale };
|
|
5884
|
+
}
|
|
5885
|
+
function decimalString(coefficient, scale) {
|
|
5886
|
+
const negative = coefficient < 0n, digits = (negative ? -coefficient : coefficient).toString().padStart(scale + 1, '0');
|
|
5887
|
+
const number = scale ? digits.slice(0, -scale) + '.' + digits.slice(-scale) : digits;
|
|
5888
|
+
return (negative ? '-' : '') + number;
|
|
5889
|
+
}
|
|
5890
|
+
function decimalCompare(a, b) {
|
|
5891
|
+
const left = decimalParts(a), right = decimalParts(b);
|
|
5892
|
+
if (!left || !right)
|
|
5893
|
+
return null;
|
|
5894
|
+
const scale = Math.max(left.scale, right.scale);
|
|
5895
|
+
const diff = left.coefficient * 10n ** BigInt(scale - left.scale) - right.coefficient * 10n ** BigInt(scale - right.scale);
|
|
5896
|
+
return diff < 0n ? -1 : diff > 0n ? 1 : 0;
|
|
5897
|
+
}
|
|
5898
|
+
function decimalStep(value, step, direction) {
|
|
5899
|
+
const a = decimalParts(value === '' || value == null ? '0' : value), b = decimalParts(step ?? '1');
|
|
5900
|
+
if (!a || !b || b.coefficient <= 0n)
|
|
5901
|
+
return null;
|
|
5902
|
+
const scale = Math.max(a.scale, b.scale);
|
|
5903
|
+
return decimalString(a.coefficient * 10n ** BigInt(scale - a.scale) + BigInt(direction) * b.coefficient * 10n ** BigInt(scale - b.scale), scale);
|
|
5904
|
+
}
|
|
5905
|
+
function validateValue(value, property, option) {
|
|
5906
|
+
if (!supported(property, option))
|
|
5907
|
+
return { unsupportedControl: true };
|
|
5908
|
+
const type = basicType(property), control = controlFor(property, option), rules = rulesFor(property, option);
|
|
5909
|
+
const empty = value === null || value === undefined || value === '';
|
|
5910
|
+
const required = option?.required ?? property?.required ?? false;
|
|
5911
|
+
if (required && (empty || (['checkbox', 'switch'].includes(control) && value !== flagValues(property, option).on)))
|
|
5912
|
+
return { required: true };
|
|
5913
|
+
if (empty)
|
|
5914
|
+
return null;
|
|
5915
|
+
const errors = {};
|
|
5916
|
+
if (type === 'boolean' && typeof value !== 'boolean')
|
|
5917
|
+
errors.boolean = true;
|
|
5918
|
+
if (['number', 'integer', 'bigdecimal'].includes(type)) {
|
|
5919
|
+
const parts = decimalParts(value);
|
|
5920
|
+
if (!parts || (type !== 'bigdecimal' && !Number.isFinite(Number(value))))
|
|
5921
|
+
errors.number = true;
|
|
5922
|
+
else {
|
|
5923
|
+
if (type === 'integer' && (parts.coefficient % 10n ** BigInt(parts.scale) !== 0n || !Number.isSafeInteger(Number(value))))
|
|
5924
|
+
errors.integer = true;
|
|
5925
|
+
if (rules.minimum !== undefined && decimalCompare(value, rules.minimum) === -1)
|
|
5926
|
+
errors.minimum = { min: rules.minimum };
|
|
5927
|
+
if (rules.maximum !== undefined && decimalCompare(value, rules.maximum) === 1)
|
|
5928
|
+
errors.maximum = { max: rules.maximum };
|
|
5929
|
+
if (rules.multipleOf !== undefined) {
|
|
5930
|
+
const multiple = decimalParts(rules.multipleOf);
|
|
5931
|
+
if (multiple && multiple.coefficient > 0n) {
|
|
5932
|
+
const scale = Math.max(parts.scale, multiple.scale);
|
|
5933
|
+
if ((parts.coefficient * 10n ** BigInt(scale - parts.scale)) % (multiple.coefficient * 10n ** BigInt(scale - multiple.scale)) !== 0n)
|
|
5934
|
+
errors.multipleOf = true;
|
|
5935
|
+
}
|
|
5936
|
+
else
|
|
5937
|
+
errors.configuration = 'multipleOf must be positive';
|
|
5938
|
+
}
|
|
5939
|
+
if (option?.decimals !== undefined && parts.scale > Number(option.decimals))
|
|
5940
|
+
errors.decimals = true;
|
|
5941
|
+
}
|
|
5942
|
+
}
|
|
5943
|
+
if (type === 'string' && typeof value === 'string') {
|
|
5944
|
+
if (rules.minLength !== undefined && value.length < rules.minLength)
|
|
5945
|
+
errors.minlength = { requiredLength: rules.minLength, actualLength: value.length };
|
|
5946
|
+
if (rules.maxLength !== undefined && value.length > rules.maxLength)
|
|
5947
|
+
errors.maxlength = { requiredLength: rules.maxLength, actualLength: value.length };
|
|
5948
|
+
if (rules.pattern) {
|
|
5949
|
+
try {
|
|
5950
|
+
if (!new RegExp(`^(?:${rules.pattern})$`).test(value))
|
|
5951
|
+
errors.pattern = true;
|
|
5952
|
+
}
|
|
5953
|
+
catch {
|
|
5954
|
+
errors.configuration = 'Invalid validation pattern';
|
|
5955
|
+
}
|
|
5956
|
+
}
|
|
5957
|
+
if ((control === 'email' || option?.email || property?.format === 'email') && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
|
|
5958
|
+
errors.email = true;
|
|
5959
|
+
}
|
|
5960
|
+
if (rules.enum && !(control === 'combobox' && option?.allowCustom === true) && !rules.enum.some((v) => Object.is(v, value)))
|
|
5961
|
+
errors.enum = true;
|
|
5962
|
+
if (['date', 'time', 'datetime'].includes(type)) {
|
|
5963
|
+
const date = value instanceof Date ? value : new Date(type === 'time' && /^\d\d:\d\d/.test(`${value}`) ? `2000-01-01T${value}` : value);
|
|
5964
|
+
if (Number.isNaN(date.getTime()))
|
|
5965
|
+
errors.date = true;
|
|
5966
|
+
for (const [key, compare] of [['minDate', -1], ['maxDate', 1]]) {
|
|
5967
|
+
if (option?.[key]) {
|
|
5968
|
+
const boundary = new Date(option[key]);
|
|
5969
|
+
if ((compare === -1 && date < boundary) || (compare === 1 && date > boundary))
|
|
5970
|
+
errors[key] = true;
|
|
5971
|
+
}
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
return Object.keys(errors).length ? errors : null;
|
|
5975
|
+
}
|
|
5976
|
+
|
|
5977
|
+
class ControlBase {
|
|
5978
|
+
value;
|
|
5979
|
+
option = {};
|
|
5980
|
+
property = {};
|
|
5981
|
+
control = '';
|
|
5982
|
+
inputId = '';
|
|
5983
|
+
inputStyle = {};
|
|
5984
|
+
disabled = false;
|
|
5985
|
+
readonly = false;
|
|
5986
|
+
keyboard = null;
|
|
5987
|
+
changed = new EventEmitter();
|
|
5988
|
+
blurred = new EventEmitter();
|
|
5989
|
+
itemSelected = new EventEmitter();
|
|
5990
|
+
classes = classes;
|
|
5991
|
+
styles = styles;
|
|
5992
|
+
get placeholder() { return this.option.placeholder ?? this.property.placeholder ?? ''; }
|
|
5993
|
+
get inputMode() { return this.keyboard === 'numeric' ? 'decimal' : this.keyboard === 'email' ? 'email' : 'text'; }
|
|
5994
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: ControlBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
5995
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.2", type: ControlBase, isStandalone: true, inputs: { value: "value", option: "option", property: "property", control: "control", inputId: "inputId", inputStyle: "inputStyle", disabled: "disabled", readonly: "readonly", keyboard: "keyboard" }, outputs: { changed: "changed", blurred: "blurred", itemSelected: "itemSelected" }, ngImport: i0 });
|
|
5996
|
+
}
|
|
5997
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: ControlBase, decorators: [{
|
|
5998
|
+
type: Directive
|
|
5999
|
+
}], propDecorators: { value: [{
|
|
6000
|
+
type: Input
|
|
6001
|
+
}], option: [{
|
|
6002
|
+
type: Input
|
|
6003
|
+
}], property: [{
|
|
6004
|
+
type: Input
|
|
6005
|
+
}], control: [{
|
|
6006
|
+
type: Input
|
|
6007
|
+
}], inputId: [{
|
|
6008
|
+
type: Input
|
|
6009
|
+
}], inputStyle: [{
|
|
6010
|
+
type: Input
|
|
6011
|
+
}], disabled: [{
|
|
6012
|
+
type: Input
|
|
6013
|
+
}], readonly: [{
|
|
6014
|
+
type: Input
|
|
6015
|
+
}], keyboard: [{
|
|
6016
|
+
type: Input
|
|
6017
|
+
}], changed: [{
|
|
6018
|
+
type: Output
|
|
6019
|
+
}], blurred: [{
|
|
6020
|
+
type: Output
|
|
6021
|
+
}], itemSelected: [{
|
|
6022
|
+
type: Output
|
|
6023
|
+
}] } });
|
|
6024
|
+
|
|
6025
|
+
class TextControlComponent extends ControlBase {
|
|
6026
|
+
reveal = false;
|
|
6027
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: TextControlComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
6028
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: TextControlComponent, isStandalone: true, selector: "vl-text-control", usesInheritance: true, ngImport: i0, template: `
|
|
6029
|
+
<textarea *ngIf="control === 'textArea'; else single" [id]="inputId" [ngModel]="value"
|
|
6030
|
+
(ngModelChange)="changed.emit($event)" [ngModelOptions]="{standalone:true}" (blur)="blurred.emit()"
|
|
6031
|
+
[disabled]="disabled" [readOnly]="readonly" [placeholder]="placeholder" [rows]="option.rows ?? 3"
|
|
6032
|
+
[attr.inputmode]="inputMode" [attr.autocomplete]="option.autocompleteHint || 'off'"
|
|
6033
|
+
[vlCustomKeyboard]="keyboard" [ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)"></textarea>
|
|
6034
|
+
<ng-template #single><div class="text-wrap">
|
|
6035
|
+
<input [id]="inputId" [type]="control === 'password' && !reveal ? 'password' : control === 'email' ? 'email' : 'text'"
|
|
6036
|
+
[ngModel]="value" (ngModelChange)="changed.emit($event)" [ngModelOptions]="{standalone:true}" (blur)="blurred.emit()"
|
|
6037
|
+
[disabled]="disabled" [readOnly]="readonly" [placeholder]="placeholder" [attr.inputmode]="inputMode"
|
|
6038
|
+
[attr.autocomplete]="option.autocompleteHint || (control === 'password' ? 'current-password' : 'off')"
|
|
6039
|
+
[vlCustomKeyboard]="keyboard" [ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)" />
|
|
6040
|
+
<button *ngIf="control === 'password' && option.showPasswordToggle !== false" type="button"
|
|
6041
|
+
[disabled]="disabled" (click)="reveal=!reveal" [attr.aria-label]="reveal?'Hide password':'Show password'"
|
|
6042
|
+
[attr.aria-pressed]="reveal">{{reveal?'Hide':'Show'}}</button>
|
|
6043
|
+
</div></ng-template>`, isInline: true, styles: [":host{display:block;min-width:0}input,textarea{box-sizing:border-box;width:100%;padding:8px;border:1px solid #cbd5e1;border-radius:6px;font:inherit}textarea{resize:vertical}.text-wrap{display:flex;align-items:center;gap:6px}.text-wrap input{min-width:0;flex:1}button{font:inherit;cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: CustomKeyboardDirective, selector: "[vlCustomKeyboard]", inputs: ["vlCustomKeyboard"] }] });
|
|
6044
|
+
}
|
|
6045
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: TextControlComponent, decorators: [{
|
|
6046
|
+
type: Component,
|
|
6047
|
+
args: [{ selector: 'vl-text-control', standalone: true, imports: [CommonModule, FormsModule, CustomKeyboardDirective], template: `
|
|
6048
|
+
<textarea *ngIf="control === 'textArea'; else single" [id]="inputId" [ngModel]="value"
|
|
6049
|
+
(ngModelChange)="changed.emit($event)" [ngModelOptions]="{standalone:true}" (blur)="blurred.emit()"
|
|
6050
|
+
[disabled]="disabled" [readOnly]="readonly" [placeholder]="placeholder" [rows]="option.rows ?? 3"
|
|
6051
|
+
[attr.inputmode]="inputMode" [attr.autocomplete]="option.autocompleteHint || 'off'"
|
|
6052
|
+
[vlCustomKeyboard]="keyboard" [ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)"></textarea>
|
|
6053
|
+
<ng-template #single><div class="text-wrap">
|
|
6054
|
+
<input [id]="inputId" [type]="control === 'password' && !reveal ? 'password' : control === 'email' ? 'email' : 'text'"
|
|
6055
|
+
[ngModel]="value" (ngModelChange)="changed.emit($event)" [ngModelOptions]="{standalone:true}" (blur)="blurred.emit()"
|
|
6056
|
+
[disabled]="disabled" [readOnly]="readonly" [placeholder]="placeholder" [attr.inputmode]="inputMode"
|
|
6057
|
+
[attr.autocomplete]="option.autocompleteHint || (control === 'password' ? 'current-password' : 'off')"
|
|
6058
|
+
[vlCustomKeyboard]="keyboard" [ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)" />
|
|
6059
|
+
<button *ngIf="control === 'password' && option.showPasswordToggle !== false" type="button"
|
|
6060
|
+
[disabled]="disabled" (click)="reveal=!reveal" [attr.aria-label]="reveal?'Hide password':'Show password'"
|
|
6061
|
+
[attr.aria-pressed]="reveal">{{reveal?'Hide':'Show'}}</button>
|
|
6062
|
+
</div></ng-template>`, styles: [":host{display:block;min-width:0}input,textarea{box-sizing:border-box;width:100%;padding:8px;border:1px solid #cbd5e1;border-radius:6px;font:inherit}textarea{resize:vertical}.text-wrap{display:flex;align-items:center;gap:6px}.text-wrap input{min-width:0;flex:1}button{font:inherit;cursor:pointer}\n"] }]
|
|
6063
|
+
}] });
|
|
6064
|
+
|
|
6065
|
+
class NumericControlComponent extends ControlBase {
|
|
6066
|
+
focused = false;
|
|
6067
|
+
editingValue = null;
|
|
6068
|
+
focusInput() { this.editingValue = this.value == null ? '' : `${this.value}`; this.focused = true; }
|
|
6069
|
+
get integer() { return basicType(this.property) === 'integer'; }
|
|
6070
|
+
get exact() { return basicType(this.property) === 'bigdecimal'; }
|
|
6071
|
+
get rules() { return rulesFor(this.property, this.option); }
|
|
6072
|
+
get displayValue() {
|
|
6073
|
+
if (this.focused)
|
|
6074
|
+
return this.editingValue ?? this.value ?? '';
|
|
6075
|
+
if (this.control === 'slider' || this.value == null || this.value === '')
|
|
6076
|
+
return this.value ?? '';
|
|
6077
|
+
if (!this.option.numberStyle && this.option.grouping === undefined)
|
|
6078
|
+
return this.value;
|
|
6079
|
+
if (!this.exact && typeof this.value === 'number') {
|
|
6080
|
+
try {
|
|
6081
|
+
return new Intl.NumberFormat(this.option.locale, {
|
|
6082
|
+
useGrouping: this.option.grouping ?? true,
|
|
6083
|
+
maximumFractionDigits: this.option.decimals ?? 20,
|
|
6084
|
+
...(this.option.numberStyle === 'currency' && this.option.currency ? { style: 'currency', currency: this.option.currency } : {})
|
|
6085
|
+
}).format(this.value);
|
|
6086
|
+
}
|
|
6087
|
+
catch {
|
|
6088
|
+
return this.value;
|
|
6089
|
+
}
|
|
6090
|
+
}
|
|
6091
|
+
// BigDecimal is never converted to a JS number for display.
|
|
6092
|
+
const parts = `${this.value}`.split('.');
|
|
6093
|
+
return (this.option.grouping ? parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',') : parts[0]) + (parts.length > 1 ? '.' + parts[1] : '');
|
|
6094
|
+
}
|
|
6095
|
+
edit(raw) {
|
|
6096
|
+
if (this.readonly || this.disabled)
|
|
6097
|
+
return;
|
|
6098
|
+
this.editingValue = raw;
|
|
6099
|
+
if (raw === '') {
|
|
6100
|
+
this.changed.emit(null);
|
|
6101
|
+
return;
|
|
6102
|
+
}
|
|
6103
|
+
this.changed.emit(this.exact || !decimalParts(raw) || (this.integer && !Number.isSafeInteger(Number(raw))) ? raw : Number(raw));
|
|
6104
|
+
}
|
|
6105
|
+
step(direction) {
|
|
6106
|
+
if (this.readonly || this.disabled)
|
|
6107
|
+
return;
|
|
6108
|
+
let next = decimalStep(this.value, this.option.step ?? '1', direction);
|
|
6109
|
+
if (next === null)
|
|
6110
|
+
return;
|
|
6111
|
+
if (this.rules.minimum !== undefined && decimalCompare(next, this.rules.minimum) === -1)
|
|
6112
|
+
next = `${this.rules.minimum}`;
|
|
6113
|
+
if (this.rules.maximum !== undefined && decimalCompare(next, this.rules.maximum) === 1)
|
|
6114
|
+
next = `${this.rules.maximum}`;
|
|
6115
|
+
this.changed.emit(this.exact ? next : Number(next));
|
|
6116
|
+
this.blurred.emit();
|
|
6117
|
+
}
|
|
6118
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: NumericControlComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
6119
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: NumericControlComponent, isStandalone: true, selector: "vl-numeric-control", usesInheritance: true, ngImport: i0, template: `
|
|
6120
|
+
<div class="numeric-wrap">
|
|
6121
|
+
<button *ngIf="control==='spinner'" type="button" aria-label="Decrease value" [disabled]="disabled||readonly" (click)="step(-1)">−</button>
|
|
6122
|
+
<input [id]="inputId" [type]="control==='slider'?'range':'text'" [value]="displayValue" [disabled]="disabled || (readonly && control==='slider')"
|
|
6123
|
+
[readOnly]="readonly" [attr.inputmode]="keyboard==='numeric'?'decimal':inputMode" [placeholder]="placeholder"
|
|
6124
|
+
[attr.min]="rules.minimum ?? (control==='slider'?0:null)" [attr.max]="rules.maximum ?? (control==='slider'?100:null)"
|
|
6125
|
+
[attr.step]="option.step ?? (integer?1:'any')" [vlCustomKeyboard]="control==='slider'?null:keyboard"
|
|
6126
|
+
(input)="edit($any($event.target).value)" (focus)="focusInput()" (blur)="focused=false; editingValue=null; blurred.emit()"
|
|
6127
|
+
[attr.aria-readonly]="readonly" [ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)" />
|
|
6128
|
+
<button *ngIf="control==='spinner'" type="button" aria-label="Increase value" [disabled]="disabled||readonly" (click)="step(1)">+</button>
|
|
6129
|
+
<span *ngIf="control==='slider'&&option.showValue">{{value}}</span>
|
|
6130
|
+
<span *ngIf="option.unitLabel || option.unit">{{option.unitLabel || option.unit}}</span>
|
|
6131
|
+
</div>`, isInline: true, styles: [":host{display:block;min-width:0}.numeric-wrap{display:flex;align-items:center;gap:6px}input{min-width:0;flex:1;box-sizing:border-box;padding:8px;border:1px solid #cbd5e1;border-radius:6px;font:inherit}button{font:inherit;padding:7px 12px;cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: CustomKeyboardDirective, selector: "[vlCustomKeyboard]", inputs: ["vlCustomKeyboard"] }] });
|
|
6132
|
+
}
|
|
6133
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: NumericControlComponent, decorators: [{
|
|
6134
|
+
type: Component,
|
|
6135
|
+
args: [{ selector: 'vl-numeric-control', standalone: true, imports: [CommonModule, CustomKeyboardDirective], template: `
|
|
6136
|
+
<div class="numeric-wrap">
|
|
6137
|
+
<button *ngIf="control==='spinner'" type="button" aria-label="Decrease value" [disabled]="disabled||readonly" (click)="step(-1)">−</button>
|
|
6138
|
+
<input [id]="inputId" [type]="control==='slider'?'range':'text'" [value]="displayValue" [disabled]="disabled || (readonly && control==='slider')"
|
|
6139
|
+
[readOnly]="readonly" [attr.inputmode]="keyboard==='numeric'?'decimal':inputMode" [placeholder]="placeholder"
|
|
6140
|
+
[attr.min]="rules.minimum ?? (control==='slider'?0:null)" [attr.max]="rules.maximum ?? (control==='slider'?100:null)"
|
|
6141
|
+
[attr.step]="option.step ?? (integer?1:'any')" [vlCustomKeyboard]="control==='slider'?null:keyboard"
|
|
6142
|
+
(input)="edit($any($event.target).value)" (focus)="focusInput()" (blur)="focused=false; editingValue=null; blurred.emit()"
|
|
6143
|
+
[attr.aria-readonly]="readonly" [ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)" />
|
|
6144
|
+
<button *ngIf="control==='spinner'" type="button" aria-label="Increase value" [disabled]="disabled||readonly" (click)="step(1)">+</button>
|
|
6145
|
+
<span *ngIf="control==='slider'&&option.showValue">{{value}}</span>
|
|
6146
|
+
<span *ngIf="option.unitLabel || option.unit">{{option.unitLabel || option.unit}}</span>
|
|
6147
|
+
</div>`, styles: [":host{display:block;min-width:0}.numeric-wrap{display:flex;align-items:center;gap:6px}input{min-width:0;flex:1;box-sizing:border-box;padding:8px;border:1px solid #cbd5e1;border-radius:6px;font:inherit}button{font:inherit;padding:7px 12px;cursor:pointer}\n"] }]
|
|
6148
|
+
}] });
|
|
6149
|
+
|
|
6150
|
+
class ToggleControlComponent extends ControlBase {
|
|
6151
|
+
get checked() { return Object.is(this.value, flagValues(this.property, this.option).on); }
|
|
6152
|
+
toggle(checked) { if (!this.disabled && !this.readonly)
|
|
6153
|
+
this.changed.emit(checked ? flagValues(this.property, this.option).on : flagValues(this.property, this.option).off); }
|
|
6154
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: ToggleControlComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
6155
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: ToggleControlComponent, isStandalone: true, selector: "vl-toggle-control", usesInheritance: true, ngImport: i0, template: `
|
|
6156
|
+
<span class="toggle-wrap"><input [id]="inputId" type="checkbox" [class.toggle]="control==='switch'"
|
|
6157
|
+
[attr.role]="control==='switch'?'switch':null" [checked]="checked" [disabled]="disabled||readonly"
|
|
6158
|
+
(change)="toggle($any($event.target).checked)" (blur)="blurred.emit()" [ngStyle]="styles(inputStyle)" />
|
|
6159
|
+
<span *ngIf="control==='switch'">{{checked?option.labelOn:option.labelOff}}</span></span>`, isInline: true, styles: [".toggle-wrap{display:inline-flex;gap:8px;align-items:center}.toggle{appearance:none;position:relative;width:36px;height:22px;border-radius:16px;background:#94a3b8;cursor:pointer}.toggle:before{content:\"\";position:absolute;width:16px;height:16px;top:3px;left:3px;border-radius:50%;background:#fff}.toggle:checked{background:#2563eb}.toggle:checked:before{transform:translate(14px)}input:focus-visible{outline:2px solid #2563eb;outline-offset:3px}input:disabled{opacity:.55;cursor:default}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
|
|
6160
|
+
}
|
|
6161
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: ToggleControlComponent, decorators: [{
|
|
6162
|
+
type: Component,
|
|
6163
|
+
args: [{ selector: 'vl-toggle-control', standalone: true, imports: [CommonModule], template: `
|
|
6164
|
+
<span class="toggle-wrap"><input [id]="inputId" type="checkbox" [class.toggle]="control==='switch'"
|
|
6165
|
+
[attr.role]="control==='switch'?'switch':null" [checked]="checked" [disabled]="disabled||readonly"
|
|
6166
|
+
(change)="toggle($any($event.target).checked)" (blur)="blurred.emit()" [ngStyle]="styles(inputStyle)" />
|
|
6167
|
+
<span *ngIf="control==='switch'">{{checked?option.labelOn:option.labelOff}}</span></span>`, styles: [".toggle-wrap{display:inline-flex;gap:8px;align-items:center}.toggle{appearance:none;position:relative;width:36px;height:22px;border-radius:16px;background:#94a3b8;cursor:pointer}.toggle:before{content:\"\";position:absolute;width:16px;height:16px;top:3px;left:3px;border-radius:50%;background:#fff}.toggle:checked{background:#2563eb}.toggle:checked:before{transform:translate(14px)}input:focus-visible{outline:2px solid #2563eb;outline-offset:3px}input:disabled{opacity:.55;cursor:default}\n"] }]
|
|
6168
|
+
}] });
|
|
6169
|
+
|
|
6170
|
+
class SelectionControlComponent extends ControlBase {
|
|
6171
|
+
items;
|
|
6172
|
+
suggestions = [];
|
|
6173
|
+
equal = Object.is;
|
|
6174
|
+
get choices() {
|
|
6175
|
+
if (basicType(this.property) === 'boolean' && this.items === undefined && !this.option.values)
|
|
6176
|
+
return choicesFor(this.property, { ...this.option, values: [true, false], labels: this.option.labels ?? ['Yes', 'No'] });
|
|
6177
|
+
return choicesFor(this.property, this.option, this.items);
|
|
6178
|
+
}
|
|
6179
|
+
get display() { return this.choices.find(item => Object.is(item.value, this.value)) ?? this.value ?? ''; }
|
|
6180
|
+
ngOnChanges() { this.suggestions = this.choices; }
|
|
6181
|
+
complete(event) { const query = `${event.query ?? ''}`.toLowerCase(); this.suggestions = this.choices.filter(item => item.label.toLowerCase().includes(query)); }
|
|
6182
|
+
typed(value) {
|
|
6183
|
+
if (typeof value === 'string' && (this.control === 'autocomplete' || this.option.allowCustom === true) && !this.disabled && !this.readonly)
|
|
6184
|
+
this.changed.emit(value);
|
|
6185
|
+
if (value === null && !this.disabled && !this.readonly)
|
|
6186
|
+
this.changed.emit(null);
|
|
6187
|
+
}
|
|
6188
|
+
chooseValue(value) { const choice = this.choices.find(item => Object.is(item.value, value)); if (choice)
|
|
6189
|
+
this.select(choice);
|
|
6190
|
+
else
|
|
6191
|
+
this.changed.emit(value); }
|
|
6192
|
+
select(item) { if (this.disabled || this.readonly || !item)
|
|
6193
|
+
return; this.changed.emit(item.value); this.itemSelected.emit(item.original); }
|
|
6194
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: SelectionControlComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
6195
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: SelectionControlComponent, isStandalone: true, selector: "vl-selection-control", inputs: { items: "items" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: `
|
|
6196
|
+
<select *ngIf="control==='dropdown'" [id]="inputId" [ngModel]="value" (ngModelChange)="chooseValue($event)"
|
|
6197
|
+
[ngModelOptions]="{standalone:true}" [disabled]="disabled||readonly" (blur)="blurred.emit()"
|
|
6198
|
+
[ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)">
|
|
6199
|
+
<option [ngValue]="null" [disabled]="option.required">{{placeholder || 'Select a value'}}</option>
|
|
6200
|
+
<option *ngFor="let item of choices" [ngValue]="item.value">{{item.label}}</option>
|
|
6201
|
+
</select>
|
|
6202
|
+
<div *ngIf="control==='radiobutton'" class="radio-list" role="radiogroup" [attr.aria-label]="option.ariaLabel || 'Options'"
|
|
6203
|
+
[style.flex-direction]="option.orientation==='horizontal'?'row':'column'">
|
|
6204
|
+
<label *ngFor="let item of choices;let index=index"><input type="radio" [name]="inputId" [id]="inputId+'-'+index"
|
|
6205
|
+
[checked]="equal(value,item.value)" [disabled]="disabled||readonly" (change)="select(item)" (blur)="blurred.emit()" />{{item.label}}</label>
|
|
6206
|
+
</div>
|
|
6207
|
+
<p-autocomplete *ngIf="control==='combobox'||control==='autocomplete'" [inputId]="inputId" [ngModel]="display"
|
|
6208
|
+
(ngModelChange)="typed($event)" [ngModelOptions]="{standalone:true}" [suggestions]="suggestions" optionLabel="label"
|
|
6209
|
+
(completeMethod)="complete($event)" (onSelect)="select($event.value)" (onBlur)="blurred.emit()"
|
|
6210
|
+
[dropdown]="control==='combobox'" [forceSelection]="control==='combobox'&&option.allowCustom!==true"
|
|
6211
|
+
[minLength]="option.minChars ?? 1" [placeholder]="placeholder" [disabled]="disabled||readonly"
|
|
6212
|
+
[vlCustomKeyboard]="keyboard" appendTo="body" [ngStyle]="styles(inputStyle)"></p-autocomplete>`, isInline: true, styles: [":host{display:block;min-width:0}select{width:100%;box-sizing:border-box;padding:8px;border:1px solid #cbd5e1;border-radius:6px;font:inherit}.radio-list{display:flex;gap:10px}.radio-list label{display:flex;gap:6px;align-items:center}::ng-deep vl-selection-control .p-autocomplete{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i18.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "directive", type: CustomKeyboardDirective, selector: "[vlCustomKeyboard]", inputs: ["vlCustomKeyboard"] }] });
|
|
6213
|
+
}
|
|
6214
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: SelectionControlComponent, decorators: [{
|
|
6215
|
+
type: Component,
|
|
6216
|
+
args: [{ selector: 'vl-selection-control', standalone: true, imports: [CommonModule, FormsModule, AutoCompleteModule, CustomKeyboardDirective], template: `
|
|
6217
|
+
<select *ngIf="control==='dropdown'" [id]="inputId" [ngModel]="value" (ngModelChange)="chooseValue($event)"
|
|
6218
|
+
[ngModelOptions]="{standalone:true}" [disabled]="disabled||readonly" (blur)="blurred.emit()"
|
|
6219
|
+
[ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)">
|
|
6220
|
+
<option [ngValue]="null" [disabled]="option.required">{{placeholder || 'Select a value'}}</option>
|
|
6221
|
+
<option *ngFor="let item of choices" [ngValue]="item.value">{{item.label}}</option>
|
|
6222
|
+
</select>
|
|
6223
|
+
<div *ngIf="control==='radiobutton'" class="radio-list" role="radiogroup" [attr.aria-label]="option.ariaLabel || 'Options'"
|
|
6224
|
+
[style.flex-direction]="option.orientation==='horizontal'?'row':'column'">
|
|
6225
|
+
<label *ngFor="let item of choices;let index=index"><input type="radio" [name]="inputId" [id]="inputId+'-'+index"
|
|
6226
|
+
[checked]="equal(value,item.value)" [disabled]="disabled||readonly" (change)="select(item)" (blur)="blurred.emit()" />{{item.label}}</label>
|
|
6227
|
+
</div>
|
|
6228
|
+
<p-autocomplete *ngIf="control==='combobox'||control==='autocomplete'" [inputId]="inputId" [ngModel]="display"
|
|
6229
|
+
(ngModelChange)="typed($event)" [ngModelOptions]="{standalone:true}" [suggestions]="suggestions" optionLabel="label"
|
|
6230
|
+
(completeMethod)="complete($event)" (onSelect)="select($event.value)" (onBlur)="blurred.emit()"
|
|
6231
|
+
[dropdown]="control==='combobox'" [forceSelection]="control==='combobox'&&option.allowCustom!==true"
|
|
6232
|
+
[minLength]="option.minChars ?? 1" [placeholder]="placeholder" [disabled]="disabled||readonly"
|
|
6233
|
+
[vlCustomKeyboard]="keyboard" appendTo="body" [ngStyle]="styles(inputStyle)"></p-autocomplete>`, styles: [":host{display:block;min-width:0}select{width:100%;box-sizing:border-box;padding:8px;border:1px solid #cbd5e1;border-radius:6px;font:inherit}.radio-list{display:flex;gap:10px}.radio-list label{display:flex;gap:6px;align-items:center}::ng-deep vl-selection-control .p-autocomplete{width:100%}\n"] }]
|
|
6234
|
+
}], propDecorators: { items: [{
|
|
6235
|
+
type: Input
|
|
6236
|
+
}] } });
|
|
6237
|
+
|
|
6238
|
+
/** Calendar values are wall-clock Dates. Date-only values never undergo timezone conversion. */
|
|
6239
|
+
function wallParts(date, zone) {
|
|
6240
|
+
const parts = new Intl.DateTimeFormat('en-CA', { timeZone: zone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h23' }).formatToParts(date);
|
|
6241
|
+
const read = (key) => Number(parts.find(p => p.type === key)?.value);
|
|
6242
|
+
return ['year', 'month', 'day', 'hour', 'minute', 'second'].map(read);
|
|
6243
|
+
}
|
|
6244
|
+
function pickerDate(value, type, zone) {
|
|
6245
|
+
if (value === null || value === undefined || value === '')
|
|
6246
|
+
return null;
|
|
6247
|
+
const local = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2}))?)?$/.exec(`${value}`);
|
|
6248
|
+
if (type === 'date' && local)
|
|
6249
|
+
return new Date(+local[1], +local[2] - 1, +local[3]);
|
|
6250
|
+
const clock = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(`${value}`);
|
|
6251
|
+
if (type === 'time' && clock)
|
|
6252
|
+
return new Date(2000, 0, 1, +clock[1], +clock[2], +(clock[3] || 0));
|
|
6253
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
6254
|
+
if (Number.isNaN(date.getTime()))
|
|
6255
|
+
return null;
|
|
6256
|
+
if (type === 'date' || !zone || zone === 'local')
|
|
6257
|
+
return date;
|
|
6258
|
+
const p = wallParts(date, zone);
|
|
6259
|
+
return new Date(p[0], p[1] - 1, p[2], p[3], p[4], p[5]);
|
|
6260
|
+
}
|
|
6261
|
+
function instantFromWall(value, zone) {
|
|
6262
|
+
const target = Date.UTC(value.getFullYear(), value.getMonth(), value.getDate(), value.getHours(), value.getMinutes(), value.getSeconds());
|
|
6263
|
+
let instant = target;
|
|
6264
|
+
for (let i = 0; i < 4; i++) {
|
|
6265
|
+
const p = wallParts(new Date(instant), zone);
|
|
6266
|
+
const represented = Date.UTC(p[0], p[1] - 1, p[2], p[3], p[4], p[5]);
|
|
6267
|
+
const delta = target - represented;
|
|
6268
|
+
if (!delta)
|
|
6269
|
+
return new Date(instant);
|
|
6270
|
+
instant += delta;
|
|
6271
|
+
}
|
|
6272
|
+
return null; // nonexistent local time during a daylight-saving jump
|
|
6273
|
+
}
|
|
6274
|
+
function storedTemporal(value, previous, type, option) {
|
|
6275
|
+
if (!value)
|
|
6276
|
+
return null;
|
|
6277
|
+
const pad = (n) => `${n}`.padStart(2, '0');
|
|
6278
|
+
const date = `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
|
|
6279
|
+
const time = `${pad(value.getHours())}:${pad(value.getMinutes())}` + (option.showSeconds ? `:${pad(value.getSeconds())}` : '');
|
|
6280
|
+
const format = option.valueFormat ?? (typeof previous === 'string' ? (type === 'date' ? 'date' : type === 'time' && /^\d\d:\d\d/.test(previous) ? 'time' : 'iso') : 'dateObject');
|
|
6281
|
+
if (type === 'date')
|
|
6282
|
+
return format === 'dateObject' ? new Date(value.getFullYear(), value.getMonth(), value.getDate()) : date;
|
|
6283
|
+
if (type === 'time' && format === 'time')
|
|
6284
|
+
return time;
|
|
6285
|
+
if (format === 'local')
|
|
6286
|
+
return `${date}T${time}`;
|
|
6287
|
+
const instant = option.displayTimezone && option.displayTimezone !== 'local' ? instantFromWall(value, option.displayTimezone) : value;
|
|
6288
|
+
if (!instant)
|
|
6289
|
+
throw new Error('This time does not exist in the selected timezone.');
|
|
6290
|
+
return format === 'iso' ? instant.toISOString() : instant;
|
|
6291
|
+
}
|
|
6292
|
+
function primeDateFormat(format) {
|
|
6293
|
+
if (typeof format !== 'string' || !format || ['date', 'date-time', 'iso-date', 'iso-time', 'iso-datetime'].includes(format))
|
|
6294
|
+
return 'yy-mm-dd';
|
|
6295
|
+
// Accept the project's common display tokens; PrimeNG uses yy for a four-digit year.
|
|
6296
|
+
return format.replace(/\s+[Hh].*$/, '').replace(/yyyy/g, 'yy').replace(/MMMM/g, 'MM').replace(/MMM/g, 'M').replace(/MM(?=[/.-])/g, 'mm').replace(/([/.-])MM/g, '$1mm');
|
|
6297
|
+
}
|
|
6298
|
+
|
|
6299
|
+
class TemporalControlComponent extends ControlBase {
|
|
6300
|
+
picker = inject(DateTimePickerService);
|
|
6301
|
+
host = inject((ElementRef));
|
|
6302
|
+
error = '';
|
|
6303
|
+
get dateValue() { try {
|
|
6304
|
+
return pickerDate(this.value, this.control, this.option.displayTimezone);
|
|
6305
|
+
}
|
|
6306
|
+
catch {
|
|
6307
|
+
return null;
|
|
6308
|
+
} }
|
|
6309
|
+
get minDate() { return this.option.minDate ? pickerDate(this.option.minDate, this.control, this.option.displayTimezone) ?? undefined : undefined; }
|
|
6310
|
+
get maxDate() { return this.option.maxDate ? pickerDate(this.option.maxDate, this.control, this.option.displayTimezone) ?? undefined : undefined; }
|
|
6311
|
+
get displayFormat() { return primeDateFormat(this.option.displayFormat ?? this.property.format); }
|
|
6312
|
+
choose(value) {
|
|
6313
|
+
if (this.disabled || this.readonly)
|
|
6314
|
+
return;
|
|
6315
|
+
try {
|
|
6316
|
+
const stored = storedTemporal(value, this.value, this.control, this.option);
|
|
6317
|
+
this.error = '';
|
|
6318
|
+
this.changed.emit(stored);
|
|
6319
|
+
}
|
|
6320
|
+
catch (error) {
|
|
6321
|
+
this.error = error instanceof Error ? error.message : 'Invalid date or timezone';
|
|
6322
|
+
this.changed.emit('invalid');
|
|
6323
|
+
}
|
|
6324
|
+
}
|
|
6325
|
+
ngOnDestroy() { this.picker.closeIfWithin(this.host.nativeElement); }
|
|
6326
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: TemporalControlComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
6327
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: TemporalControlComponent, isStandalone: true, selector: "vl-temporal-control", usesInheritance: true, ngImport: i0, template: `
|
|
6328
|
+
<div class="picker-field">
|
|
6329
|
+
<p-datepicker #calendar [inputId]="inputId" [ngModel]="dateValue" (ngModelChange)="choose($event)"
|
|
6330
|
+
[ngModelOptions]="{standalone:true}" [disabled]="disabled||readonly" [readonlyInput]="option.allowManualInput === false"
|
|
6331
|
+
[timeOnly]="control==='time'" [showTime]="control==='datetime'" [showSeconds]="option.showSeconds ?? false"
|
|
6332
|
+
[hourFormat]="option.hourFormat || '24'" [dateFormat]="displayFormat" [placeholder]="placeholder"
|
|
6333
|
+
[minDate]="minDate" [maxDate]="maxDate" [showClear]="option.showClear ?? true"
|
|
6334
|
+
[stepHour]="option.stepHour ?? 1" [stepMinute]="option.stepMinute ?? 1" [stepSecond]="option.stepSecond ?? 1"
|
|
6335
|
+
[showIcon]="false" [showOnFocus]="false" appendTo="body" [autoZIndex]="true" [baseZIndex]="10000"
|
|
6336
|
+
[panelStyleClass]="control==='time'?picker.timePanelClass:picker.datePanelClass" [motionOptions]="picker.dockedMotion"
|
|
6337
|
+
[vlCustomKeyboard]="option.allowManualInput===false?null:keyboard"
|
|
6338
|
+
(pointerdown)="picker.onFieldPointerDown($event,calendar)" (onFocus)="picker.onFieldFocus($event)"
|
|
6339
|
+
(onShow)="picker.onShown()" (onClose)="picker.onClose(calendar)" (onBlur)="blurred.emit()"
|
|
6340
|
+
[ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)"></p-datepicker>
|
|
6341
|
+
<button type="button" class="picker-field__icon" [disabled]="disabled||readonly" [attr.aria-label]="control==='time'?'Open time picker':'Open calendar'"
|
|
6342
|
+
(pointerdown)="picker.openFromIcon($event,calendar)" (click)="picker.blockIconClick($event)"><i [class]="control==='time'?'pi pi-clock':'pi pi-calendar'"></i></button>
|
|
6343
|
+
</div>
|
|
6344
|
+
<small *ngIf="error" role="alert">{{error}}</small>`, isInline: true, styles: [":host{display:block;min-width:0}.picker-field{position:relative;display:flex;align-items:center}.picker-field__icon{position:absolute;right:6px;background:transparent;border:0;cursor:pointer}small{color:#b91c1c}::ng-deep vl-temporal-control .p-datepicker{width:100%}::ng-deep vl-temporal-control .p-datepicker-input{width:100%;padding-right:36px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i12.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "directive", type: CustomKeyboardDirective, selector: "[vlCustomKeyboard]", inputs: ["vlCustomKeyboard"] }] });
|
|
6345
|
+
}
|
|
6346
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: TemporalControlComponent, decorators: [{
|
|
6347
|
+
type: Component,
|
|
6348
|
+
args: [{ selector: 'vl-temporal-control', standalone: true, imports: [CommonModule, FormsModule, DatePickerModule, CustomKeyboardDirective], template: `
|
|
6349
|
+
<div class="picker-field">
|
|
6350
|
+
<p-datepicker #calendar [inputId]="inputId" [ngModel]="dateValue" (ngModelChange)="choose($event)"
|
|
6351
|
+
[ngModelOptions]="{standalone:true}" [disabled]="disabled||readonly" [readonlyInput]="option.allowManualInput === false"
|
|
6352
|
+
[timeOnly]="control==='time'" [showTime]="control==='datetime'" [showSeconds]="option.showSeconds ?? false"
|
|
6353
|
+
[hourFormat]="option.hourFormat || '24'" [dateFormat]="displayFormat" [placeholder]="placeholder"
|
|
6354
|
+
[minDate]="minDate" [maxDate]="maxDate" [showClear]="option.showClear ?? true"
|
|
6355
|
+
[stepHour]="option.stepHour ?? 1" [stepMinute]="option.stepMinute ?? 1" [stepSecond]="option.stepSecond ?? 1"
|
|
6356
|
+
[showIcon]="false" [showOnFocus]="false" appendTo="body" [autoZIndex]="true" [baseZIndex]="10000"
|
|
6357
|
+
[panelStyleClass]="control==='time'?picker.timePanelClass:picker.datePanelClass" [motionOptions]="picker.dockedMotion"
|
|
6358
|
+
[vlCustomKeyboard]="option.allowManualInput===false?null:keyboard"
|
|
6359
|
+
(pointerdown)="picker.onFieldPointerDown($event,calendar)" (onFocus)="picker.onFieldFocus($event)"
|
|
6360
|
+
(onShow)="picker.onShown()" (onClose)="picker.onClose(calendar)" (onBlur)="blurred.emit()"
|
|
6361
|
+
[ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)"></p-datepicker>
|
|
6362
|
+
<button type="button" class="picker-field__icon" [disabled]="disabled||readonly" [attr.aria-label]="control==='time'?'Open time picker':'Open calendar'"
|
|
6363
|
+
(pointerdown)="picker.openFromIcon($event,calendar)" (click)="picker.blockIconClick($event)"><i [class]="control==='time'?'pi pi-clock':'pi pi-calendar'"></i></button>
|
|
6364
|
+
</div>
|
|
6365
|
+
<small *ngIf="error" role="alert">{{error}}</small>`, styles: [":host{display:block;min-width:0}.picker-field{position:relative;display:flex;align-items:center}.picker-field__icon{position:absolute;right:6px;background:transparent;border:0;cursor:pointer}small{color:#b91c1c}::ng-deep vl-temporal-control .p-datepicker{width:100%}::ng-deep vl-temporal-control .p-datepicker-input{width:100%;padding-right:36px}\n"] }]
|
|
6366
|
+
}] });
|
|
6367
|
+
|
|
6368
|
+
class MediaControlComponent extends ControlBase {
|
|
6369
|
+
failed = false;
|
|
6370
|
+
get source() {
|
|
6371
|
+
const source = this.option.src ?? this.value;
|
|
6372
|
+
if (typeof source === 'string')
|
|
6373
|
+
return source;
|
|
6374
|
+
return source?.src ?? source?.dataUrl ?? (source?.base64 ? `data:${source.mimeType || 'application/octet-stream'};base64,${source.base64}` : '');
|
|
6375
|
+
}
|
|
6376
|
+
get kind() {
|
|
6377
|
+
const mime = this.option.mimeType ?? /^data:([^;,]+)/i.exec(this.source)?.[1] ?? this.value?.mimeType ?? '';
|
|
6378
|
+
if (mime.startsWith('image/'))
|
|
6379
|
+
return 'image';
|
|
6380
|
+
if (mime.startsWith('video/'))
|
|
6381
|
+
return 'video';
|
|
6382
|
+
if (mime.startsWith('audio/'))
|
|
6383
|
+
return 'audio';
|
|
6384
|
+
if (mime === 'application/pdf')
|
|
6385
|
+
return 'pdf';
|
|
6386
|
+
const path = (this.option.fileName || this.source).split(/[?#]/)[0];
|
|
6387
|
+
const ext = /\.([a-z0-9]+)$/i.exec(path)?.[1]?.toLowerCase();
|
|
6388
|
+
if (['mp4', 'webm', 'mov', 'm4v', 'ogv', 'mkv', 'avi'].includes(ext || ''))
|
|
6389
|
+
return 'video';
|
|
6390
|
+
if (['mp3', 'wav', 'ogg', 'oga', 'm4a', 'aac', 'flac', 'opus'].includes(ext || ''))
|
|
6391
|
+
return 'audio';
|
|
6392
|
+
if (ext === 'pdf')
|
|
6393
|
+
return 'pdf';
|
|
6394
|
+
if (['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'zip', 'rar', '7z', 'txt', 'csv'].includes(ext || ''))
|
|
6395
|
+
return 'file';
|
|
6396
|
+
return 'image';
|
|
6397
|
+
}
|
|
6398
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MediaControlComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
6399
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: MediaControlComponent, isStandalone: true, selector: "vl-media-control", usesInheritance: true, ngImport: i0, template: `
|
|
6400
|
+
<ng-container *ngIf="source" [ngSwitch]="kind">
|
|
6401
|
+
<img *ngSwitchDefault [src]="source" [alt]="option.alt || property.caption || ''" [ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)" loading="lazy" (error)="failed=true" />
|
|
6402
|
+
<video *ngSwitchCase="'video'" [src]="source" [poster]="option.poster || ''" [controls]="option.controls !== false" [muted]="option.muted ?? false"
|
|
6403
|
+
[loop]="option.loop ?? false" [autoplay]="option.autoplay ?? false" [preload]="option.preload || 'metadata'" playsinline (error)="failed=true"
|
|
6404
|
+
[ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)"></video>
|
|
6405
|
+
<audio *ngSwitchCase="'audio'" [src]="source" [controls]="option.controls !== false" [muted]="option.muted ?? false"
|
|
6406
|
+
[loop]="option.loop ?? false" [autoplay]="option.autoplay ?? false" [preload]="option.preload || 'metadata'" (error)="failed=true"
|
|
6407
|
+
[ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)"></audio>
|
|
6408
|
+
<a *ngSwitchCase="'pdf'" [href]="source" target="_blank" rel="noopener noreferrer">{{option.linkLabel || 'Open PDF'}}</a>
|
|
6409
|
+
<a *ngSwitchCase="'file'" [href]="source" target="_blank" rel="noopener noreferrer">{{option.linkLabel || 'Open file'}}</a>
|
|
6410
|
+
</ng-container>
|
|
6411
|
+
<small *ngIf="failed" role="alert">{{option.errorMessage || 'This media could not be loaded.'}}</small>`, isInline: true, styles: [":host{display:block;min-width:0}img,video,audio{max-width:100%}small{color:#b91c1c}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1$1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1$1.NgSwitchDefault, selector: "[ngSwitchDefault]" }] });
|
|
6412
|
+
}
|
|
6413
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MediaControlComponent, decorators: [{
|
|
6414
|
+
type: Component,
|
|
6415
|
+
args: [{ selector: 'vl-media-control', standalone: true, imports: [CommonModule], template: `
|
|
6416
|
+
<ng-container *ngIf="source" [ngSwitch]="kind">
|
|
6417
|
+
<img *ngSwitchDefault [src]="source" [alt]="option.alt || property.caption || ''" [ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)" loading="lazy" (error)="failed=true" />
|
|
6418
|
+
<video *ngSwitchCase="'video'" [src]="source" [poster]="option.poster || ''" [controls]="option.controls !== false" [muted]="option.muted ?? false"
|
|
6419
|
+
[loop]="option.loop ?? false" [autoplay]="option.autoplay ?? false" [preload]="option.preload || 'metadata'" playsinline (error)="failed=true"
|
|
6420
|
+
[ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)"></video>
|
|
6421
|
+
<audio *ngSwitchCase="'audio'" [src]="source" [controls]="option.controls !== false" [muted]="option.muted ?? false"
|
|
6422
|
+
[loop]="option.loop ?? false" [autoplay]="option.autoplay ?? false" [preload]="option.preload || 'metadata'" (error)="failed=true"
|
|
6423
|
+
[ngStyle]="styles(inputStyle)" [class]="classes(inputStyle)"></audio>
|
|
6424
|
+
<a *ngSwitchCase="'pdf'" [href]="source" target="_blank" rel="noopener noreferrer">{{option.linkLabel || 'Open PDF'}}</a>
|
|
6425
|
+
<a *ngSwitchCase="'file'" [href]="source" target="_blank" rel="noopener noreferrer">{{option.linkLabel || 'Open file'}}</a>
|
|
6426
|
+
</ng-container>
|
|
6427
|
+
<small *ngIf="failed" role="alert">{{option.errorMessage || 'This media could not be loaded.'}}</small>`, styles: [":host{display:block;min-width:0}img,video,audio{max-width:100%}small{color:#b91c1c}\n"] }]
|
|
6428
|
+
}] });
|
|
6429
|
+
|
|
6430
|
+
let nextFieldId = 0;
|
|
6431
|
+
class FieldRendererComponent {
|
|
6432
|
+
field = {};
|
|
6433
|
+
property = {};
|
|
6434
|
+
items;
|
|
6435
|
+
translations = {};
|
|
6436
|
+
errorMessages = {};
|
|
6437
|
+
showErrors = false;
|
|
6438
|
+
itemSelected = new EventEmitter();
|
|
6439
|
+
fieldBlur = new EventEmitter();
|
|
6440
|
+
value = null;
|
|
6441
|
+
touched = false;
|
|
6442
|
+
formDisabled = false;
|
|
6443
|
+
inputId = `vl-shared-field-${++nextFieldId}`;
|
|
6444
|
+
classes = classes;
|
|
6445
|
+
styles = styles;
|
|
6446
|
+
onChange = () => { };
|
|
6447
|
+
onTouched = () => { };
|
|
6448
|
+
validatorChanged = () => { };
|
|
6449
|
+
get option() { return this.field?.option || {}; }
|
|
6450
|
+
get type() { return basicType(this.property); }
|
|
6451
|
+
get control() { return controlFor(this.property, this.option); }
|
|
6452
|
+
get allowed() { return supported(this.property, this.option); }
|
|
6453
|
+
get keyboard() { return keyboardFor(this.property, this.option); }
|
|
6454
|
+
get disabled() { return this.formDisabled || this.option.disabled === true; }
|
|
6455
|
+
get readonly() { return this.option.readonly ?? this.property?.readonly ?? false; }
|
|
6456
|
+
get protectedValue() { return this.option.protected === true; }
|
|
6457
|
+
get label() {
|
|
6458
|
+
const label = this.field?.label;
|
|
6459
|
+
const caption = typeof label === 'string' ? label : label?.value ?? label?.caption ?? this.property?.caption ?? '';
|
|
6460
|
+
return this.translations?.[caption] ?? caption;
|
|
6461
|
+
}
|
|
6462
|
+
get afterLabel() { return ['checkbox', 'switch'].includes(this.control) && (this.option.labelPosition === 'right' || this.option.postfixCaption === true); }
|
|
6463
|
+
get innerStyle() {
|
|
6464
|
+
if (this.field?.layout === 'horizontal')
|
|
6465
|
+
return { display: 'grid', gridTemplateColumns: '80px minmax(0,1fr)', alignItems: 'center', gap: '10px', ...(this.field?.layoutStyle || {}) };
|
|
6466
|
+
return { display: 'grid', gap: '6px', ...(this.field?.layoutStyle || {}) };
|
|
6467
|
+
}
|
|
6468
|
+
get resolvedOption() {
|
|
6469
|
+
const p = this.property;
|
|
6470
|
+
return {
|
|
6471
|
+
...this.option, placeholder: this.translations?.[this.option.placeholder ?? p.placeholder] ?? this.option.placeholder ?? p.placeholder,
|
|
6472
|
+
currency: this.option.currency ?? this.option.fieldData?.currency ?? p.currency,
|
|
6473
|
+
required: this.option.required ?? p.required
|
|
6474
|
+
};
|
|
6475
|
+
}
|
|
6476
|
+
get errors() { return this.disabled ? null : validateValue(this.value, this.property, this.option); }
|
|
6477
|
+
get errorText() {
|
|
6478
|
+
const key = Object.keys(this.errors || {})[0];
|
|
6479
|
+
if (!key)
|
|
6480
|
+
return '';
|
|
6481
|
+
const custom = this.option.messages?.[key];
|
|
6482
|
+
const defaults = { required: 'This field is required.', boolean: 'Choose a boolean value.', email: 'Enter a valid email address.', number: 'Enter a valid number.', integer: 'Enter a whole number.', minimum: 'The value is below the minimum.', maximum: 'The value exceeds the maximum.', minlength: 'The value is too short.', maxlength: 'The value is too long.', pattern: 'The value does not match the required format.', enum: 'Choose an allowed value.', decimals: 'Too many decimal places.', multipleOf: 'Enter a permitted increment.', date: 'Enter a valid date or time.', minDate: 'Choose a later date or time.', maxDate: 'Choose an earlier date or time.', unsupportedControl: 'This control is not supported for this property type.', configuration: 'The field configuration is invalid.' };
|
|
6483
|
+
const text = custom ?? this.errorMessages?.[key] ?? defaults[key] ?? 'Invalid value';
|
|
6484
|
+
const translated = this.translations?.[text] ?? text;
|
|
6485
|
+
const details = this.errors?.[key] || {};
|
|
6486
|
+
return `${translated}`.replace(/{{\s*field\s*}}/g, this.label).replace(/{{\s*requiredLength\s*}}/g, `${details.requiredLength ?? ''}`);
|
|
6487
|
+
}
|
|
6488
|
+
get formattedValue() {
|
|
6489
|
+
if (this.control === 'password')
|
|
6490
|
+
return this.value ? '••••••••' : '';
|
|
6491
|
+
const found = choicesFor(this.property, this.option, this.items).find(c => Object.is(c.value, this.value));
|
|
6492
|
+
if (found)
|
|
6493
|
+
return found.label;
|
|
6494
|
+
if (this.value instanceof Date)
|
|
6495
|
+
return this.value.toLocaleString();
|
|
6496
|
+
return this.value == null ? '' : typeof this.value === 'object' ? JSON.stringify(this.value) : `${this.value}`;
|
|
6497
|
+
}
|
|
6498
|
+
ngOnChanges() { this.validatorChanged(); }
|
|
6499
|
+
writeValue(value) { this.value = value; }
|
|
6500
|
+
registerOnChange(fn) { this.onChange = fn; }
|
|
6501
|
+
registerOnTouched(fn) { this.onTouched = fn; }
|
|
6502
|
+
setDisabledState(disabled) { this.formDisabled = disabled; }
|
|
6503
|
+
registerOnValidatorChange(fn) { this.validatorChanged = fn; }
|
|
6504
|
+
validate(_control) { return this.errors; }
|
|
6505
|
+
change(value) {
|
|
6506
|
+
if (this.disabled || this.readonly || this.protectedValue)
|
|
6507
|
+
return;
|
|
6508
|
+
this.value = value;
|
|
6509
|
+
this.onChange(value);
|
|
6510
|
+
this.validatorChanged();
|
|
6511
|
+
}
|
|
6512
|
+
blur() { this.touched = true; this.onTouched(); this.fieldBlur.emit(); }
|
|
6513
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: FieldRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6514
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.2", type: FieldRendererComponent, isStandalone: true, selector: "vl-field-renderer", inputs: { field: "field", property: "property", items: "items", translations: "translations", errorMessages: "errorMessages", showErrors: "showErrors" }, outputs: { itemSelected: "itemSelected", fieldBlur: "fieldBlur" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => FieldRendererComponent), multi: true }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => FieldRendererComponent), multi: true }], usesOnChanges: true, ngImport: i0, template: "<div *ngIf=\"option.visible !== false\" [ngStyle]=\"innerStyle\"\n [attr.aria-disabled]=\"disabled\">\n <label *ngIf=\"label && !afterLabel\"\n [attr.for]=\"control==='radiobutton'?null:inputId\"\n [ngStyle]=\"styles(field.label?.style)\"\n [class]=\"classes(field.label?.style)\">\n {{label}}<span *ngIf=\"resolvedOption.required\" aria-hidden=\"true\"> *</span>\n <span *ngIf=\"option.tooltip\" class=\"help\"\n [matTooltip]=\"translations[option.tooltip] || option.tooltip\" tabindex=\"0\"\n [attr.aria-label]=\"translations[option.tooltip] || option.tooltip\">\u24D8</span>\n </label>\n <div class=\"control-area\"\n [attr.aria-invalid]=\"errors && (touched||showErrors)?'true':null\"\n [attr.aria-describedby]=\"errors && (touched||showErrors)?inputId+'-error':null\">\n <div *ngIf=\"!allowed\" role=\"alert\">{{errorText}}</div>\n <span *ngIf=\"allowed && protectedValue && type!=='media'\"\n [ngStyle]=\"styles(field.scope?.style)\">{{formattedValue}}</span>\n <ng-container *ngIf=\"allowed && (!protectedValue || type==='media')\">\n <vl-text-control\n *ngIf=\"type==='string' && ['textInput','textArea','password','email'].includes(control)\"\n [value]=\"value\" [property]=\"property\" [option]=\"resolvedOption\"\n [control]=\"control\" [inputId]=\"inputId\"\n [inputStyle]=\"field.scope?.style\" [disabled]=\"disabled\"\n [readonly]=\"readonly\" [keyboard]=\"keyboard\"\n (changed)=\"change($event)\" (blurred)=\"blur()\"></vl-text-control>\n <vl-numeric-control\n *ngIf=\"['number','integer','bigdecimal'].includes(type)\" [value]=\"value\"\n [property]=\"property\" [option]=\"resolvedOption\" [control]=\"control\"\n [inputId]=\"inputId\"\n [inputStyle]=\"field.scope?.style\" [disabled]=\"disabled\"\n [readonly]=\"readonly\" [keyboard]=\"keyboard\"\n (changed)=\"change($event)\" (blurred)=\"blur()\"></vl-numeric-control>\n <vl-toggle-control *ngIf=\"['checkbox','switch'].includes(control)\"\n [value]=\"value\" [property]=\"property\" [option]=\"resolvedOption\"\n [control]=\"control\" [inputId]=\"inputId\"\n [inputStyle]=\"field.scope?.style\" [disabled]=\"disabled\"\n [readonly]=\"readonly\" [keyboard]=\"keyboard\"\n (changed)=\"change($event)\" (blurred)=\"blur()\"></vl-toggle-control>\n <vl-selection-control\n *ngIf=\"['dropdown','combobox','autocomplete','radiobutton'].includes(control)\"\n [value]=\"value\" [property]=\"property\" [option]=\"resolvedOption\"\n [control]=\"control\" [inputId]=\"inputId\"\n [inputStyle]=\"field.scope?.style\" [disabled]=\"disabled\"\n [readonly]=\"readonly\" [keyboard]=\"keyboard\"\n (changed)=\"change($event)\" (blurred)=\"blur()\" [items]=\"items\"\n (itemSelected)=\"itemSelected.emit($event)\"></vl-selection-control>\n <vl-temporal-control *ngIf=\"['date','time','datetime'].includes(type)\"\n [value]=\"value\" [property]=\"property\" [option]=\"resolvedOption\"\n [control]=\"control\" [inputId]=\"inputId\"\n [inputStyle]=\"field.scope?.style\" [disabled]=\"disabled\"\n [readonly]=\"readonly\" [keyboard]=\"keyboard\"\n (changed)=\"change($event)\" (blurred)=\"blur()\"></vl-temporal-control>\n <vl-media-control *ngIf=\"type==='media'\" [value]=\"value\"\n [property]=\"property\" [option]=\"resolvedOption\" [control]=\"control\"\n [inputId]=\"inputId\"\n [inputStyle]=\"field.scope?.style\" [disabled]=\"disabled\"\n [readonly]=\"readonly\" [keyboard]=\"keyboard\"\n (changed)=\"change($event)\" (blurred)=\"blur()\"></vl-media-control>\n </ng-container>\n <label *ngIf=\"label && afterLabel\" [attr.for]=\"inputId\"\n [ngStyle]=\"styles(field.label?.style)\"\n [class]=\"classes(field.label?.style)\">{{label}}</label>\n <small *ngIf=\"errors && (touched||showErrors)\" [id]=\"inputId+'-error'\"\n class=\"error\" role=\"alert\"\n [ngStyle]=\"styles(field.validation?.style)\">{{errorText}}</small>\n </div>\n</div>\n", styles: [":host{display:block;min-width:0}.control-area{min-width:0}.error{display:block;margin-top:4px;color:#b91c1c;font-size:12px}.help{margin-inline-start:5px;color:#64748b;cursor:help}label{font:inherit}.control-area>label{margin-inline-start:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i17.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: TextControlComponent, selector: "vl-text-control" }, { kind: "component", type: NumericControlComponent, selector: "vl-numeric-control" }, { kind: "component", type: ToggleControlComponent, selector: "vl-toggle-control" }, { kind: "component", type: SelectionControlComponent, selector: "vl-selection-control", inputs: ["items"] }, { kind: "component", type: TemporalControlComponent, selector: "vl-temporal-control" }, { kind: "component", type: MediaControlComponent, selector: "vl-media-control" }] });
|
|
6515
|
+
}
|
|
6516
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: FieldRendererComponent, decorators: [{
|
|
6517
|
+
type: Component,
|
|
6518
|
+
args: [{ selector: 'vl-field-renderer', standalone: true, imports: [CommonModule, MatTooltipModule, TextControlComponent, NumericControlComponent, ToggleControlComponent, SelectionControlComponent, TemporalControlComponent, MediaControlComponent], providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => FieldRendererComponent), multi: true }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => FieldRendererComponent), multi: true }], template: "<div *ngIf=\"option.visible !== false\" [ngStyle]=\"innerStyle\"\n [attr.aria-disabled]=\"disabled\">\n <label *ngIf=\"label && !afterLabel\"\n [attr.for]=\"control==='radiobutton'?null:inputId\"\n [ngStyle]=\"styles(field.label?.style)\"\n [class]=\"classes(field.label?.style)\">\n {{label}}<span *ngIf=\"resolvedOption.required\" aria-hidden=\"true\"> *</span>\n <span *ngIf=\"option.tooltip\" class=\"help\"\n [matTooltip]=\"translations[option.tooltip] || option.tooltip\" tabindex=\"0\"\n [attr.aria-label]=\"translations[option.tooltip] || option.tooltip\">\u24D8</span>\n </label>\n <div class=\"control-area\"\n [attr.aria-invalid]=\"errors && (touched||showErrors)?'true':null\"\n [attr.aria-describedby]=\"errors && (touched||showErrors)?inputId+'-error':null\">\n <div *ngIf=\"!allowed\" role=\"alert\">{{errorText}}</div>\n <span *ngIf=\"allowed && protectedValue && type!=='media'\"\n [ngStyle]=\"styles(field.scope?.style)\">{{formattedValue}}</span>\n <ng-container *ngIf=\"allowed && (!protectedValue || type==='media')\">\n <vl-text-control\n *ngIf=\"type==='string' && ['textInput','textArea','password','email'].includes(control)\"\n [value]=\"value\" [property]=\"property\" [option]=\"resolvedOption\"\n [control]=\"control\" [inputId]=\"inputId\"\n [inputStyle]=\"field.scope?.style\" [disabled]=\"disabled\"\n [readonly]=\"readonly\" [keyboard]=\"keyboard\"\n (changed)=\"change($event)\" (blurred)=\"blur()\"></vl-text-control>\n <vl-numeric-control\n *ngIf=\"['number','integer','bigdecimal'].includes(type)\" [value]=\"value\"\n [property]=\"property\" [option]=\"resolvedOption\" [control]=\"control\"\n [inputId]=\"inputId\"\n [inputStyle]=\"field.scope?.style\" [disabled]=\"disabled\"\n [readonly]=\"readonly\" [keyboard]=\"keyboard\"\n (changed)=\"change($event)\" (blurred)=\"blur()\"></vl-numeric-control>\n <vl-toggle-control *ngIf=\"['checkbox','switch'].includes(control)\"\n [value]=\"value\" [property]=\"property\" [option]=\"resolvedOption\"\n [control]=\"control\" [inputId]=\"inputId\"\n [inputStyle]=\"field.scope?.style\" [disabled]=\"disabled\"\n [readonly]=\"readonly\" [keyboard]=\"keyboard\"\n (changed)=\"change($event)\" (blurred)=\"blur()\"></vl-toggle-control>\n <vl-selection-control\n *ngIf=\"['dropdown','combobox','autocomplete','radiobutton'].includes(control)\"\n [value]=\"value\" [property]=\"property\" [option]=\"resolvedOption\"\n [control]=\"control\" [inputId]=\"inputId\"\n [inputStyle]=\"field.scope?.style\" [disabled]=\"disabled\"\n [readonly]=\"readonly\" [keyboard]=\"keyboard\"\n (changed)=\"change($event)\" (blurred)=\"blur()\" [items]=\"items\"\n (itemSelected)=\"itemSelected.emit($event)\"></vl-selection-control>\n <vl-temporal-control *ngIf=\"['date','time','datetime'].includes(type)\"\n [value]=\"value\" [property]=\"property\" [option]=\"resolvedOption\"\n [control]=\"control\" [inputId]=\"inputId\"\n [inputStyle]=\"field.scope?.style\" [disabled]=\"disabled\"\n [readonly]=\"readonly\" [keyboard]=\"keyboard\"\n (changed)=\"change($event)\" (blurred)=\"blur()\"></vl-temporal-control>\n <vl-media-control *ngIf=\"type==='media'\" [value]=\"value\"\n [property]=\"property\" [option]=\"resolvedOption\" [control]=\"control\"\n [inputId]=\"inputId\"\n [inputStyle]=\"field.scope?.style\" [disabled]=\"disabled\"\n [readonly]=\"readonly\" [keyboard]=\"keyboard\"\n (changed)=\"change($event)\" (blurred)=\"blur()\"></vl-media-control>\n </ng-container>\n <label *ngIf=\"label && afterLabel\" [attr.for]=\"inputId\"\n [ngStyle]=\"styles(field.label?.style)\"\n [class]=\"classes(field.label?.style)\">{{label}}</label>\n <small *ngIf=\"errors && (touched||showErrors)\" [id]=\"inputId+'-error'\"\n class=\"error\" role=\"alert\"\n [ngStyle]=\"styles(field.validation?.style)\">{{errorText}}</small>\n </div>\n</div>\n", styles: [":host{display:block;min-width:0}.control-area{min-width:0}.error{display:block;margin-top:4px;color:#b91c1c;font-size:12px}.help{margin-inline-start:5px;color:#64748b;cursor:help}label{font:inherit}.control-area>label{margin-inline-start:8px}\n"] }]
|
|
6519
|
+
}], propDecorators: { field: [{
|
|
6520
|
+
type: Input
|
|
6521
|
+
}], property: [{
|
|
6522
|
+
type: Input
|
|
6523
|
+
}], items: [{
|
|
6524
|
+
type: Input
|
|
6525
|
+
}], translations: [{
|
|
6526
|
+
type: Input
|
|
6527
|
+
}], errorMessages: [{
|
|
6528
|
+
type: Input
|
|
6529
|
+
}], showErrors: [{
|
|
6530
|
+
type: Input
|
|
6531
|
+
}], itemSelected: [{
|
|
6532
|
+
type: Output
|
|
6533
|
+
}], fieldBlur: [{
|
|
6534
|
+
type: Output
|
|
6535
|
+
}] } });
|
|
6536
|
+
|
|
5551
6537
|
// Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13
|
|
5552
6538
|
// - fix: a field without a placeholder stays empty; it showed the text "undefined" (resolvePlaceholder)
|
|
5553
6539
|
// Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07
|
|
@@ -5612,41 +6598,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
5612
6598
|
type: Input
|
|
5613
6599
|
}] } });
|
|
5614
6600
|
|
|
5615
|
-
// Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13
|
|
5616
|
-
// - fix: a field's backend list (option.autocomplete: url, method, params) is requested whenever the
|
|
5617
|
-
// field has that source, whatever its data type and control - autocomplete reads it as its
|
|
5618
|
-
// suggestions, dropdown and combobox as their items (agreed with the developer). The work order
|
|
5619
|
-
// fields are type string and were never requested. {{userInfo.x}} in the parameters resolves -
|
|
5620
|
-
// resourceNo was sent empty
|
|
5621
|
-
// - fix: the autocomplete request runs under the waiting rule of every request (stagedRequest:
|
|
5622
|
-
// hints after 10 and 30 s, given up after 60 s); on failure the list is empty, so the spinner no
|
|
5623
|
-
// longer turns for ever
|
|
5624
|
-
// Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07
|
|
5625
|
-
// - change: the date and time icons open the picker with showOverlay() and markForCheck(). PrimeNG 21
|
|
5626
|
-
// DatePicker has no show(); the call threw, the catch swallowed it, and the overlay set in finally
|
|
5627
|
-
// stayed invisible (the picker is OnPush) until the user tapped the field - which then showed the
|
|
5628
|
-
// calendar together with a keyboard: the "two keyboards" seen on iOS
|
|
5629
|
-
// - change: a second picker icon is no longer ignored while another picker is open. The open picker
|
|
5630
|
-
// is closed without focusing its field, and its late onClose no longer cancels the new one
|
|
5631
|
-
// - change: after an icon tap a click that no longer hits the icon is dropped. Closing the keyboard
|
|
5632
|
-
// resizes the dialog under the finger, and the click opened the autocomplete moved there
|
|
5633
|
-
// - change: an icon tap releases the field that has the focus. Its preventDefault kept an
|
|
5634
|
-
// autocomplete focused, so its list and the system keyboard stayed open beside the calendar
|
|
5635
|
-
// - new: keyboardTypeFor, so every field gets the toolkit keyboard of its attribute type
|
|
5636
|
-
// - new: on the devices the date picker docks at the bottom, where the keyboard would be, and the
|
|
5637
|
-
// dialog ends above it; the small time picker stays at its field. Tapping into a field with an
|
|
5638
|
-
// open picker opens the keyboard at once
|
|
5639
|
-
// - change: after a selection in a picker the field gives up the focus PrimeNG returns to it; it
|
|
5640
|
-
// showed a focus frame without any keyboard
|
|
5641
|
-
// - change: the docked date picker has no open/close animation; it made the panel jump up to its
|
|
5642
|
-
// field while closing
|
|
5643
|
-
// - new: the field of an open picker keeps a focus frame (picker-field--active), since the field
|
|
5644
|
-
// itself gives the focus away
|
|
5645
|
-
// - change: the picker logic moved to DateTimePickerService, shared with form, card and grid
|
|
5646
|
-
// - new: ngOnDestroy closes a picker that is still open, so closing the dialog cannot leave the
|
|
5647
|
-
// keyboard service blocked and the next dialog cut short
|
|
5648
|
-
// Shuaib 2026-09-24
|
|
5649
|
-
// - new: renderFieldType maps legacy checkbox fields to boolean.
|
|
5650
6601
|
class ModelComponent {
|
|
5651
6602
|
cdr;
|
|
5652
6603
|
dialogRef;
|
|
@@ -5697,6 +6648,7 @@ class ModelComponent {
|
|
|
5697
6648
|
});
|
|
5698
6649
|
}
|
|
5699
6650
|
ngOnDestroy() {
|
|
6651
|
+
this.projectLanguage?.unsubscribe?.();
|
|
5700
6652
|
/*
|
|
5701
6653
|
* The dialog can be closed with a picker still open. Without this the keyboard service keeps
|
|
5702
6654
|
* "picker open" and the reserved bottom area: no toolkit keyboard opens any more and the next
|
|
@@ -5725,6 +6677,7 @@ class ModelComponent {
|
|
|
5725
6677
|
if (this.modalItemInfo?.currentRecord) {
|
|
5726
6678
|
this.mapDataToPropertySchema(this.modalItemInfo.currentRecord, this.modalItemInfo);
|
|
5727
6679
|
}
|
|
6680
|
+
this.initializeSharedDefaults();
|
|
5728
6681
|
await this.setAutoCompleteData();
|
|
5729
6682
|
}
|
|
5730
6683
|
updateDialogHeader() {
|
|
@@ -6138,6 +7091,7 @@ class ModelComponent {
|
|
|
6138
7091
|
}
|
|
6139
7092
|
}
|
|
6140
7093
|
checkValidty(form) {
|
|
7094
|
+
this.sharedErrorsVisible = true;
|
|
6141
7095
|
if (!form) {
|
|
6142
7096
|
return false;
|
|
6143
7097
|
}
|
|
@@ -6152,12 +7106,120 @@ class ModelComponent {
|
|
|
6152
7106
|
action: 'cancel'
|
|
6153
7107
|
});
|
|
6154
7108
|
}
|
|
6155
|
-
|
|
6156
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.2", type: ModelComponent, isStandalone: true, selector: "vl-model", inputs: { errorMessages: "errorMessages", modalItemInfo: "modalItemInfo", selectedLanguageInfo: "selectedLanguageInfo", userInfo: "userInfo", setFormDataHandler: "setFormDataHandler" }, usesOnChanges: true, ngImport: i0, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\r\n project defines none; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - change: number, autocomplete, date and time fields take the toolkit keyboard of their\r\n attribute type (keyboardTypeFor) instead of only an explicit option.keyboardType\r\n - change: the date picker docks at the bottom on the devices (model-datepicker-panel--docked);\r\n the time picker stays attached to its field in a compact form (model-timepicker-panel);\r\n both report which picker closed (onPickerClose(picker)) -->\r\n\r\n<!-- Shuaib 2026-09-24\r\n - change: the model field switch uses renderFieldType so legacy checkbox fields render as boolean. -->\r\n\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: a @contract comment on the widest type coverage in the toolkit, and one on the credit\r\n card case recording that the rename reached this file first -->\r\n<!-- Loader Spinner -->\r\n<div *ngIf=\"isLoading\" class=\"loader-overlay\">\r\n <div class=\"spinner\"></div>\r\n</div>\r\n<div [style]=\"getStyleWithoutClasses(modalItemInfo?.layoutSchema?.style).style\"\r\n [class]=\"modalItemInfo?.layoutSchema?.style?.classes[0]\">\r\n <form #dynamicForm=\"ngForm\"\r\n *ngIf=\"modalItemInfo?.layoutSchema?.formSections?.length > 0\"\r\n (submit)=\"$event.preventDefault()\">\r\n <div\r\n *ngFor=\"let formSection of modalItemInfo?.layoutSchema?.formSections\">\r\n <div *ngIf=\"formSection?.label?.caption\"\r\n [class]=\"formSection?.label?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(formSection?.label?.style).style\">\r\n {{formSection.label.caption}}\r\n </div>\r\n <div *ngIf=\"formSection?.formFields?.length > 0\"\r\n [class]=\"formSection?.style?.classes?.[0] || ''\"\r\n [ngStyle]=\"getSectionBodyStyle(formSection)\">\r\n <div *ngFor=\"let formField of formSection.formFields\"\r\n [ngStyle]=\"getFieldWrapperStyle(formField)\"\r\n [class]=\"formField?.style?.classes || ''\">\r\n\r\n <!-- @contract decision: the widest type coverage in the toolkit\r\n reality: every field type except the grid-only quantity renders here.\r\n When another container drops a type, this is the way out.\r\n seen: measured 2026-09-19. Note the file is model.component.html while\r\n the container is called modal in layout files - that costs a\r\n search every time -->\r\n <!-- @contract compatibility: checkbox is rendered as boolean for existing projects. -->\r\n <div [ngSwitch]=\"renderFieldType(\r\n formField?.scope?.reference &&\r\n modalItemInfo?.propertySchema?.[\r\n getScope(formField.scope.reference)\r\n ]\r\n ? modalItemInfo.propertySchema[\r\n getScope(formField.scope.reference)\r\n ]?.[\r\n formField.scope.reference.split('/').pop()\r\n ]?.type\r\n : 'default'\r\n )\">\r\n <!-- image control -->\r\n <div *ngSwitchCase=\"'image'\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\">\r\n <img [src]=\"formField.option.src\" [alt]=\"'img'\" />\r\n </div>\r\n\r\n <!-- credit card control -->\r\n <!-- @contract decision: the credit card type is spelled creditCard\r\n reality: this file was renamed on 2026-09-15, form.component.html and\r\n utility.service.ts followed on 2026-09-19. carditCard is read\r\n nowhere any more.\r\n failure: a data object still declaring carditCard renders empty. -->\r\n <div *ngSwitchCase=\"'creditCard'\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [class]=\"formField.style?.classes[0]\">\r\n <div id=\"card-element\">\r\n <!-- a Stripe element will be inserted here -->\r\n </div>\r\n <!-- Used to display form errors -->\r\n <div id=\"card-errors\"\r\n class=\"displayError.textContent!=='': 'alert alert-danger mt-1'\">\r\n </div>\r\n </div>\r\n\r\n <!-- string control -->\r\n <div *ngSwitchCase=\"'string'\" class=\"form-group mb-3\">\r\n @if(formField.option && formField.option.control &&\r\n !formField.option.protected){\r\n @switch (formField.option.control) {\r\n @case ('textInput') {\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <vl-label [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-label>\r\n <vl-textbox [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-textbox>\r\n </div>\r\n <!-- Error Message -->\r\n <!-- <div *ngIf=\"inputField?.touched && inputField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\" *ngIf=\"inputField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages, \"required\")}}</p>\r\n <p class=\"text-danger\" *ngIf=\"inputField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div> -->\r\n }\r\n\r\n @case ('password') {\r\n <span>Switch not supported yet</span>\r\n\r\n }\r\n @case ('combobox') {\r\n <span>Combobox not supported yet</span>\r\n }\r\n @case('textArea'){\r\n <span>TextArea not supported yet</span>\r\n }\r\n @case ('switch') {\r\n <span>Switch not supported yet</span>\r\n }\r\n\r\n @case('radiobutton') {\r\n <span>Radiobutton not supported yet</span>\r\n }\r\n\r\n @case ('dropdown') {\r\n <span *ngIf=\"formField.option?.postfixCaption\">\r\n <select id=\"dynamic-dropdown\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\"\r\n #dropdownField=\"ngModel\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [name]=\"formField.scope?.reference\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\">\r\n <option\r\n *ngFor=\"let item of modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.data\"\r\n [value]=\"formField?.option?.optionValue ? formField.option.optionValue : 'value'\">\r\n {{ formField?.option?.optionLabel ?\r\n formField.option.optionLabel :\r\n 'label'}}</option>\r\n </select>\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\">{{modalItemInfo.propertySchema[getScope(formField.scope)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n </span>\r\n <div *ngIf=\"!formField.option?.postfixCaption\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\">{{modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n\r\n <nz-select nzShowSearch nzAllowClear\r\n nzPlaceHolder=\"Select\"\r\n #dropdownField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [name]=\"formField.scope?.reference\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\">\r\n <nz-option\r\n *ngFor=\"let item of modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.data\"\r\n [nzLabel]=\"item[formField?.option?.optionLabel ? formField.option.optionLabel : 'label']\"\r\n [nzValue]=\"item[formField?.option?.optionValue ? formField.option.optionValue : 'value']\"></nz-option>\r\n </nz-select>\r\n </div>\r\n <!-- Error Message -->\r\n <div\r\n *ngIf=\"dropdownField?.touched && dropdownField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dropdownField?.errors?.['required']\">\r\n {{formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n +\r\n ' ' + errorMessages.required}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dropdownField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </div>\r\n }\r\n\r\n @case ('autocomplete') {\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <vl-label [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-label>\r\n\r\n <span>\r\n <p-autocomplete\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [class]=\"formField?.scope?.style?.classes ? formField?.scope?.style?.classes[0] : ''\"\r\n [suggestions]=\"matchData\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"formField?.option?.autocomplete?.response?.label ? formField.option.autocomplete.response.label : 'label'\"\r\n [placeholder]=\"modalItemInfo.propertySchema | vlPlaceholder: formField.scope?.reference\"\r\n (completeMethod)=\"autocompChangeEvent($event, formField)\"\r\n (ngModelChange)=\"autocompFocusOutEvent($event, formField)\"\r\n [vlCustomKeyboard]=\"keyboardTypeFor(formField)\">\r\n </p-autocomplete>\r\n <!-- Error Message -->\r\n <div\r\n *ngIf=\"autocompleteField?.touched && autocompleteField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['required']\">\r\n {{setErroMessage(formField,\r\n errorMessages, \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' +\r\n errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n </div>\r\n }\r\n }\r\n }@else{\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <vl-label [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-label>\r\n <vl-textbox [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-textbox>\r\n </div>\r\n <!-- Error Message -->\r\n <!-- <div *ngIf=\"inputField?.touched && inputField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\" *ngIf=\"inputField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages, \"required\")}}</p>\r\n <p class=\"text-danger\" *ngIf=\"inputField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div> -->\r\n </div>\r\n }\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-string>\r\n </span>\r\n\r\n </div>\r\n\r\n <!-- number control -->\r\n <div *ngSwitchCase=\"'number'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <h5>{{\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }} {{\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.value\r\n |\r\n currency:formField.option.currency :true:\r\n formField.option?.decimalPoint}}\r\n </h5>\r\n </span>\r\n <span *ngIf=\"!formField.option.protected\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{\r\n formField.label?.value\r\n ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <input type=\"number\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\"\r\n [vlCustomKeyboard]=\"keyboardTypeFor(formField)\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\r\n [name]=\"formField.scope?.reference\"\r\n [placeholder]=\"modalItemInfo.propertySchema | vlPlaceholder: formField.scope?.reference\" />\r\n </div>\r\n </span>\r\n </div>\r\n\r\n <!-- DateRange control -->\r\n <div *ngSwitchCase=\"'dateRange'\"\r\n class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{\r\n formField.label?.value\r\n ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <nz-range-picker #dateRangeField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"></nz-range-picker>\r\n </div>\r\n <!-- Error Message -->\r\n <div\r\n *ngIf=\"dateRangeField?.touched && dateRangeField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateRangeField?.errors?.['required']\">\r\n {{formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n +\r\n ' ' + errorMessages.required}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateRangeField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Date control -->\r\n <div *ngSwitchCase=\"'date'\" class=\"form-group mb-3\">\r\n <span\r\n *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string\r\n [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-string>\r\n </span>\r\n\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n {{ getLabel(formField) }}\r\n </label>\r\n\r\n <span>\r\n <div class=\"picker-field\">\r\n <p-datepicker\r\n #datePicker\r\n #dateField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n [disabled]=\"formField?.option?.disabled\"\r\n [showIcon]=\"false\"\r\n [showOnFocus]=\"false\"\r\n appendTo=\"body\"\r\n [autoZIndex]=\"true\"\r\n [baseZIndex]=\"10000\"\r\n [panelStyleClass]=\"picker.datePanelClass\"\r\n [motionOptions]=\"picker.dockedMotion\"\r\n [vlCustomKeyboard]=\"keyboardTypeFor(formField)\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, datePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(datePicker)\"\r\n (ngModelChange)=\"dateChangeEvent(formField, $event)\"\r\n (onInput)=\"onDateTyping($event)\"\r\n [dateFormat]=\"\r\n modalItemInfo.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ]?.format || 'yy-mm-dd'\r\n \"\r\n [(ngModel)]=\"\r\n modalItemInfo.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [class]=\"formField?.scope?.style?.classes?.[0] || ''\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\">\r\n </p-datepicker>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"picker-field__icon\"\r\n aria-label=\"Open calendar\"\r\n [disabled]=\"formField?.option?.disabled\"\r\n (pointerdown)=\"picker.openFromIcon($event, datePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-calendar\"></i>\r\n </button>\r\n </div>\r\n\r\n <div\r\n *ngIf=\"dateField?.touched && dateField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateField?.errors?.['required']\">\r\n {{ setErroMessage(formField,\r\n errorMessages, 'required') }}\r\n </p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateField?.errors?.['email']\">\r\n {{\r\n (formField.label?.value\r\n ? formField.label?.value\r\n : modalItemInfo.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption)\r\n + ' ' + errorMessages.pattern\r\n }}\r\n </p>\r\n </div>\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Time control -->\r\n <div *ngSwitchCase=\"'time'\" class=\"form-group mb-3\">\r\n <span\r\n *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string\r\n [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-string>\r\n </span>\r\n\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n {{ getLabel(formField) }}\r\n </label>\r\n\r\n <span>\r\n <div class=\"picker-field\">\r\n <p-datepicker\r\n #timePicker\r\n #timeField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n [timeOnly]=\"true\"\r\n hourFormat=\"24\"\r\n [disabled]=\"formField?.option?.disabled\"\r\n [showIcon]=\"false\"\r\n [showOnFocus]=\"false\"\r\n appendTo=\"body\"\r\n [autoZIndex]=\"true\"\r\n [baseZIndex]=\"10000\"\r\n [panelStyleClass]=\"picker.timePanelClass\"\r\n [vlCustomKeyboard]=\"keyboardTypeFor(formField)\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, timePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"onTimePanelShow(formField)\"\r\n (onClose)=\"picker.onClose(timePicker)\"\r\n (onInput)=\"onTimeTyping($event)\"\r\n (ngModelChange)=\"timeChangeEvent(formField, $event)\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [(ngModel)]=\"\r\n modalItemInfo.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [class]=\"formField?.scope?.style?.classes?.[0] || ''\">\r\n </p-datepicker>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"picker-field__icon\"\r\n aria-label=\"Open time picker\"\r\n [disabled]=\"formField?.option?.disabled\"\r\n (pointerdown)=\"picker.openFromIcon($event, timePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-clock\"></i>\r\n </button>\r\n </div>\r\n\r\n <div\r\n *ngIf=\"timeField?.touched && timeField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"timeField?.errors?.['required']\">\r\n {{ setErroMessage(formField,\r\n errorMessages, 'required') }}\r\n </p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"timeField?.errors?.['email']\">\r\n {{\r\n (formField.label?.value\r\n ? formField.label?.value\r\n : modalItemInfo.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption)\r\n + ' ' + errorMessages.pattern\r\n }}\r\n </p>\r\n </div>\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Autocomplete control -->\r\n <div *ngSwitchCase=\"'autocomplete'\"\r\n class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <vl-label [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-label>\r\n\r\n <span>\r\n <p-autocomplete\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [class]=\"formField?.scope?.style?.classes ? formField?.scope?.style?.classes[0] : ''\"\r\n [suggestions]=\"matchData\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"formField?.option?.autocomplete?.response?.label ? formField.option.autocomplete.response.label : 'label'\"\r\n [placeholder]=\"modalItemInfo.propertySchema | vlPlaceholder: formField.scope?.reference\"\r\n (completeMethod)=\"autocompChangeEvent($event, formField)\"\r\n (ngModelChange)=\"autocompFocusOutEvent($event, formField)\"\r\n [vlCustomKeyboard]=\"keyboardTypeFor(formField)\">\r\n </p-autocomplete>\r\n <!-- Error Message -->\r\n <div\r\n *ngIf=\"autocompleteField?.touched && autocompleteField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['required']\">\r\n {{setErroMessage(formField,\r\n errorMessages, \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' +\r\n errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- checkBox control -->\r\n <div *ngSwitchCase=\"'boolean'\" class=\"form-group mb-3\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0] : ''\">\r\n <span *ngIf=\"formField.option?.postfixCaption\">\r\n <label class=\"form-check-label\"><input\r\n type=\"checkbox\" class=\"form-check-input\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\r\n [name]=\"formField.scope?.reference\"\r\n (ngModelChange)=\"invokeSetformData()\" />{{\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n </span>\r\n <div *ngIf=\"!formField.option?.postfixCaption\"\r\n [class]=\"formField?.layout == 'horizontal' ? 'horizontal-layout' : 'row'\">\r\n <label class=\"form-check-label\">{{\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}\r\n <input type=\"checkbox\"\r\n class=\"form-check-input\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\r\n [name]=\"formField.scope?.reference\" /></label>\r\n </div>\r\n </div>\r\n\r\n <!-- dropdown control -->\r\n <div *ngSwitchCase=\"'dropdown'\" class=\"form-group mb-3\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0] : ''\">\r\n <span *ngIf=\"formField.option?.postfixCaption\">\r\n <select id=\"dynamic-dropdown\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\"\r\n #dropdownField=\"ngModel\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [name]=\"formField.scope?.reference\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\">\r\n <option\r\n *ngFor=\"let item of modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.data\"\r\n [value]=\"formField?.option?.optionValue ? formField.option.optionValue : 'value'\">\r\n {{ formField?.option?.optionLabel ?\r\n formField.option.optionLabel :\r\n 'label'}}</option>\r\n </select>\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\">{{modalItemInfo.propertySchema[getScope(formField.scope)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n </span>\r\n <div *ngIf=\"!formField.option?.postfixCaption\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\">{{modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n\r\n <nz-select nzShowSearch nzAllowClear\r\n nzPlaceHolder=\"Select\"\r\n #dropdownField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [name]=\"formField.scope?.reference\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\">\r\n <nz-option\r\n *ngFor=\"let item of modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.data\"\r\n [nzLabel]=\"item[formField?.option?.optionLabel ? formField.option.optionLabel : 'label']\"\r\n [nzValue]=\"item[formField?.option?.optionValue ? formField.option.optionValue : 'value']\"></nz-option>\r\n </nz-select>\r\n </div>\r\n <!-- Error Message -->\r\n <div\r\n *ngIf=\"dropdownField?.touched && dropdownField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dropdownField?.errors?.['required']\">\r\n {{formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n +\r\n ' ' + errorMessages.required}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dropdownField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- button control -->\r\n <div *ngSwitchCase=\"'default'\">\r\n <button type=\"button\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField.style).style\"\r\n (click)=\"invokeFormBtnClick(formField.option, modalItemInfo.propertySchema, dynamicForm)\"\r\n class=\"btn btn-primary btn-block\">\r\n {{ getButtonCaption(formField) }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n</div>\r\n", styles: [":host ::ng-deep .custom-datepicker .p-datepicker-dropdown{height:100%;border-radius:4px}:host ::ng-deep .custom-datepicker .p-datepicker-input{border-radius:4px}::ng-deep .p-autocomplete-list{background-color:#fff}:host ::ng-deep .custom-autocomplete{display:flex;width:100%}:host ::ng-deep .custom-autocomplete .p-autocomplete-input{flex:1;width:100%;min-width:0;border:none!important;outline:none!important;box-shadow:none!important;background:transparent!important}:host ::ng-deep .custom-autocomplete .p-autocomplete-dropdown{height:100%;border-radius:4px}:host ::ng-deep .custom-autocomplete .p-autocomplete-input:focus,:host ::ng-deep .custom-autocomplete .p-autocomplete-input:focus-visible,:host ::ng-deep .custom-autocomplete .p-inputtext:enabled:focus{border:none!important;outline:none!important;box-shadow:none!important}.responsive-modal-grid{width:100%;min-width:0;box-sizing:border-box}@media screen and (max-width:767px){.responsive-modal-grid{grid-template-columns:repeat(12,minmax(0,1fr))!important;gap:10px!important}.responsive-modal-field{grid-column:1 / -1!important;width:100%!important;max-width:none!important;min-width:0!important}.responsive-modal-action:nth-last-child(2){grid-column:1 / span 6!important}.responsive-modal-action:last-child{grid-column:7 / span 6!important}.responsive-modal-action{width:100%!important;max-width:none!important;min-width:0!important;justify-self:stretch!important;box-sizing:border-box!important}.responsive-modal-action button{display:block!important;width:100%!important;max-width:none!important;min-width:0!important;margin:0!important;box-sizing:border-box!important}}:host{--modal-field-focus-color: var(--p-primary-color, #10b981);--modal-field-focus-shadow: 0 0 0 2px rgba(16, 185, 129, .16)}:host ::ng-deep input:not([type=checkbox]):not(:disabled):focus,:host ::ng-deep textarea:not(:disabled):focus{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .custom-autocomplete:focus-within{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .custom-autocomplete:focus-within .p-autocomplete-input{border:none!important;outline:none!important;box-shadow:none!important}:host ::ng-deep select:not(:disabled):focus{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .ant-select-focused:not(.ant-select-disabled) .ant-select-selector,:host ::ng-deep nz-select:not(.ant-select-disabled):focus-within .ant-select-selector{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .p-select:not(.p-disabled).p-focus,:host ::ng-deep .p-select:not(.p-disabled):focus-within,:host ::ng-deep .p-dropdown:not(.p-disabled).p-focus,:host ::ng-deep .p-dropdown:not(.p-disabled):focus-within{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .custom-datepicker:focus-within .p-datepicker-input{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep input:disabled,:host ::ng-deep textarea:disabled,:host ::ng-deep select:disabled,:host ::ng-deep .p-disabled,:host ::ng-deep .ant-select-disabled .ant-select-selector{box-shadow:none!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i5.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i5.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i5.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i5.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: NzSelectModule }, { kind: "component", type: i3.NzOptionComponent, selector: "nz-option", inputs: ["nzTitle", "nzLabel", "nzValue", "nzKey", "nzDisabled", "nzHide", "nzCustomContent"], exportAs: ["nzOption"] }, { kind: "component", type: i3.NzSelectComponent, selector: "nz-select", inputs: ["nzId", "nzSize", "nzStatus", "nzVariant", "nzOptionHeightPx", "nzOptionOverflowSize", "nzDropdownClassName", "nzDropdownMatchSelectWidth", "nzDropdownStyle", "nzNotFoundContent", "nzPlaceHolder", "nzPlacement", "nzMaxTagCount", "nzDropdownRender", "nzCustomTemplate", "nzPrefix", "nzSuffixIcon", "nzClearIcon", "nzRemoveIcon", "nzMenuItemSelectedIcon", "nzTokenSeparators", "nzMaxTagPlaceholder", "nzMaxMultipleCount", "nzMode", "nzFilterOption", "compareWith", "nzAllowClear", "nzShowSearch", "nzLoading", "nzAutoFocus", "nzAutoClearSearchValue", "nzServerSearch", "nzDisabled", "nzOpen", "nzSelectOnTab", "nzBackdrop", "nzOptions", "nzShowArrow"], outputs: ["nzOnSearch", "nzScrollToBottom", "nzOpenChange", "nzBlur", "nzFocus", "nzOnClear"], exportAs: ["nzSelect"] }, { kind: "ngmodule", type: NzDatePickerModule }, { kind: "component", type: i4.NzDatePickerComponent, selector: "nz-date-picker,nz-week-picker,nz-month-picker,nz-quarter-picker,nz-year-picker,nz-range-picker", inputs: ["nzInline", "nzAllowClear", "nzAutoFocus", "nzDisabled", "nzInputReadOnly", "nzOpen", "nzDisabledDate", "nzLocale", "nzPlaceHolder", "nzPopupStyle", "nzDropdownClassName", "nzSize", "nzStatus", "nzFormat", "nzVariant", "nzDateRender", "nzDisabledTime", "nzRenderExtraFooter", "nzShowToday", "nzMode", "nzShowNow", "nzRanges", "nzDefaultPickerValue", "nzSeparator", "nzSuffixIcon", "nzBackdrop", "nzId", "nzPlacement", "nzShowWeekNumber", "nzShowTime"], outputs: ["nzOnPanelChange", "nzOnCalendarChange", "nzOnOk", "nzOnOpenChange"], exportAs: ["nzDatePicker"] }, { kind: "directive", type: i4.NzRangePickerComponent, selector: "nz-range-picker", exportAs: ["nzRangePicker"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i12.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i18.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "component", type: Label, selector: "vl-label", inputs: ["formField", "modalItemInfo"] }, { kind: "component", type: String$1, selector: "vl-string", inputs: ["formField", "modalItemInfo"] }, { kind: "component", type: Textbox, selector: "vl-textbox", inputs: ["formField", "modalItemInfo"] }, { kind: "directive", type: CustomKeyboardDirective, selector: "[vlCustomKeyboard]", inputs: ["vlCustomKeyboard"] }, { kind: "pipe", type: PlaceholderPipe, name: "vlPlaceholder" }, { kind: "pipe", type: i5.CurrencyPipe, name: "currency" }] });
|
|
7109
|
+
getMediaProperty(field) {
|
|
7110
|
+
const reference = field?.scope?.reference;
|
|
7111
|
+
const key = reference?.split('/').pop();
|
|
7112
|
+
return key
|
|
7113
|
+
? this.modalItemInfo?.propertySchema?.[this.getScope(reference)]?.[key]
|
|
7114
|
+
: undefined;
|
|
7115
|
+
}
|
|
7116
|
+
getMediaSource(field) {
|
|
7117
|
+
const property = this.getMediaProperty(field);
|
|
7118
|
+
const value = field?.option?.src || property?.value || property?.default || '';
|
|
7119
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
7120
|
+
}
|
|
7121
|
+
getMediaLabel(field) {
|
|
7122
|
+
if (typeof field?.label === 'string' && field.label.trim()) {
|
|
7123
|
+
return field.label;
|
|
7124
|
+
}
|
|
7125
|
+
return field?.label?.value
|
|
7126
|
+
|| field?.label?.caption
|
|
7127
|
+
|| this.getMediaProperty(field)?.caption
|
|
7128
|
+
|| 'Media';
|
|
7129
|
+
}
|
|
7130
|
+
getMediaKind(field) {
|
|
7131
|
+
const source = this.getMediaSource(field);
|
|
7132
|
+
const mime = /^data:([^;,]+)/i.exec(source)?.[1]?.toLowerCase();
|
|
7133
|
+
if (mime?.startsWith('image/'))
|
|
7134
|
+
return 'image';
|
|
7135
|
+
if (mime?.startsWith('video/'))
|
|
7136
|
+
return 'video';
|
|
7137
|
+
if (mime?.startsWith('audio/'))
|
|
7138
|
+
return 'audio';
|
|
7139
|
+
if (mime === 'application/pdf')
|
|
7140
|
+
return 'pdf';
|
|
7141
|
+
const property = this.getMediaProperty(field);
|
|
7142
|
+
const candidates = [
|
|
7143
|
+
source,
|
|
7144
|
+
field?.option?.fileName,
|
|
7145
|
+
property?.value,
|
|
7146
|
+
property?.default
|
|
7147
|
+
];
|
|
7148
|
+
const videos = ['mp4', 'm4v', 'webm', 'mov', 'ogv', 'avi', 'mkv'];
|
|
7149
|
+
const audios = ['mp3', 'wav', 'ogg', 'oga', 'm4a', 'aac', 'flac', 'opus'];
|
|
7150
|
+
const files = [
|
|
7151
|
+
'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
|
7152
|
+
'txt', 'csv', 'zip', 'rar', '7z'
|
|
7153
|
+
];
|
|
7154
|
+
for (const candidate of candidates) {
|
|
7155
|
+
if (typeof candidate !== 'string')
|
|
7156
|
+
continue;
|
|
7157
|
+
if (/^(data|blob):/i.test(candidate))
|
|
7158
|
+
continue;
|
|
7159
|
+
let path = candidate.split(/[?#]/)[0];
|
|
7160
|
+
try {
|
|
7161
|
+
path = decodeURIComponent(path);
|
|
7162
|
+
}
|
|
7163
|
+
catch {
|
|
7164
|
+
// Keep the original path.
|
|
7165
|
+
}
|
|
7166
|
+
const extension = /\.([a-z0-9]+)$/i.exec(path)?.[1]?.toLowerCase();
|
|
7167
|
+
if (!extension)
|
|
7168
|
+
continue;
|
|
7169
|
+
if (videos.includes(extension))
|
|
7170
|
+
return 'video';
|
|
7171
|
+
if (audios.includes(extension))
|
|
7172
|
+
return 'audio';
|
|
7173
|
+
if (extension === 'pdf')
|
|
7174
|
+
return 'pdf';
|
|
7175
|
+
if (files.includes(extension))
|
|
7176
|
+
return 'file';
|
|
7177
|
+
}
|
|
7178
|
+
return 'image';
|
|
7179
|
+
}
|
|
7180
|
+
sharedErrorsVisible = false;
|
|
7181
|
+
sharedProperty(field) {
|
|
7182
|
+
const reference = field?.scope?.reference;
|
|
7183
|
+
if (typeof reference !== 'string')
|
|
7184
|
+
return undefined;
|
|
7185
|
+
const parts = reference.replace(/^#\//, '').split('/');
|
|
7186
|
+
const root = this.modalItemInfo?.propertySchema?.[parts[0]];
|
|
7187
|
+
const key = parts[parts.length - 1];
|
|
7188
|
+
return root?.[key] ?? root?.properties?.[key];
|
|
7189
|
+
}
|
|
7190
|
+
usesSharedField(field) {
|
|
7191
|
+
const property = this.sharedProperty(field);
|
|
7192
|
+
return !!property && Object.prototype.hasOwnProperty.call(CONTROL_MAP, basicType(property));
|
|
7193
|
+
}
|
|
7194
|
+
initializeSharedDefaults() {
|
|
7195
|
+
for (const section of this.modalItemInfo?.layoutSchema?.formSections || []) {
|
|
7196
|
+
for (const field of section.formFields || []) {
|
|
7197
|
+
const property = this.sharedProperty(field);
|
|
7198
|
+
if (!property || property.value !== undefined)
|
|
7199
|
+
continue;
|
|
7200
|
+
const value = field.option?.default ?? field.option?.fieldData?.default ?? property.default;
|
|
7201
|
+
if (value !== undefined)
|
|
7202
|
+
property.value = structuredClone(value);
|
|
7203
|
+
}
|
|
7204
|
+
}
|
|
7205
|
+
}
|
|
7206
|
+
onSharedFieldChange(field, value) {
|
|
7207
|
+
const property = this.sharedProperty(field);
|
|
7208
|
+
if (!property)
|
|
7209
|
+
return;
|
|
7210
|
+
property.value = value;
|
|
7211
|
+
// Existing date/time rules and boolean copy actions continue through their original methods.
|
|
7212
|
+
if (field.option?.rules)
|
|
7213
|
+
this.handleRules(field, value);
|
|
7214
|
+
if (basicType(property) === 'boolean')
|
|
7215
|
+
this.invokeSetformData();
|
|
7216
|
+
}
|
|
7217
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: ModelComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i1$4.DynamicDialogRef }, { token: i1$4.DynamicDialogConfig }, { token: UtilityService }], target: i0.ɵɵFactoryTarget.Component });
|
|
7218
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.2", type: ModelComponent, isStandalone: true, selector: "vl-model", inputs: { errorMessages: "errorMessages", modalItemInfo: "modalItemInfo", selectedLanguageInfo: "selectedLanguageInfo", userInfo: "userInfo", setFormDataHandler: "setFormDataHandler" }, usesOnChanges: true, ngImport: i0, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\n project defines none; it showed the text \"undefined\" -->\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\n - change: number, autocomplete, date and time fields take the toolkit keyboard of their\n attribute type (keyboardTypeFor) instead of only an explicit option.keyboardType\n - change: the date picker docks at the bottom on the devices (model-datepicker-panel--docked);\n the time picker stays attached to its field in a compact form (model-timepicker-panel);\n both report which picker closed (onPickerClose(picker)) -->\n\n<!-- Shuaib 2026-09-24\n - change: the model field switch uses renderFieldType so legacy checkbox fields render as boolean. -->\n\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\n - new: a @contract comment on the widest type coverage in the toolkit, and one on the credit\n card case recording that the rename reached this file first -->\n<!-- Loader Spinner -->\n<div *ngIf=\"isLoading\" class=\"loader-overlay\">\n <div class=\"spinner\"></div>\n</div>\n<div [style]=\"getStyleWithoutClasses(modalItemInfo?.layoutSchema?.style).style\"\n [class]=\"modalItemInfo?.layoutSchema?.style?.classes[0]\">\n <form #dynamicForm=\"ngForm\"\n *ngIf=\"modalItemInfo?.layoutSchema?.formSections?.length > 0\"\n (submit)=\"$event.preventDefault()\">\n <div\n *ngFor=\"let formSection of modalItemInfo?.layoutSchema?.formSections\">\n <div *ngIf=\"formSection?.label?.caption\"\n [class]=\"formSection?.label?.style?.classes[0]\"\n [style]=\"getStyleWithoutClasses(formSection?.label?.style).style\">\n {{formSection.label.caption}}\n </div>\n <div *ngIf=\"formSection?.formFields?.length > 0\"\n [class]=\"formSection?.style?.classes?.[0] || ''\"\n [ngStyle]=\"getSectionBodyStyle(formSection)\">\n <div *ngFor=\"let formField of formSection.formFields\"\n [ngStyle]=\"getFieldWrapperStyle(formField)\"\n [style.display]=\"usesSharedField(formField) && formField.option?.visible === false ? 'none' : null\"\n [class]=\"formField?.style?.classes || ''\">\n\n <!-- @contract decision: the widest type coverage in the toolkit\n reality: every field type except the grid-only quantity renders here.\n When another container drops a type, this is the way out.\n seen: measured 2026-09-19. Note the file is model.component.html while\n the container is called modal in layout files - that costs a\n search every time -->\n <!-- @contract compatibility: checkbox is rendered as boolean for existing projects. -->\n @if (usesSharedField(formField)) {\n @if (sharedProperty(formField); as property) {\n @if (formField.option?.visible !== false) {\n <vl-field-renderer [field]=\"formField\" [property]=\"property\"\n [ngModel]=\"property.value\" (ngModelChange)=\"onSharedFieldChange(formField,$event)\"\n [name]=\"formField.scope.reference\" [disabled]=\"formField.option?.disabled === true\"\n [items]=\"formField.data ?? property.data\" [translations]=\"selectedLanguageInfo?.selectedLanguage || {}\"\n [errorMessages]=\"errorMessages\" [showErrors]=\"sharedErrorsVisible\"\n (itemSelected)=\"autocompSelectEvent($event,formField)\">\n </vl-field-renderer>\n }\n }\n } @else {\n <div [ngSwitch]=\"renderFieldType(\n formField?.scope?.reference &&\n modalItemInfo?.propertySchema?.[\n getScope(formField.scope.reference)\n ]\n ? modalItemInfo.propertySchema[\n getScope(formField.scope.reference)\n ]?.[\n formField.scope.reference.split('/').pop()\n ]?.type\n : 'default'\n )\">\n <!-- Media control -->\n \n\n <!-- credit card control -->\n <!-- @contract decision: the credit card type is spelled creditCard\n reality: this file was renamed on 2026-09-15, form.component.html and\n utility.service.ts followed on 2026-09-19. carditCard is read\n nowhere any more.\n failure: a data object still declaring carditCard renders empty. -->\n <div *ngSwitchCase=\"'creditCard'\"\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\n [class]=\"formField.style?.classes[0]\">\n <div id=\"card-element\">\n <!-- a Stripe element will be inserted here -->\n </div>\n <!-- Used to display form errors -->\n <div id=\"card-errors\"\n class=\"displayError.textContent!=='': 'alert alert-danger mt-1'\">\n </div>\n </div>\n\n <!-- string control -->\n \n\n <!-- number control -->\n \n\n <!-- DateRange control -->\n <div *ngSwitchCase=\"'dateRange'\"\n class=\"form-group mb-3\">\n <span *ngIf=\"formField.option.protected\"\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\n <vl-string [formField]=\"formField\"\n [modalItemInfo]=\"modalItemInfo\">\n </vl-string>\n </span>\n <div *ngIf=\"!formField.option.protected\">\n <div\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\n <label\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{\n formField.label?.value\n ?\n formField.label?.value :\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\n }}</label>\n <nz-range-picker #dateRangeField=\"ngModel\"\n [required]=\"formField.option.required\"\n [name]=\"formField.scope?.reference\"\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"></nz-range-picker>\n </div>\n <!-- Error Message -->\n <div\n *ngIf=\"dateRangeField?.touched && dateRangeField?.invalid\"\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\n <p class=\"text-danger\"\n *ngIf=\"dateRangeField?.errors?.['required']\">\n {{formField.label?.value ?\n formField.label?.value :\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\n +\n ' ' + errorMessages.required}}</p>\n <p class=\"text-danger\"\n *ngIf=\"dateRangeField?.errors?.['email']\">\n {{(formField.label?.value ?\n formField.label?.value :\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\n +\n ' ' + errorMessages.pattern}}</p>\n </div>\n </div>\n </div>\n\n <!-- Date control -->\n \n\n <!-- Time control -->\n \n\n <!-- Autocomplete control -->\n <div *ngSwitchCase=\"'autocomplete'\"\n class=\"form-group mb-3\">\n <span *ngIf=\"formField.option.protected\"\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\n <vl-string [formField]=\"formField\"\n [modalItemInfo]=\"modalItemInfo\">\n </vl-string>\n </span>\n <div *ngIf=\"!formField.option.protected\">\n <div\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\n <vl-label [formField]=\"formField\"\n [modalItemInfo]=\"modalItemInfo\">\n </vl-label>\n\n <span>\n <p-autocomplete\n #autocompleteField=\"ngModel\"\n [required]=\"formField.option.required\"\n [name]=\"formField.scope?.reference\"\n styleClass=\"custom-autocomplete\"\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\n [class]=\"formField?.scope?.style?.classes ? formField?.scope?.style?.classes[0] : ''\"\n [suggestions]=\"matchData\"\n [dropdown]=\"true\"\n [optionLabel]=\"formField?.option?.autocomplete?.response?.label ? formField.option.autocomplete.response.label : 'label'\"\n [placeholder]=\"modalItemInfo.propertySchema | vlPlaceholder: formField.scope?.reference\"\n (completeMethod)=\"autocompChangeEvent($event, formField)\"\n (ngModelChange)=\"autocompFocusOutEvent($event, formField)\"\n [vlCustomKeyboard]=\"keyboardTypeFor(formField)\">\n </p-autocomplete>\n <!-- Error Message -->\n <div\n *ngIf=\"autocompleteField?.touched && autocompleteField?.invalid\"\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\n <p class=\"text-danger\"\n *ngIf=\"autocompleteField?.errors?.['required']\">\n {{setErroMessage(formField,\n errorMessages, \"required\")}}</p>\n <p class=\"text-danger\"\n *ngIf=\"autocompleteField?.errors?.['email']\">\n {{(formField.label?.value ?\n formField.label?.value :\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\n +\n ' ' +\n errorMessages.pattern}}</p>\n </div>\n </span>\n </div>\n </div>\n </div>\n\n <!-- checkBox control -->\n \n\n <!-- dropdown control -->\n <div *ngSwitchCase=\"'dropdown'\" class=\"form-group mb-3\"\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\n [class]=\"formField.style?.classes ? formField.style?.classes[0] : ''\">\n <span *ngIf=\"formField.option?.postfixCaption\">\n <select id=\"dynamic-dropdown\"\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\"\n #dropdownField=\"ngModel\"\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\n [name]=\"formField.scope?.reference\"\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\">\n <option\n *ngFor=\"let item of modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.data\"\n [value]=\"formField?.option?.optionValue ? formField.option.optionValue : 'value'\">\n {{ formField?.option?.optionLabel ?\n formField.option.optionLabel :\n 'label'}}</option>\n </select>\n <label\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\">{{modalItemInfo.propertySchema[getScope(formField.scope)][formField.scope?.reference.split('/').pop()]?.caption\n }}</label>\n </span>\n <div *ngIf=\"!formField.option?.postfixCaption\">\n <div\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\n <label\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\">{{modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\n }}</label>\n\n <nz-select nzShowSearch nzAllowClear\n nzPlaceHolder=\"Select\"\n #dropdownField=\"ngModel\"\n [required]=\"formField.option.required\"\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\"\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\n [name]=\"formField.scope?.reference\"\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\">\n <nz-option\n *ngFor=\"let item of modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.data\"\n [nzLabel]=\"item[formField?.option?.optionLabel ? formField.option.optionLabel : 'label']\"\n [nzValue]=\"item[formField?.option?.optionValue ? formField.option.optionValue : 'value']\"></nz-option>\n </nz-select>\n </div>\n <!-- Error Message -->\n <div\n *ngIf=\"dropdownField?.touched && dropdownField?.invalid\"\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\n <p class=\"text-danger\"\n *ngIf=\"dropdownField?.errors?.['required']\">\n {{formField.label?.value ?\n formField.label?.value :\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\n +\n ' ' + errorMessages.required}}</p>\n <p class=\"text-danger\"\n *ngIf=\"dropdownField?.errors?.['email']\">\n {{(formField.label?.value ?\n formField.label?.value :\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\n +\n ' ' + errorMessages.pattern}}</p>\n </div>\n </div>\n </div>\n\n <!-- button control -->\n <div *ngSwitchCase=\"'default'\">\n <button type=\"button\"\n [ngStyle]=\"getStyleWithoutClasses(formField.style).style\"\n (click)=\"invokeFormBtnClick(formField.option, modalItemInfo.propertySchema, dynamicForm)\"\n class=\"btn btn-primary btn-block\">\n {{ getButtonCaption(formField) }}\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </form>\n</div>\n", styles: [":host ::ng-deep .custom-datepicker .p-datepicker-dropdown{height:100%;border-radius:4px}:host ::ng-deep .custom-datepicker .p-datepicker-input{border-radius:4px}::ng-deep .p-autocomplete-list{background-color:#fff}:host ::ng-deep .custom-autocomplete{display:flex;width:100%}:host ::ng-deep .custom-autocomplete .p-autocomplete-input{flex:1;width:100%;min-width:0;border:none!important;outline:none!important;box-shadow:none!important;background:transparent!important}:host ::ng-deep .custom-autocomplete .p-autocomplete-dropdown{height:100%;border-radius:4px}:host ::ng-deep .custom-autocomplete .p-autocomplete-input:focus,:host ::ng-deep .custom-autocomplete .p-autocomplete-input:focus-visible,:host ::ng-deep .custom-autocomplete .p-inputtext:enabled:focus{border:none!important;outline:none!important;box-shadow:none!important}.responsive-modal-grid{width:100%;min-width:0;box-sizing:border-box}@media screen and (max-width:767px){.responsive-modal-grid{grid-template-columns:repeat(12,minmax(0,1fr))!important;gap:10px!important}.responsive-modal-field{grid-column:1 / -1!important;width:100%!important;max-width:none!important;min-width:0!important}.responsive-modal-action:nth-last-child(2){grid-column:1 / span 6!important}.responsive-modal-action:last-child{grid-column:7 / span 6!important}.responsive-modal-action{width:100%!important;max-width:none!important;min-width:0!important;justify-self:stretch!important;box-sizing:border-box!important}.responsive-modal-action button{display:block!important;width:100%!important;max-width:none!important;min-width:0!important;margin:0!important;box-sizing:border-box!important}}:host{--modal-field-focus-color: var(--p-primary-color, #10b981);--modal-field-focus-shadow: 0 0 0 2px rgba(16, 185, 129, .16)}:host ::ng-deep input:not([type=checkbox]):not(:disabled):focus,:host ::ng-deep textarea:not(:disabled):focus{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .custom-autocomplete:focus-within{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .custom-autocomplete:focus-within .p-autocomplete-input{border:none!important;outline:none!important;box-shadow:none!important}:host ::ng-deep select:not(:disabled):focus{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .ant-select-focused:not(.ant-select-disabled) .ant-select-selector,:host ::ng-deep nz-select:not(.ant-select-disabled):focus-within .ant-select-selector{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .p-select:not(.p-disabled).p-focus,:host ::ng-deep .p-select:not(.p-disabled):focus-within,:host ::ng-deep .p-dropdown:not(.p-disabled).p-focus,:host ::ng-deep .p-dropdown:not(.p-disabled):focus-within{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .custom-datepicker:focus-within .p-datepicker-input{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep input:disabled,:host ::ng-deep textarea:disabled,:host ::ng-deep select:disabled,:host ::ng-deep .p-disabled,:host ::ng-deep .ant-select-disabled .ant-select-selector{box-shadow:none!important}\n"], dependencies: [{ kind: "component", type: FieldRendererComponent, selector: "vl-field-renderer", inputs: ["field", "property", "items", "translations", "errorMessages", "showErrors"], outputs: ["itemSelected", "fieldBlur"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i1$1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: NzSelectModule }, { kind: "component", type: i3.NzOptionComponent, selector: "nz-option", inputs: ["nzTitle", "nzLabel", "nzValue", "nzKey", "nzDisabled", "nzHide", "nzCustomContent"], exportAs: ["nzOption"] }, { kind: "component", type: i3.NzSelectComponent, selector: "nz-select", inputs: ["nzId", "nzSize", "nzStatus", "nzVariant", "nzOptionHeightPx", "nzOptionOverflowSize", "nzDropdownClassName", "nzDropdownMatchSelectWidth", "nzDropdownStyle", "nzNotFoundContent", "nzPlaceHolder", "nzPlacement", "nzMaxTagCount", "nzDropdownRender", "nzCustomTemplate", "nzPrefix", "nzSuffixIcon", "nzClearIcon", "nzRemoveIcon", "nzMenuItemSelectedIcon", "nzTokenSeparators", "nzMaxTagPlaceholder", "nzMaxMultipleCount", "nzMode", "nzFilterOption", "compareWith", "nzAllowClear", "nzShowSearch", "nzLoading", "nzAutoFocus", "nzAutoClearSearchValue", "nzServerSearch", "nzDisabled", "nzOpen", "nzSelectOnTab", "nzBackdrop", "nzOptions", "nzShowArrow"], outputs: ["nzOnSearch", "nzScrollToBottom", "nzOpenChange", "nzBlur", "nzFocus", "nzOnClear"], exportAs: ["nzSelect"] }, { kind: "ngmodule", type: NzDatePickerModule }, { kind: "component", type: i4.NzDatePickerComponent, selector: "nz-date-picker,nz-week-picker,nz-month-picker,nz-quarter-picker,nz-year-picker,nz-range-picker", inputs: ["nzInline", "nzAllowClear", "nzAutoFocus", "nzDisabled", "nzInputReadOnly", "nzOpen", "nzDisabledDate", "nzLocale", "nzPlaceHolder", "nzPopupStyle", "nzDropdownClassName", "nzSize", "nzStatus", "nzFormat", "nzVariant", "nzDateRender", "nzDisabledTime", "nzRenderExtraFooter", "nzShowToday", "nzMode", "nzShowNow", "nzRanges", "nzDefaultPickerValue", "nzSeparator", "nzSuffixIcon", "nzBackdrop", "nzId", "nzPlacement", "nzShowWeekNumber", "nzShowTime"], outputs: ["nzOnPanelChange", "nzOnCalendarChange", "nzOnOk", "nzOnOpenChange"], exportAs: ["nzDatePicker"] }, { kind: "directive", type: i4.NzRangePickerComponent, selector: "nz-range-picker", exportAs: ["nzRangePicker"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i18.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "component", type: Label, selector: "vl-label", inputs: ["formField", "modalItemInfo"] }, { kind: "component", type: String$1, selector: "vl-string", inputs: ["formField", "modalItemInfo"] }, { kind: "directive", type: CustomKeyboardDirective, selector: "[vlCustomKeyboard]", inputs: ["vlCustomKeyboard"] }, { kind: "pipe", type: PlaceholderPipe, name: "vlPlaceholder" }] });
|
|
6157
7219
|
}
|
|
6158
7220
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: ModelComponent, decorators: [{
|
|
6159
7221
|
type: Component,
|
|
6160
|
-
args: [{ standalone: true, selector: 'vl-model', imports: [PlaceholderPipe,
|
|
7222
|
+
args: [{ standalone: true, selector: 'vl-model', imports: [FieldRendererComponent, PlaceholderPipe,
|
|
6161
7223
|
CommonModule,
|
|
6162
7224
|
FormsModule,
|
|
6163
7225
|
NzSelectModule,
|
|
@@ -6169,8 +7231,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
6169
7231
|
String$1,
|
|
6170
7232
|
Textbox,
|
|
6171
7233
|
CustomKeyboardDirective,
|
|
6172
|
-
], template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\r\n project defines none; it showed the text \"undefined\" -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - change: number, autocomplete, date and time fields take the toolkit keyboard of their\r\n attribute type (keyboardTypeFor) instead of only an explicit option.keyboardType\r\n - change: the date picker docks at the bottom on the devices (model-datepicker-panel--docked);\r\n the time picker stays attached to its field in a compact form (model-timepicker-panel);\r\n both report which picker closed (onPickerClose(picker)) -->\r\n\r\n<!-- Shuaib 2026-09-24\r\n - change: the model field switch uses renderFieldType so legacy checkbox fields render as boolean. -->\r\n\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: a @contract comment on the widest type coverage in the toolkit, and one on the credit\r\n card case recording that the rename reached this file first -->\r\n<!-- Loader Spinner -->\r\n<div *ngIf=\"isLoading\" class=\"loader-overlay\">\r\n <div class=\"spinner\"></div>\r\n</div>\r\n<div [style]=\"getStyleWithoutClasses(modalItemInfo?.layoutSchema?.style).style\"\r\n [class]=\"modalItemInfo?.layoutSchema?.style?.classes[0]\">\r\n <form #dynamicForm=\"ngForm\"\r\n *ngIf=\"modalItemInfo?.layoutSchema?.formSections?.length > 0\"\r\n (submit)=\"$event.preventDefault()\">\r\n <div\r\n *ngFor=\"let formSection of modalItemInfo?.layoutSchema?.formSections\">\r\n <div *ngIf=\"formSection?.label?.caption\"\r\n [class]=\"formSection?.label?.style?.classes[0]\"\r\n [style]=\"getStyleWithoutClasses(formSection?.label?.style).style\">\r\n {{formSection.label.caption}}\r\n </div>\r\n <div *ngIf=\"formSection?.formFields?.length > 0\"\r\n [class]=\"formSection?.style?.classes?.[0] || ''\"\r\n [ngStyle]=\"getSectionBodyStyle(formSection)\">\r\n <div *ngFor=\"let formField of formSection.formFields\"\r\n [ngStyle]=\"getFieldWrapperStyle(formField)\"\r\n [class]=\"formField?.style?.classes || ''\">\r\n\r\n <!-- @contract decision: the widest type coverage in the toolkit\r\n reality: every field type except the grid-only quantity renders here.\r\n When another container drops a type, this is the way out.\r\n seen: measured 2026-09-19. Note the file is model.component.html while\r\n the container is called modal in layout files - that costs a\r\n search every time -->\r\n <!-- @contract compatibility: checkbox is rendered as boolean for existing projects. -->\r\n <div [ngSwitch]=\"renderFieldType(\r\n formField?.scope?.reference &&\r\n modalItemInfo?.propertySchema?.[\r\n getScope(formField.scope.reference)\r\n ]\r\n ? modalItemInfo.propertySchema[\r\n getScope(formField.scope.reference)\r\n ]?.[\r\n formField.scope.reference.split('/').pop()\r\n ]?.type\r\n : 'default'\r\n )\">\r\n <!-- image control -->\r\n <div *ngSwitchCase=\"'image'\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\">\r\n <img [src]=\"formField.option.src\" [alt]=\"'img'\" />\r\n </div>\r\n\r\n <!-- credit card control -->\r\n <!-- @contract decision: the credit card type is spelled creditCard\r\n reality: this file was renamed on 2026-09-15, form.component.html and\r\n utility.service.ts followed on 2026-09-19. carditCard is read\r\n nowhere any more.\r\n failure: a data object still declaring carditCard renders empty. -->\r\n <div *ngSwitchCase=\"'creditCard'\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [class]=\"formField.style?.classes[0]\">\r\n <div id=\"card-element\">\r\n <!-- a Stripe element will be inserted here -->\r\n </div>\r\n <!-- Used to display form errors -->\r\n <div id=\"card-errors\"\r\n class=\"displayError.textContent!=='': 'alert alert-danger mt-1'\">\r\n </div>\r\n </div>\r\n\r\n <!-- string control -->\r\n <div *ngSwitchCase=\"'string'\" class=\"form-group mb-3\">\r\n @if(formField.option && formField.option.control &&\r\n !formField.option.protected){\r\n @switch (formField.option.control) {\r\n @case ('textInput') {\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <vl-label [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-label>\r\n <vl-textbox [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-textbox>\r\n </div>\r\n <!-- Error Message -->\r\n <!-- <div *ngIf=\"inputField?.touched && inputField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\" *ngIf=\"inputField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages, \"required\")}}</p>\r\n <p class=\"text-danger\" *ngIf=\"inputField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div> -->\r\n }\r\n\r\n @case ('password') {\r\n <span>Switch not supported yet</span>\r\n\r\n }\r\n @case ('combobox') {\r\n <span>Combobox not supported yet</span>\r\n }\r\n @case('textArea'){\r\n <span>TextArea not supported yet</span>\r\n }\r\n @case ('switch') {\r\n <span>Switch not supported yet</span>\r\n }\r\n\r\n @case('radiobutton') {\r\n <span>Radiobutton not supported yet</span>\r\n }\r\n\r\n @case ('dropdown') {\r\n <span *ngIf=\"formField.option?.postfixCaption\">\r\n <select id=\"dynamic-dropdown\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\"\r\n #dropdownField=\"ngModel\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [name]=\"formField.scope?.reference\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\">\r\n <option\r\n *ngFor=\"let item of modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.data\"\r\n [value]=\"formField?.option?.optionValue ? formField.option.optionValue : 'value'\">\r\n {{ formField?.option?.optionLabel ?\r\n formField.option.optionLabel :\r\n 'label'}}</option>\r\n </select>\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\">{{modalItemInfo.propertySchema[getScope(formField.scope)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n </span>\r\n <div *ngIf=\"!formField.option?.postfixCaption\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\">{{modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n\r\n <nz-select nzShowSearch nzAllowClear\r\n nzPlaceHolder=\"Select\"\r\n #dropdownField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [name]=\"formField.scope?.reference\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\">\r\n <nz-option\r\n *ngFor=\"let item of modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.data\"\r\n [nzLabel]=\"item[formField?.option?.optionLabel ? formField.option.optionLabel : 'label']\"\r\n [nzValue]=\"item[formField?.option?.optionValue ? formField.option.optionValue : 'value']\"></nz-option>\r\n </nz-select>\r\n </div>\r\n <!-- Error Message -->\r\n <div\r\n *ngIf=\"dropdownField?.touched && dropdownField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dropdownField?.errors?.['required']\">\r\n {{formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n +\r\n ' ' + errorMessages.required}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dropdownField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </div>\r\n }\r\n\r\n @case ('autocomplete') {\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <vl-label [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-label>\r\n\r\n <span>\r\n <p-autocomplete\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [class]=\"formField?.scope?.style?.classes ? formField?.scope?.style?.classes[0] : ''\"\r\n [suggestions]=\"matchData\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"formField?.option?.autocomplete?.response?.label ? formField.option.autocomplete.response.label : 'label'\"\r\n [placeholder]=\"modalItemInfo.propertySchema | vlPlaceholder: formField.scope?.reference\"\r\n (completeMethod)=\"autocompChangeEvent($event, formField)\"\r\n (ngModelChange)=\"autocompFocusOutEvent($event, formField)\"\r\n [vlCustomKeyboard]=\"keyboardTypeFor(formField)\">\r\n </p-autocomplete>\r\n <!-- Error Message -->\r\n <div\r\n *ngIf=\"autocompleteField?.touched && autocompleteField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['required']\">\r\n {{setErroMessage(formField,\r\n errorMessages, \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' +\r\n errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n </div>\r\n }\r\n }\r\n }@else{\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <vl-label [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-label>\r\n <vl-textbox [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-textbox>\r\n </div>\r\n <!-- Error Message -->\r\n <!-- <div *ngIf=\"inputField?.touched && inputField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\" *ngIf=\"inputField?.errors?.['required']\">\r\n {{setErroMessage(formField, errorMessages, \"required\")}}</p>\r\n <p class=\"text-danger\" *ngIf=\"inputField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div> -->\r\n </div>\r\n }\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-string>\r\n </span>\r\n\r\n </div>\r\n\r\n <!-- number control -->\r\n <div *ngSwitchCase=\"'number'\" class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <h5>{{\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }} {{\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.value\r\n |\r\n currency:formField.option.currency :true:\r\n formField.option?.decimalPoint}}\r\n </h5>\r\n </span>\r\n <span *ngIf=\"!formField.option.protected\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{\r\n formField.label?.value\r\n ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <input type=\"number\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\"\r\n [vlCustomKeyboard]=\"keyboardTypeFor(formField)\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\r\n [name]=\"formField.scope?.reference\"\r\n [placeholder]=\"modalItemInfo.propertySchema | vlPlaceholder: formField.scope?.reference\" />\r\n </div>\r\n </span>\r\n </div>\r\n\r\n <!-- DateRange control -->\r\n <div *ngSwitchCase=\"'dateRange'\"\r\n class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{\r\n formField.label?.value\r\n ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n <nz-range-picker #dateRangeField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"></nz-range-picker>\r\n </div>\r\n <!-- Error Message -->\r\n <div\r\n *ngIf=\"dateRangeField?.touched && dateRangeField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateRangeField?.errors?.['required']\">\r\n {{formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n +\r\n ' ' + errorMessages.required}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateRangeField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Date control -->\r\n <div *ngSwitchCase=\"'date'\" class=\"form-group mb-3\">\r\n <span\r\n *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string\r\n [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-string>\r\n </span>\r\n\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n {{ getLabel(formField) }}\r\n </label>\r\n\r\n <span>\r\n <div class=\"picker-field\">\r\n <p-datepicker\r\n #datePicker\r\n #dateField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n [disabled]=\"formField?.option?.disabled\"\r\n [showIcon]=\"false\"\r\n [showOnFocus]=\"false\"\r\n appendTo=\"body\"\r\n [autoZIndex]=\"true\"\r\n [baseZIndex]=\"10000\"\r\n [panelStyleClass]=\"picker.datePanelClass\"\r\n [motionOptions]=\"picker.dockedMotion\"\r\n [vlCustomKeyboard]=\"keyboardTypeFor(formField)\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, datePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"picker.onShown()\"\r\n (onClose)=\"picker.onClose(datePicker)\"\r\n (ngModelChange)=\"dateChangeEvent(formField, $event)\"\r\n (onInput)=\"onDateTyping($event)\"\r\n [dateFormat]=\"\r\n modalItemInfo.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ]?.format || 'yy-mm-dd'\r\n \"\r\n [(ngModel)]=\"\r\n modalItemInfo.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [class]=\"formField?.scope?.style?.classes?.[0] || ''\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\">\r\n </p-datepicker>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"picker-field__icon\"\r\n aria-label=\"Open calendar\"\r\n [disabled]=\"formField?.option?.disabled\"\r\n (pointerdown)=\"picker.openFromIcon($event, datePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-calendar\"></i>\r\n </button>\r\n </div>\r\n\r\n <div\r\n *ngIf=\"dateField?.touched && dateField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateField?.errors?.['required']\">\r\n {{ setErroMessage(formField,\r\n errorMessages, 'required') }}\r\n </p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dateField?.errors?.['email']\">\r\n {{\r\n (formField.label?.value\r\n ? formField.label?.value\r\n : modalItemInfo.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption)\r\n + ' ' + errorMessages.pattern\r\n }}\r\n </p>\r\n </div>\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Time control -->\r\n <div *ngSwitchCase=\"'time'\" class=\"form-group mb-3\">\r\n <span\r\n *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string\r\n [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-string>\r\n </span>\r\n\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n {{ getLabel(formField) }}\r\n </label>\r\n\r\n <span>\r\n <div class=\"picker-field\">\r\n <p-datepicker\r\n #timePicker\r\n #timeField=\"ngModel\"\r\n styleClass=\"custom-datepicker\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n [timeOnly]=\"true\"\r\n hourFormat=\"24\"\r\n [disabled]=\"formField?.option?.disabled\"\r\n [showIcon]=\"false\"\r\n [showOnFocus]=\"false\"\r\n appendTo=\"body\"\r\n [autoZIndex]=\"true\"\r\n [baseZIndex]=\"10000\"\r\n [panelStyleClass]=\"picker.timePanelClass\"\r\n [vlCustomKeyboard]=\"keyboardTypeFor(formField)\"\r\n (pointerdown)=\"picker.onFieldPointerDown($event, timePicker)\"\r\n (onFocus)=\"picker.onFieldFocus($event)\"\r\n (onShow)=\"onTimePanelShow(formField)\"\r\n (onClose)=\"picker.onClose(timePicker)\"\r\n (onInput)=\"onTimeTyping($event)\"\r\n (ngModelChange)=\"timeChangeEvent(formField, $event)\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [(ngModel)]=\"\r\n modalItemInfo.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ].value\r\n \"\r\n [class]=\"formField?.scope?.style?.classes?.[0] || ''\">\r\n </p-datepicker>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"picker-field__icon\"\r\n aria-label=\"Open time picker\"\r\n [disabled]=\"formField?.option?.disabled\"\r\n (pointerdown)=\"picker.openFromIcon($event, timePicker)\"\r\n (click)=\"picker.blockIconClick($event)\">\r\n <i class=\"pi pi-clock\"></i>\r\n </button>\r\n </div>\r\n\r\n <div\r\n *ngIf=\"timeField?.touched && timeField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"timeField?.errors?.['required']\">\r\n {{ setErroMessage(formField,\r\n errorMessages, 'required') }}\r\n </p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"timeField?.errors?.['email']\">\r\n {{\r\n (formField.label?.value\r\n ? formField.label?.value\r\n : modalItemInfo.propertySchema[\r\n getScope(formField.scope?.reference)\r\n ][\r\n formField.scope?.reference.split('/').pop()\r\n ]?.caption)\r\n + ' ' + errorMessages.pattern\r\n }}\r\n </p>\r\n </div>\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Autocomplete control -->\r\n <div *ngSwitchCase=\"'autocomplete'\"\r\n class=\"form-group mb-3\">\r\n <span *ngIf=\"formField.option.protected\"\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\r\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\r\n <vl-string [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-string>\r\n </span>\r\n <div *ngIf=\"!formField.option.protected\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <vl-label [formField]=\"formField\"\r\n [modalItemInfo]=\"modalItemInfo\">\r\n </vl-label>\r\n\r\n <span>\r\n <p-autocomplete\r\n #autocompleteField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [name]=\"formField.scope?.reference\"\r\n styleClass=\"custom-autocomplete\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\r\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\r\n [class]=\"formField?.scope?.style?.classes ? formField?.scope?.style?.classes[0] : ''\"\r\n [suggestions]=\"matchData\"\r\n [dropdown]=\"true\"\r\n [optionLabel]=\"formField?.option?.autocomplete?.response?.label ? formField.option.autocomplete.response.label : 'label'\"\r\n [placeholder]=\"modalItemInfo.propertySchema | vlPlaceholder: formField.scope?.reference\"\r\n (completeMethod)=\"autocompChangeEvent($event, formField)\"\r\n (ngModelChange)=\"autocompFocusOutEvent($event, formField)\"\r\n [vlCustomKeyboard]=\"keyboardTypeFor(formField)\">\r\n </p-autocomplete>\r\n <!-- Error Message -->\r\n <div\r\n *ngIf=\"autocompleteField?.touched && autocompleteField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['required']\">\r\n {{setErroMessage(formField,\r\n errorMessages, \"required\")}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"autocompleteField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' +\r\n errorMessages.pattern}}</p>\r\n </div>\r\n </span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- checkBox control -->\r\n <div *ngSwitchCase=\"'boolean'\" class=\"form-group mb-3\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0] : ''\">\r\n <span *ngIf=\"formField.option?.postfixCaption\">\r\n <label class=\"form-check-label\"><input\r\n type=\"checkbox\" class=\"form-check-input\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\r\n [name]=\"formField.scope?.reference\"\r\n (ngModelChange)=\"invokeSetformData()\" />{{\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n </span>\r\n <div *ngIf=\"!formField.option?.postfixCaption\"\r\n [class]=\"formField?.layout == 'horizontal' ? 'horizontal-layout' : 'row'\">\r\n <label class=\"form-check-label\">{{\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}\r\n <input type=\"checkbox\"\r\n class=\"form-check-input\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\r\n [name]=\"formField.scope?.reference\" /></label>\r\n </div>\r\n </div>\r\n\r\n <!-- dropdown control -->\r\n <div *ngSwitchCase=\"'dropdown'\" class=\"form-group mb-3\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0] : ''\">\r\n <span *ngIf=\"formField.option?.postfixCaption\">\r\n <select id=\"dynamic-dropdown\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\"\r\n #dropdownField=\"ngModel\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [name]=\"formField.scope?.reference\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\">\r\n <option\r\n *ngFor=\"let item of modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.data\"\r\n [value]=\"formField?.option?.optionValue ? formField.option.optionValue : 'value'\">\r\n {{ formField?.option?.optionLabel ?\r\n formField.option.optionLabel :\r\n 'label'}}</option>\r\n </select>\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\">{{modalItemInfo.propertySchema[getScope(formField.scope)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n </span>\r\n <div *ngIf=\"!formField.option?.postfixCaption\">\r\n <div\r\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\r\n <label\r\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\">{{modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n }}</label>\r\n\r\n <nz-select nzShowSearch nzAllowClear\r\n nzPlaceHolder=\"Select\"\r\n #dropdownField=\"ngModel\"\r\n [required]=\"formField.option.required\"\r\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\"\r\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\r\n [name]=\"formField.scope?.reference\"\r\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\">\r\n <nz-option\r\n *ngFor=\"let item of modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.data\"\r\n [nzLabel]=\"item[formField?.option?.optionLabel ? formField.option.optionLabel : 'label']\"\r\n [nzValue]=\"item[formField?.option?.optionValue ? formField.option.optionValue : 'value']\"></nz-option>\r\n </nz-select>\r\n </div>\r\n <!-- Error Message -->\r\n <div\r\n *ngIf=\"dropdownField?.touched && dropdownField?.invalid\"\r\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\r\n <p class=\"text-danger\"\r\n *ngIf=\"dropdownField?.errors?.['required']\">\r\n {{formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\r\n +\r\n ' ' + errorMessages.required}}</p>\r\n <p class=\"text-danger\"\r\n *ngIf=\"dropdownField?.errors?.['email']\">\r\n {{(formField.label?.value ?\r\n formField.label?.value :\r\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\r\n +\r\n ' ' + errorMessages.pattern}}</p>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- button control -->\r\n <div *ngSwitchCase=\"'default'\">\r\n <button type=\"button\"\r\n [ngStyle]=\"getStyleWithoutClasses(formField.style).style\"\r\n (click)=\"invokeFormBtnClick(formField.option, modalItemInfo.propertySchema, dynamicForm)\"\r\n class=\"btn btn-primary btn-block\">\r\n {{ getButtonCaption(formField) }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n</div>\r\n", styles: [":host ::ng-deep .custom-datepicker .p-datepicker-dropdown{height:100%;border-radius:4px}:host ::ng-deep .custom-datepicker .p-datepicker-input{border-radius:4px}::ng-deep .p-autocomplete-list{background-color:#fff}:host ::ng-deep .custom-autocomplete{display:flex;width:100%}:host ::ng-deep .custom-autocomplete .p-autocomplete-input{flex:1;width:100%;min-width:0;border:none!important;outline:none!important;box-shadow:none!important;background:transparent!important}:host ::ng-deep .custom-autocomplete .p-autocomplete-dropdown{height:100%;border-radius:4px}:host ::ng-deep .custom-autocomplete .p-autocomplete-input:focus,:host ::ng-deep .custom-autocomplete .p-autocomplete-input:focus-visible,:host ::ng-deep .custom-autocomplete .p-inputtext:enabled:focus{border:none!important;outline:none!important;box-shadow:none!important}.responsive-modal-grid{width:100%;min-width:0;box-sizing:border-box}@media screen and (max-width:767px){.responsive-modal-grid{grid-template-columns:repeat(12,minmax(0,1fr))!important;gap:10px!important}.responsive-modal-field{grid-column:1 / -1!important;width:100%!important;max-width:none!important;min-width:0!important}.responsive-modal-action:nth-last-child(2){grid-column:1 / span 6!important}.responsive-modal-action:last-child{grid-column:7 / span 6!important}.responsive-modal-action{width:100%!important;max-width:none!important;min-width:0!important;justify-self:stretch!important;box-sizing:border-box!important}.responsive-modal-action button{display:block!important;width:100%!important;max-width:none!important;min-width:0!important;margin:0!important;box-sizing:border-box!important}}:host{--modal-field-focus-color: var(--p-primary-color, #10b981);--modal-field-focus-shadow: 0 0 0 2px rgba(16, 185, 129, .16)}:host ::ng-deep input:not([type=checkbox]):not(:disabled):focus,:host ::ng-deep textarea:not(:disabled):focus{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .custom-autocomplete:focus-within{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .custom-autocomplete:focus-within .p-autocomplete-input{border:none!important;outline:none!important;box-shadow:none!important}:host ::ng-deep select:not(:disabled):focus{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .ant-select-focused:not(.ant-select-disabled) .ant-select-selector,:host ::ng-deep nz-select:not(.ant-select-disabled):focus-within .ant-select-selector{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .p-select:not(.p-disabled).p-focus,:host ::ng-deep .p-select:not(.p-disabled):focus-within,:host ::ng-deep .p-dropdown:not(.p-disabled).p-focus,:host ::ng-deep .p-dropdown:not(.p-disabled):focus-within{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .custom-datepicker:focus-within .p-datepicker-input{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep input:disabled,:host ::ng-deep textarea:disabled,:host ::ng-deep select:disabled,:host ::ng-deep .p-disabled,:host ::ng-deep .ant-select-disabled .ant-select-selector{box-shadow:none!important}\n"] }]
|
|
6173
|
-
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1$
|
|
7234
|
+
], template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\n - fix: the placeholder comes from vlPlaceholder (pipes/placeholder.pipe.ts) - empty where the\n project defines none; it showed the text \"undefined\" -->\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\n - change: number, autocomplete, date and time fields take the toolkit keyboard of their\n attribute type (keyboardTypeFor) instead of only an explicit option.keyboardType\n - change: the date picker docks at the bottom on the devices (model-datepicker-panel--docked);\n the time picker stays attached to its field in a compact form (model-timepicker-panel);\n both report which picker closed (onPickerClose(picker)) -->\n\n<!-- Shuaib 2026-09-24\n - change: the model field switch uses renderFieldType so legacy checkbox fields render as boolean. -->\n\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\n - new: a @contract comment on the widest type coverage in the toolkit, and one on the credit\n card case recording that the rename reached this file first -->\n<!-- Loader Spinner -->\n<div *ngIf=\"isLoading\" class=\"loader-overlay\">\n <div class=\"spinner\"></div>\n</div>\n<div [style]=\"getStyleWithoutClasses(modalItemInfo?.layoutSchema?.style).style\"\n [class]=\"modalItemInfo?.layoutSchema?.style?.classes[0]\">\n <form #dynamicForm=\"ngForm\"\n *ngIf=\"modalItemInfo?.layoutSchema?.formSections?.length > 0\"\n (submit)=\"$event.preventDefault()\">\n <div\n *ngFor=\"let formSection of modalItemInfo?.layoutSchema?.formSections\">\n <div *ngIf=\"formSection?.label?.caption\"\n [class]=\"formSection?.label?.style?.classes[0]\"\n [style]=\"getStyleWithoutClasses(formSection?.label?.style).style\">\n {{formSection.label.caption}}\n </div>\n <div *ngIf=\"formSection?.formFields?.length > 0\"\n [class]=\"formSection?.style?.classes?.[0] || ''\"\n [ngStyle]=\"getSectionBodyStyle(formSection)\">\n <div *ngFor=\"let formField of formSection.formFields\"\n [ngStyle]=\"getFieldWrapperStyle(formField)\"\n [style.display]=\"usesSharedField(formField) && formField.option?.visible === false ? 'none' : null\"\n [class]=\"formField?.style?.classes || ''\">\n\n <!-- @contract decision: the widest type coverage in the toolkit\n reality: every field type except the grid-only quantity renders here.\n When another container drops a type, this is the way out.\n seen: measured 2026-09-19. Note the file is model.component.html while\n the container is called modal in layout files - that costs a\n search every time -->\n <!-- @contract compatibility: checkbox is rendered as boolean for existing projects. -->\n @if (usesSharedField(formField)) {\n @if (sharedProperty(formField); as property) {\n @if (formField.option?.visible !== false) {\n <vl-field-renderer [field]=\"formField\" [property]=\"property\"\n [ngModel]=\"property.value\" (ngModelChange)=\"onSharedFieldChange(formField,$event)\"\n [name]=\"formField.scope.reference\" [disabled]=\"formField.option?.disabled === true\"\n [items]=\"formField.data ?? property.data\" [translations]=\"selectedLanguageInfo?.selectedLanguage || {}\"\n [errorMessages]=\"errorMessages\" [showErrors]=\"sharedErrorsVisible\"\n (itemSelected)=\"autocompSelectEvent($event,formField)\">\n </vl-field-renderer>\n }\n }\n } @else {\n <div [ngSwitch]=\"renderFieldType(\n formField?.scope?.reference &&\n modalItemInfo?.propertySchema?.[\n getScope(formField.scope.reference)\n ]\n ? modalItemInfo.propertySchema[\n getScope(formField.scope.reference)\n ]?.[\n formField.scope.reference.split('/').pop()\n ]?.type\n : 'default'\n )\">\n <!-- Media control -->\n \n\n <!-- credit card control -->\n <!-- @contract decision: the credit card type is spelled creditCard\n reality: this file was renamed on 2026-09-15, form.component.html and\n utility.service.ts followed on 2026-09-19. carditCard is read\n nowhere any more.\n failure: a data object still declaring carditCard renders empty. -->\n <div *ngSwitchCase=\"'creditCard'\"\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\n [class]=\"formField.style?.classes[0]\">\n <div id=\"card-element\">\n <!-- a Stripe element will be inserted here -->\n </div>\n <!-- Used to display form errors -->\n <div id=\"card-errors\"\n class=\"displayError.textContent!=='': 'alert alert-danger mt-1'\">\n </div>\n </div>\n\n <!-- string control -->\n \n\n <!-- number control -->\n \n\n <!-- DateRange control -->\n <div *ngSwitchCase=\"'dateRange'\"\n class=\"form-group mb-3\">\n <span *ngIf=\"formField.option.protected\"\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\n <vl-string [formField]=\"formField\"\n [modalItemInfo]=\"modalItemInfo\">\n </vl-string>\n </span>\n <div *ngIf=\"!formField.option.protected\">\n <div\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\n <label\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">{{\n formField.label?.value\n ?\n formField.label?.value :\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\n }}</label>\n <nz-range-picker #dateRangeField=\"ngModel\"\n [required]=\"formField.option.required\"\n [name]=\"formField.scope?.reference\"\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"></nz-range-picker>\n </div>\n <!-- Error Message -->\n <div\n *ngIf=\"dateRangeField?.touched && dateRangeField?.invalid\"\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\n <p class=\"text-danger\"\n *ngIf=\"dateRangeField?.errors?.['required']\">\n {{formField.label?.value ?\n formField.label?.value :\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\n +\n ' ' + errorMessages.required}}</p>\n <p class=\"text-danger\"\n *ngIf=\"dateRangeField?.errors?.['email']\">\n {{(formField.label?.value ?\n formField.label?.value :\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\n +\n ' ' + errorMessages.pattern}}</p>\n </div>\n </div>\n </div>\n\n <!-- Date control -->\n \n\n <!-- Time control -->\n \n\n <!-- Autocomplete control -->\n <div *ngSwitchCase=\"'autocomplete'\"\n class=\"form-group mb-3\">\n <span *ngIf=\"formField.option.protected\"\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\"\n [class]=\"formField?.label?.style.classes ? formField?.label?.style?.classes[0] : ''\">\n <vl-string [formField]=\"formField\"\n [modalItemInfo]=\"modalItemInfo\">\n </vl-string>\n </span>\n <div *ngIf=\"!formField.option.protected\">\n <div\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\n <vl-label [formField]=\"formField\"\n [modalItemInfo]=\"modalItemInfo\">\n </vl-label>\n\n <span>\n <p-autocomplete\n #autocompleteField=\"ngModel\"\n [required]=\"formField.option.required\"\n [name]=\"formField.scope?.reference\"\n styleClass=\"custom-autocomplete\"\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\"\n [style]=\"getStyleWithoutClasses(formField?.scope?.style).style\"\n [class]=\"formField?.scope?.style?.classes ? formField?.scope?.style?.classes[0] : ''\"\n [suggestions]=\"matchData\"\n [dropdown]=\"true\"\n [optionLabel]=\"formField?.option?.autocomplete?.response?.label ? formField.option.autocomplete.response.label : 'label'\"\n [placeholder]=\"modalItemInfo.propertySchema | vlPlaceholder: formField.scope?.reference\"\n (completeMethod)=\"autocompChangeEvent($event, formField)\"\n (ngModelChange)=\"autocompFocusOutEvent($event, formField)\"\n [vlCustomKeyboard]=\"keyboardTypeFor(formField)\">\n </p-autocomplete>\n <!-- Error Message -->\n <div\n *ngIf=\"autocompleteField?.touched && autocompleteField?.invalid\"\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\n <p class=\"text-danger\"\n *ngIf=\"autocompleteField?.errors?.['required']\">\n {{setErroMessage(formField,\n errorMessages, \"required\")}}</p>\n <p class=\"text-danger\"\n *ngIf=\"autocompleteField?.errors?.['email']\">\n {{(formField.label?.value ?\n formField.label?.value :\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\n +\n ' ' +\n errorMessages.pattern}}</p>\n </div>\n </span>\n </div>\n </div>\n </div>\n\n <!-- checkBox control -->\n \n\n <!-- dropdown control -->\n <div *ngSwitchCase=\"'dropdown'\" class=\"form-group mb-3\"\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\n [class]=\"formField.style?.classes ? formField.style?.classes[0] : ''\">\n <span *ngIf=\"formField.option?.postfixCaption\">\n <select id=\"dynamic-dropdown\"\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\"\n #dropdownField=\"ngModel\"\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\n [name]=\"formField.scope?.reference\"\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\">\n <option\n *ngFor=\"let item of modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.data\"\n [value]=\"formField?.option?.optionValue ? formField.option.optionValue : 'value'\">\n {{ formField?.option?.optionLabel ?\n formField.option.optionLabel :\n 'label'}}</option>\n </select>\n <label\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\">{{modalItemInfo.propertySchema[getScope(formField.scope)][formField.scope?.reference.split('/').pop()]?.caption\n }}</label>\n </span>\n <div *ngIf=\"!formField.option?.postfixCaption\">\n <div\n [ngStyle]=\"getFieldInnerLayoutStyle(formField)\">\n <label\n [style]=\"getStyleWithoutClasses(formField?.label?.style).style\">{{modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\n }}</label>\n\n <nz-select nzShowSearch nzAllowClear\n nzPlaceHolder=\"Select\"\n #dropdownField=\"ngModel\"\n [required]=\"formField.option.required\"\n [class]=\"formField.style?.classes ? formField.style?.classes[0]: ''\"\n [style]=\"getStyleWithoutClasses(formField?.style).style\"\n [name]=\"formField.scope?.reference\"\n [(ngModel)]=\"modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()].value\">\n <nz-option\n *ngFor=\"let item of modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.data\"\n [nzLabel]=\"item[formField?.option?.optionLabel ? formField.option.optionLabel : 'label']\"\n [nzValue]=\"item[formField?.option?.optionValue ? formField.option.optionValue : 'value']\"></nz-option>\n </nz-select>\n </div>\n <!-- Error Message -->\n <div\n *ngIf=\"dropdownField?.touched && dropdownField?.invalid\"\n [style]=\"getStyleWithoutClasses(formField?.validation?.style).style\">\n <p class=\"text-danger\"\n *ngIf=\"dropdownField?.errors?.['required']\">\n {{formField.label?.value ?\n formField.label?.value :\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption\n +\n ' ' + errorMessages.required}}</p>\n <p class=\"text-danger\"\n *ngIf=\"dropdownField?.errors?.['email']\">\n {{(formField.label?.value ?\n formField.label?.value :\n modalItemInfo.propertySchema[getScope(formField.scope?.reference)][formField.scope?.reference.split('/').pop()]?.caption)\n +\n ' ' + errorMessages.pattern}}</p>\n </div>\n </div>\n </div>\n\n <!-- button control -->\n <div *ngSwitchCase=\"'default'\">\n <button type=\"button\"\n [ngStyle]=\"getStyleWithoutClasses(formField.style).style\"\n (click)=\"invokeFormBtnClick(formField.option, modalItemInfo.propertySchema, dynamicForm)\"\n class=\"btn btn-primary btn-block\">\n {{ getButtonCaption(formField) }}\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </form>\n</div>\n", styles: [":host ::ng-deep .custom-datepicker .p-datepicker-dropdown{height:100%;border-radius:4px}:host ::ng-deep .custom-datepicker .p-datepicker-input{border-radius:4px}::ng-deep .p-autocomplete-list{background-color:#fff}:host ::ng-deep .custom-autocomplete{display:flex;width:100%}:host ::ng-deep .custom-autocomplete .p-autocomplete-input{flex:1;width:100%;min-width:0;border:none!important;outline:none!important;box-shadow:none!important;background:transparent!important}:host ::ng-deep .custom-autocomplete .p-autocomplete-dropdown{height:100%;border-radius:4px}:host ::ng-deep .custom-autocomplete .p-autocomplete-input:focus,:host ::ng-deep .custom-autocomplete .p-autocomplete-input:focus-visible,:host ::ng-deep .custom-autocomplete .p-inputtext:enabled:focus{border:none!important;outline:none!important;box-shadow:none!important}.responsive-modal-grid{width:100%;min-width:0;box-sizing:border-box}@media screen and (max-width:767px){.responsive-modal-grid{grid-template-columns:repeat(12,minmax(0,1fr))!important;gap:10px!important}.responsive-modal-field{grid-column:1 / -1!important;width:100%!important;max-width:none!important;min-width:0!important}.responsive-modal-action:nth-last-child(2){grid-column:1 / span 6!important}.responsive-modal-action:last-child{grid-column:7 / span 6!important}.responsive-modal-action{width:100%!important;max-width:none!important;min-width:0!important;justify-self:stretch!important;box-sizing:border-box!important}.responsive-modal-action button{display:block!important;width:100%!important;max-width:none!important;min-width:0!important;margin:0!important;box-sizing:border-box!important}}:host{--modal-field-focus-color: var(--p-primary-color, #10b981);--modal-field-focus-shadow: 0 0 0 2px rgba(16, 185, 129, .16)}:host ::ng-deep input:not([type=checkbox]):not(:disabled):focus,:host ::ng-deep textarea:not(:disabled):focus{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .custom-autocomplete:focus-within{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .custom-autocomplete:focus-within .p-autocomplete-input{border:none!important;outline:none!important;box-shadow:none!important}:host ::ng-deep select:not(:disabled):focus{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .ant-select-focused:not(.ant-select-disabled) .ant-select-selector,:host ::ng-deep nz-select:not(.ant-select-disabled):focus-within .ant-select-selector{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .p-select:not(.p-disabled).p-focus,:host ::ng-deep .p-select:not(.p-disabled):focus-within,:host ::ng-deep .p-dropdown:not(.p-disabled).p-focus,:host ::ng-deep .p-dropdown:not(.p-disabled):focus-within{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep .custom-datepicker:focus-within .p-datepicker-input{border-color:var(--modal-field-focus-color)!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow)!important}:host ::ng-deep input:disabled,:host ::ng-deep textarea:disabled,:host ::ng-deep select:disabled,:host ::ng-deep .p-disabled,:host ::ng-deep .ant-select-disabled .ant-select-selector{box-shadow:none!important}\n"] }]
|
|
7235
|
+
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1$4.DynamicDialogRef }, { type: i1$4.DynamicDialogConfig }, { type: UtilityService }], propDecorators: { errorMessages: [{
|
|
6174
7236
|
type: Input
|
|
6175
7237
|
}], modalItemInfo: [{
|
|
6176
7238
|
type: Input
|
|
@@ -6299,6 +7361,228 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
6299
7361
|
args: [{ selector: 'vl-time', standalone: true, template: "<p>time works!</p>\r\n" }]
|
|
6300
7362
|
}] });
|
|
6301
7363
|
|
|
7364
|
+
// Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-14
|
|
7365
|
+
// - new: one OpenID Connect flow for the devices, used by Keycloak and Okta alike. Both are OIDC with
|
|
7366
|
+
// PKCE; only issuer, client id and redirect differ, and the endpoints come from the issuer's
|
|
7367
|
+
// discovery document. Before, MobileKeycloakService and MobileOktaService each had their own
|
|
7368
|
+
// login, refresh and logout, and the logout behaved differently (Okta: white page, browser window)
|
|
7369
|
+
/** Set by signOut: the next login asks for the password instead of reusing the provider session. */
|
|
7370
|
+
const PROMPT_LOGIN_KEY = 'vl-oidc-prompt-login';
|
|
7371
|
+
/** Set by signOut when the project requires a login: the restarted app shows its loader (app.ts). */
|
|
7372
|
+
const RELOGIN_KEY = 'vl-relogin';
|
|
7373
|
+
class MobileOidcService {
|
|
7374
|
+
discoveries = new Map();
|
|
7375
|
+
/** The issuer's discovery document, fetched once per issuer. */
|
|
7376
|
+
discover(issuer) {
|
|
7377
|
+
const key = issuer.replace(/\/+$/, '');
|
|
7378
|
+
if (!this.discoveries.has(key)) {
|
|
7379
|
+
const request = stagedRequest(signal => fetch(`${key}/.well-known/openid-configuration`, { signal }))
|
|
7380
|
+
.then(response => {
|
|
7381
|
+
if (!response.ok) {
|
|
7382
|
+
throw new Error(`OIDC discovery failed (${response.status})`);
|
|
7383
|
+
}
|
|
7384
|
+
return response.json();
|
|
7385
|
+
});
|
|
7386
|
+
// A failed discovery must not stay cached.
|
|
7387
|
+
request.catch(() => this.discoveries.delete(key));
|
|
7388
|
+
this.discoveries.set(key, request);
|
|
7389
|
+
}
|
|
7390
|
+
return this.discoveries.get(key);
|
|
7391
|
+
}
|
|
7392
|
+
/**
|
|
7393
|
+
* Signs in through the system browser (authorization code + PKCE). With offlineAccess the scope
|
|
7394
|
+
* gets offline_access; if the provider refuses it the login is repeated without it. After a
|
|
7395
|
+
* logout the provider is asked for the password again (prompt=login).
|
|
7396
|
+
*/
|
|
7397
|
+
async login(provider) {
|
|
7398
|
+
const discovery = await this.discover(provider.issuer);
|
|
7399
|
+
const scope = this.scopeOf(provider.scope);
|
|
7400
|
+
const promptLogin = this.flag(PROMPT_LOGIN_KEY);
|
|
7401
|
+
const authenticate = (withScope) => OAuth2Client.authenticate({
|
|
7402
|
+
appId: provider.clientId,
|
|
7403
|
+
authorizationBaseUrl: discovery.authorization_endpoint,
|
|
7404
|
+
accessTokenEndpoint: discovery.token_endpoint,
|
|
7405
|
+
redirectUrl: provider.redirectUri,
|
|
7406
|
+
responseType: 'code',
|
|
7407
|
+
scope: withScope,
|
|
7408
|
+
pkceEnabled: true,
|
|
7409
|
+
logsEnabled: false,
|
|
7410
|
+
...(promptLogin ? { additionalParameters: { prompt: 'login' } } : {})
|
|
7411
|
+
});
|
|
7412
|
+
let result;
|
|
7413
|
+
try {
|
|
7414
|
+
result = await authenticate(provider.offlineAccess ? this.withOfflineAccess(scope) : scope);
|
|
7415
|
+
}
|
|
7416
|
+
catch (error) {
|
|
7417
|
+
const cancelled = /USER_CANCELLED/i.test(String(error?.message ?? error));
|
|
7418
|
+
if (!provider.offlineAccess || cancelled) {
|
|
7419
|
+
throw error;
|
|
7420
|
+
}
|
|
7421
|
+
console.warn('Identity provider refused offline_access, signing in without it');
|
|
7422
|
+
result = await authenticate(scope);
|
|
7423
|
+
}
|
|
7424
|
+
this.setFlag(PROMPT_LOGIN_KEY, false);
|
|
7425
|
+
const response = result?.access_token_response ?? result;
|
|
7426
|
+
const accessToken = response?.access_token ?? result?.access_token;
|
|
7427
|
+
if (!accessToken) {
|
|
7428
|
+
return null;
|
|
7429
|
+
}
|
|
7430
|
+
return {
|
|
7431
|
+
access_token: accessToken,
|
|
7432
|
+
refresh_token: response?.refresh_token,
|
|
7433
|
+
id_token: response?.id_token,
|
|
7434
|
+
expires_in: response?.expires_in,
|
|
7435
|
+
token_type: response?.token_type,
|
|
7436
|
+
issuer: provider.issuer
|
|
7437
|
+
};
|
|
7438
|
+
}
|
|
7439
|
+
/** New tokens from the refresh token (grant_type=refresh_token, public client - no secret). */
|
|
7440
|
+
async refresh(provider, stored) {
|
|
7441
|
+
if (!stored?.refresh_token || !provider?.clientId) {
|
|
7442
|
+
return 'refused';
|
|
7443
|
+
}
|
|
7444
|
+
try {
|
|
7445
|
+
const discovery = await this.discover(stored.issuer || provider.issuer);
|
|
7446
|
+
const response = await stagedRequest(signal => fetch(discovery.token_endpoint, {
|
|
7447
|
+
method: 'POST',
|
|
7448
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
7449
|
+
body: new URLSearchParams({
|
|
7450
|
+
grant_type: 'refresh_token',
|
|
7451
|
+
refresh_token: stored.refresh_token,
|
|
7452
|
+
client_id: provider.clientId
|
|
7453
|
+
}).toString(),
|
|
7454
|
+
signal
|
|
7455
|
+
}));
|
|
7456
|
+
if (!response.ok) {
|
|
7457
|
+
// 400 invalid_grant: expired, rotated away or revoked.
|
|
7458
|
+
return 'refused';
|
|
7459
|
+
}
|
|
7460
|
+
const result = await response.json();
|
|
7461
|
+
if (!result?.access_token) {
|
|
7462
|
+
return 'refused';
|
|
7463
|
+
}
|
|
7464
|
+
return {
|
|
7465
|
+
access_token: result.access_token,
|
|
7466
|
+
// With token rotation the provider sends a new refresh token each time.
|
|
7467
|
+
refresh_token: result.refresh_token ?? stored.refresh_token,
|
|
7468
|
+
id_token: result.id_token ?? stored.id_token,
|
|
7469
|
+
expires_in: result.expires_in,
|
|
7470
|
+
token_type: result.token_type,
|
|
7471
|
+
issuer: stored.issuer || provider.issuer
|
|
7472
|
+
};
|
|
7473
|
+
}
|
|
7474
|
+
catch {
|
|
7475
|
+
console.warn('Token refresh not reachable');
|
|
7476
|
+
return null;
|
|
7477
|
+
}
|
|
7478
|
+
}
|
|
7479
|
+
/** The user's claims from the provider's userinfo endpoint; null when the token is refused. */
|
|
7480
|
+
async userInfo(provider, accessToken) {
|
|
7481
|
+
const discovery = await this.discover(provider.issuer);
|
|
7482
|
+
const response = await stagedRequest(signal => fetch(discovery.userinfo_endpoint, {
|
|
7483
|
+
headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' },
|
|
7484
|
+
signal
|
|
7485
|
+
}));
|
|
7486
|
+
return response.ok ? response.json() : null;
|
|
7487
|
+
}
|
|
7488
|
+
/**
|
|
7489
|
+
* The same logout for every provider, without a browser window: the provider session is ended
|
|
7490
|
+
* where the provider allows it from the app (Keycloak: logout endpoint with the refresh token),
|
|
7491
|
+
* refresh and access token are revoked, the saved sign-in is removed and the app restarts. The
|
|
7492
|
+
* next login asks for the password. When the project requires a login, the restarted app shows
|
|
7493
|
+
* its loader until the sign-in window opens.
|
|
7494
|
+
*/
|
|
7495
|
+
async signOut(provider, stored, storageKey, removeStored, options) {
|
|
7496
|
+
this.setFlag(PROMPT_LOGIN_KEY, true);
|
|
7497
|
+
if (provider?.clientId && (stored?.refresh_token || stored?.access_token)) {
|
|
7498
|
+
try {
|
|
7499
|
+
const discovery = await this.discover(stored?.issuer || provider.issuer);
|
|
7500
|
+
// Back-channel end of the session; providers without it (Okta) simply refuse - ignored.
|
|
7501
|
+
if (discovery.end_session_endpoint && stored?.refresh_token) {
|
|
7502
|
+
await this.post(discovery.end_session_endpoint, {
|
|
7503
|
+
client_id: provider.clientId,
|
|
7504
|
+
refresh_token: stored.refresh_token
|
|
7505
|
+
});
|
|
7506
|
+
}
|
|
7507
|
+
if (discovery.revocation_endpoint) {
|
|
7508
|
+
if (stored?.refresh_token) {
|
|
7509
|
+
await this.post(discovery.revocation_endpoint, {
|
|
7510
|
+
client_id: provider.clientId, token: stored.refresh_token, token_type_hint: 'refresh_token'
|
|
7511
|
+
});
|
|
7512
|
+
}
|
|
7513
|
+
if (stored?.access_token) {
|
|
7514
|
+
await this.post(discovery.revocation_endpoint, {
|
|
7515
|
+
client_id: provider.clientId, token: stored.access_token, token_type_hint: 'access_token'
|
|
7516
|
+
});
|
|
7517
|
+
}
|
|
7518
|
+
}
|
|
7519
|
+
}
|
|
7520
|
+
catch {
|
|
7521
|
+
console.warn('Identity provider not reachable, signing out locally');
|
|
7522
|
+
}
|
|
7523
|
+
}
|
|
7524
|
+
await removeStored(storageKey);
|
|
7525
|
+
this.setFlag(RELOGIN_KEY, options.loginRequired);
|
|
7526
|
+
window.location.reload();
|
|
7527
|
+
}
|
|
7528
|
+
/** Scope as the space separated string the OAuth2 plugin expects. */
|
|
7529
|
+
scopeOf(scope) {
|
|
7530
|
+
const value = Array.isArray(scope) ? scope.join(' ') : scope;
|
|
7531
|
+
return value?.trim() || 'openid profile email';
|
|
7532
|
+
}
|
|
7533
|
+
withOfflineAccess(scope) {
|
|
7534
|
+
const parts = scope.split(/\s+/).filter(Boolean);
|
|
7535
|
+
if (!parts.includes('offline_access')) {
|
|
7536
|
+
parts.push('offline_access');
|
|
7537
|
+
}
|
|
7538
|
+
return parts.join(' ');
|
|
7539
|
+
}
|
|
7540
|
+
async post(url, fields) {
|
|
7541
|
+
try {
|
|
7542
|
+
const response = await stagedRequest(signal => fetch(url, {
|
|
7543
|
+
method: 'POST',
|
|
7544
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
7545
|
+
body: new URLSearchParams(fields).toString(),
|
|
7546
|
+
signal
|
|
7547
|
+
}));
|
|
7548
|
+
if (!response.ok) {
|
|
7549
|
+
console.warn('Logout request refused', response.status);
|
|
7550
|
+
}
|
|
7551
|
+
}
|
|
7552
|
+
catch {
|
|
7553
|
+
console.warn('Logout request not reachable');
|
|
7554
|
+
}
|
|
7555
|
+
}
|
|
7556
|
+
flag(key) {
|
|
7557
|
+
try {
|
|
7558
|
+
return localStorage.getItem(key) === '1';
|
|
7559
|
+
}
|
|
7560
|
+
catch {
|
|
7561
|
+
return false;
|
|
7562
|
+
}
|
|
7563
|
+
}
|
|
7564
|
+
setFlag(key, on) {
|
|
7565
|
+
try {
|
|
7566
|
+
on ? localStorage.setItem(key, '1') : localStorage.removeItem(key);
|
|
7567
|
+
}
|
|
7568
|
+
catch {
|
|
7569
|
+
// Storage not available - the flag is a convenience only.
|
|
7570
|
+
}
|
|
7571
|
+
}
|
|
7572
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MobileOidcService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
7573
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MobileOidcService, providedIn: 'root' });
|
|
7574
|
+
}
|
|
7575
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MobileOidcService, decorators: [{
|
|
7576
|
+
type: Injectable,
|
|
7577
|
+
args: [{
|
|
7578
|
+
providedIn: 'root'
|
|
7579
|
+
}]
|
|
7580
|
+
}] });
|
|
7581
|
+
|
|
7582
|
+
// Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-14
|
|
7583
|
+
// - change: login, refresh and logout run through MobileOidcService - the same OpenID Connect flow
|
|
7584
|
+
// as Okta. The logout additionally revokes the tokens and makes the next login ask for the
|
|
7585
|
+
// password; the Keycloak session is still ended through the logout endpoint
|
|
6302
7586
|
// Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13
|
|
6303
7587
|
// - change: the Keycloak requests (discovery, token refresh, logout) run under the waiting rule
|
|
6304
7588
|
// - new: logout marks the restart (localStorage vl-relogin); the app shows its loader until the
|
|
@@ -6324,12 +7608,14 @@ class MobileKeycloakService {
|
|
|
6324
7608
|
http;
|
|
6325
7609
|
utilityService;
|
|
6326
7610
|
platform;
|
|
7611
|
+
oidc;
|
|
6327
7612
|
tokens = null;
|
|
6328
|
-
constructor(config, http, utilityService, platform) {
|
|
7613
|
+
constructor(config, http, utilityService, platform, oidc) {
|
|
6329
7614
|
this.config = config;
|
|
6330
7615
|
this.http = http;
|
|
6331
7616
|
this.utilityService = utilityService;
|
|
6332
7617
|
this.platform = platform;
|
|
7618
|
+
this.oidc = oidc;
|
|
6333
7619
|
if (Capacitor.isNativePlatform()) {
|
|
6334
7620
|
App.addListener('appUrlOpen', async (url) => {
|
|
6335
7621
|
// Nothing to do here; capacitor-oauth2 resolves internally.
|
|
@@ -6337,48 +7623,27 @@ class MobileKeycloakService {
|
|
|
6337
7623
|
});
|
|
6338
7624
|
}
|
|
6339
7625
|
}
|
|
7626
|
+
/** The Keycloak realm as an OIDC provider, from login.mobile of the project. */
|
|
7627
|
+
provider() {
|
|
7628
|
+
const cfg = this.config.getKeycloakMobileConfig();
|
|
7629
|
+
return {
|
|
7630
|
+
issuer: cfg?.issuer,
|
|
7631
|
+
clientId: cfg?.clientId,
|
|
7632
|
+
redirectUri: cfg?.redirectUri,
|
|
7633
|
+
scope: cfg?.scope,
|
|
7634
|
+
// login.mobile.offlineAccess: the sign-in lasts as long as the app is used at least once per
|
|
7635
|
+
// "Offline Session Idle" (30 days in realm p5) instead of the 5 days of a normal refresh token
|
|
7636
|
+
offlineAccess: cfg?.offlineAccess === true
|
|
7637
|
+
};
|
|
7638
|
+
}
|
|
6340
7639
|
async login() {
|
|
6341
7640
|
if (!Capacitor.isNativePlatform())
|
|
6342
7641
|
return null;
|
|
6343
7642
|
const cfg = this.config.getKeycloakMobileConfig();
|
|
6344
|
-
const
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
authorizationBaseUrl: discovery.authorization_endpoint,
|
|
6349
|
-
accessTokenEndpoint: discovery.token_endpoint,
|
|
6350
|
-
responseType: 'code',
|
|
6351
|
-
redirectUrl: cfg.redirectUri,
|
|
6352
|
-
scope,
|
|
6353
|
-
pkceEnabled: true,
|
|
6354
|
-
logsEnabled: false
|
|
6355
|
-
});
|
|
6356
|
-
/*
|
|
6357
|
-
* With login.mobile.offlineAccess the app asks for an offline token: the sign-in then lasts as
|
|
6358
|
-
* long as the app is used at least once per "Offline Session Idle" (30 days in realm p5)
|
|
6359
|
-
* instead of the 5 days of a normal refresh token. If Keycloak refuses the scope - role or
|
|
6360
|
-
* client scope removed - the login is repeated without it rather than failing.
|
|
6361
|
-
*/
|
|
6362
|
-
const offlineScope = this.withOfflineAccess(cfg.scope);
|
|
6363
|
-
let result;
|
|
6364
|
-
try {
|
|
6365
|
-
result = await authenticate(cfg.offlineAccess ? offlineScope : cfg.scope);
|
|
6366
|
-
}
|
|
6367
|
-
catch (error) {
|
|
6368
|
-
const cancelled = /USER_CANCELLED/i.test(String(error?.message ?? error));
|
|
6369
|
-
if (!cfg.offlineAccess || cancelled) {
|
|
6370
|
-
throw error;
|
|
6371
|
-
}
|
|
6372
|
-
console.warn('Keycloak refused offline_access, signing in without it');
|
|
6373
|
-
result = await authenticate(cfg.scope);
|
|
6374
|
-
}
|
|
6375
|
-
this.tokens = {
|
|
6376
|
-
access_token: result?.access_token_response?.access_token ? result?.access_token_response?.access_token : result.access_token,
|
|
6377
|
-
refresh_token: result?.access_token_response?.refresh_token,
|
|
6378
|
-
id_token: result?.access_token_response?.id_token,
|
|
6379
|
-
expires_in: result?.access_token_response?.expires_at ? result?.access_token_response?.expires_at : result?.access_token_response?.expires_in,
|
|
6380
|
-
token_type: result?.access_token_response?.token_type
|
|
6381
|
-
};
|
|
7643
|
+
const tokens = await this.oidc.login(this.provider());
|
|
7644
|
+
if (!tokens)
|
|
7645
|
+
return '';
|
|
7646
|
+
this.tokens = { ...tokens };
|
|
6382
7647
|
let keycloakLoginUser = "";
|
|
6383
7648
|
if (this.tokens.access_token) {
|
|
6384
7649
|
keycloakLoginUser = await this.getUserInfo(this.tokens.access_token, cfg.issuer);
|
|
@@ -6393,57 +7658,13 @@ class MobileKeycloakService {
|
|
|
6393
7658
|
}
|
|
6394
7659
|
return keycloakLoginUser;
|
|
6395
7660
|
}
|
|
6396
|
-
/** The configured scope plus offline_access, without duplicating it. */
|
|
6397
|
-
withOfflineAccess(scope) {
|
|
6398
|
-
const parts = (scope ?? 'openid').split(/\s+/).filter(Boolean);
|
|
6399
|
-
if (!parts.includes('offline_access')) {
|
|
6400
|
-
parts.push('offline_access');
|
|
6401
|
-
}
|
|
6402
|
-
return parts.join(' ');
|
|
6403
|
-
}
|
|
6404
|
-
/**
|
|
6405
|
-
* New tokens from the refresh token (grant_type=refresh_token). Returns them in the stored token
|
|
6406
|
-
* shape, or null when Keycloak refuses - then the caller falls back to the login.
|
|
6407
|
-
*/
|
|
6408
7661
|
async refreshTokens(stored) {
|
|
6409
|
-
const
|
|
6410
|
-
|
|
6411
|
-
if (!stored?.refresh_token || !issuer || !cfg?.clientId) {
|
|
6412
|
-
return null;
|
|
6413
|
-
}
|
|
6414
|
-
try {
|
|
6415
|
-
const discovery = await (await stagedRequest(signal => fetch(`${issuer}/.well-known/openid-configuration`, { signal }))).json();
|
|
6416
|
-
const response = await stagedRequest(signal => fetch(discovery.token_endpoint, {
|
|
6417
|
-
method: 'POST',
|
|
6418
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
6419
|
-
body: new URLSearchParams({
|
|
6420
|
-
grant_type: 'refresh_token',
|
|
6421
|
-
refresh_token: stored.refresh_token,
|
|
6422
|
-
client_id: cfg.clientId
|
|
6423
|
-
}).toString(),
|
|
6424
|
-
signal
|
|
6425
|
-
}));
|
|
6426
|
-
if (!response.ok) {
|
|
6427
|
-
return null;
|
|
6428
|
-
}
|
|
6429
|
-
const result = await response.json();
|
|
6430
|
-
if (!result?.access_token) {
|
|
6431
|
-
return null;
|
|
6432
|
-
}
|
|
6433
|
-
this.tokens = {
|
|
6434
|
-
access_token: result.access_token,
|
|
6435
|
-
refresh_token: result.refresh_token ?? stored.refresh_token,
|
|
6436
|
-
id_token: result.id_token ?? stored.id_token,
|
|
6437
|
-
expires_in: result.expires_in,
|
|
6438
|
-
token_type: result.token_type,
|
|
6439
|
-
issuer
|
|
6440
|
-
};
|
|
6441
|
-
return this.tokens;
|
|
6442
|
-
}
|
|
6443
|
-
catch (error) {
|
|
6444
|
-
console.warn('Keycloak token refresh failed');
|
|
7662
|
+
const result = await this.oidc.refresh(this.provider(), stored);
|
|
7663
|
+
if (!result || result === 'refused') {
|
|
6445
7664
|
return null;
|
|
6446
7665
|
}
|
|
7666
|
+
this.tokens = { ...result, issuer: stored?.issuer || result.issuer };
|
|
7667
|
+
return this.tokens;
|
|
6447
7668
|
}
|
|
6448
7669
|
async getUserInfo(accessToken, issuer) {
|
|
6449
7670
|
try {
|
|
@@ -6472,37 +7693,12 @@ class MobileKeycloakService {
|
|
|
6472
7693
|
*/
|
|
6473
7694
|
async logout() {
|
|
6474
7695
|
const keycloakInfo = await this.utilityService.getLocalStorage('KeycloakInfo');
|
|
6475
|
-
const
|
|
6476
|
-
const issuer = keycloakInfo?.issuer || cfg?.issuer;
|
|
6477
|
-
const refreshToken = keycloakInfo?.refresh_token ?? keycloakInfo?.keycloakInfo?.refresh_token;
|
|
6478
|
-
if (issuer && cfg?.clientId && refreshToken) {
|
|
6479
|
-
try {
|
|
6480
|
-
const response = await stagedRequest(signal => fetch(`${issuer}/protocol/openid-connect/logout`, {
|
|
6481
|
-
method: 'POST',
|
|
6482
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
6483
|
-
body: new URLSearchParams({ client_id: cfg.clientId, refresh_token: refreshToken }).toString(),
|
|
6484
|
-
signal
|
|
6485
|
-
}));
|
|
6486
|
-
if (!response.ok) {
|
|
6487
|
-
console.warn('Keycloak logout refused', response.status);
|
|
6488
|
-
}
|
|
6489
|
-
}
|
|
6490
|
-
catch {
|
|
6491
|
-
console.warn('Keycloak logout not reachable, signing out locally');
|
|
6492
|
-
}
|
|
6493
|
-
}
|
|
7696
|
+
const tokens = keycloakInfo?.keycloakInfo ?? keycloakInfo;
|
|
6494
7697
|
this.tokens = null;
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
try {
|
|
6498
|
-
localStorage.setItem('vl-relogin', '1');
|
|
6499
|
-
}
|
|
6500
|
-
catch {
|
|
6501
|
-
// Storage not available - the app starts without the loader.
|
|
6502
|
-
}
|
|
6503
|
-
window.location.reload();
|
|
7698
|
+
// Keycloak projects require a login: the restarted app shows its loader until the sign-in opens.
|
|
7699
|
+
await this.oidc.signOut(this.provider(), tokens, 'KeycloakInfo', key => this.utilityService.removeLocalStorage(key), { loginRequired: true });
|
|
6504
7700
|
}
|
|
6505
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MobileKeycloakService, deps: [{ token: ConfigService }, { token: i1$
|
|
7701
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MobileKeycloakService, deps: [{ token: ConfigService }, { token: i1$2.HttpClient }, { token: UtilityService }, { token: i1.Platform }, { token: MobileOidcService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
6506
7702
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MobileKeycloakService, providedIn: 'root' });
|
|
6507
7703
|
}
|
|
6508
7704
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MobileKeycloakService, decorators: [{
|
|
@@ -6510,7 +7706,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
6510
7706
|
args: [{
|
|
6511
7707
|
providedIn: 'root'
|
|
6512
7708
|
}]
|
|
6513
|
-
}], ctorParameters: () => [{ type: ConfigService }, { type: i1$
|
|
7709
|
+
}], ctorParameters: () => [{ type: ConfigService }, { type: i1$2.HttpClient }, { type: UtilityService }, { type: i1.Platform }, { type: MobileOidcService }] });
|
|
6514
7710
|
|
|
6515
7711
|
// Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13
|
|
6516
7712
|
// - new: activeLanguage, the language file in use ("de.json"); its menu entry gets a check mark
|
|
@@ -7330,8 +8526,8 @@ class SideMenuComponent {
|
|
|
7330
8526
|
originalCaption;
|
|
7331
8527
|
});
|
|
7332
8528
|
}
|
|
7333
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: SideMenuComponent, deps: [{ token: i1$
|
|
7334
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.2", type: SideMenuComponent, isStandalone: true, selector: "vl-side-menu", inputs: { menuItems: "menuItems", userInfo: "userInfo", settings: "settings", projects: "projects", tenantName: "tenantName", environment: "environment", projectSeleted: "projectSeleted", appVersionInfo: "appVersionInfo" }, outputs: { languageChange: "languageChange", invokeMenu: "invokeMenu", menuToggle: "menuToggle", invokeProject: "invokeProject", errorMessage: "errorMessage", deleteProject: "deleteProject", paginationType: "paginationType" }, host: { listeners: { "window:resize": "onResizeMobileMode()", "document:click": "onDocumentClick($event)" }, properties: { "class.sidemenu-mobile": "this.mobileMode" } }, viewQueries: [{ propertyName: "sideMenu", first: true, predicate: ["sideMenu"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: the active language has a check mark, like the active pagination type\r\n - fix: all check marks (pagination, language) stand in one column at the right edge; the\r\n pagination marks followed their caption at a fixed distance and landed elsewhere on iOS\r\n - change: the version footer has three columns - version, build (source commit) and build\r\n number (appBuildNumber), the three values build.sh and the web Docker build produce -->\r\n<!-- Shuaib 2026-09-24\r\n - change: sign-in visibility checks now include jwtToken alongside keycloak and oktaAuth. -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: a @contract comment on the option key, singular since 2026-09-16, and on the sign-in\r\n branches, which know keycloak and oktaAuth but not jwtToken -->\r\n<div class=\"left-wrap sidemenu\" [class.collapsed]=\"isCollapsed\"\r\n [ngStyle]=\"{ 'z-index': !isCollapsed && isMobile ? 999999 : 'auto' }\"\r\n #sideMenu>\r\n <!-- Move toggle button OUTSIDE menu container -->\r\n <div class=\"side-toggle-handle\" id=\"toggle-id\" (click)=\"toggleCollapsed()\"\r\n [class.collapsed]=\"isCollapsed\">\r\n <span id=\"toggle-icon\" nz-icon\r\n [nzType]=\"isCollapsed ? 'caret-right' : 'caret-left'\"></span>\r\n </div>\r\n <div class=\"side-menu\" id=\"sidemenuwrap\"\r\n [ngStyle]=\"{ width: (isMobile && isCollapsed) ? '1px' : isCollapsed ? '60px' : '' }\">\r\n <!-- user content -->\r\n <ng-container *ngIf=\"loginedUserInfo && loginedUserInfo?.name\">\r\n <div class=\"user-profile\" [class.collapsed]=\"isCollapsed\">\r\n <img class=\"avatar\" [src]=\"loginedUserInfo?.image\" alt=\"User\" />\r\n <div [class]=\"isMobile ? '' : 'details'\" *ngIf=\"!isCollapsed\">\r\n <div class=\"name\">{{ loginedUserInfo.name }}</div>\r\n <div class=\"email\">{{ loginedUserInfo?.email }}</div>\r\n <div class=\"actions\" *ngIf=\"loginedUserInfo?.actions?.length\">\r\n <button *ngFor=\"let action of loginedUserInfo?.actions\" nz-button\r\n nzType=\"link\" nzSize=\"small\"\r\n (click)=\"invokeNavigate(action)\">\r\n <span>{{ action?.label?.caption }}</span>\r\n <span nz-icon [nzType]=\"action.icon\"></span>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <!-- @contract decision: this container reads option (singular), like every other one\r\n reality: it read options (plural) until 2026-09-16. A menu file written for the old\r\n spelling is ignored without an error.\r\n\r\n @contract decision: sign-in visibility supports keycloak, oktaAuth and jwtToken.\r\n A JWT project uses the decoded user's login state, like the other providers.\r\n seen: the login type comparisons in the lines below -->\r\n <!-- dynamic menu -->\r\n <ul nz-menu nzMode=\"inline\" nzTheme=\"dark\"\r\n [nzInlineCollapsed]=\"isCollapsed\">\r\n <ng-container *ngFor=\"let item of menuItems\">\r\n <ng-container\r\n *ngIf=\"!item.submenu && item?.option?.action != 'logout'; else submenu\">\r\n <span\r\n [class]=\"environment?.login?.type === 'oktaAuth' || environment?.login?.type === 'keycloak' || environment?.login?.type === 'jwtToken' ? item.option?.login === true ? loginedUserInfo?.name ? '' : 'hidden' : item.option?.login === false ? loginedUserInfo?.name ? 'hidden' : '' : '' : userInfo?.userId ? item.option?.login ? item.option.role ? item.option.role == userInfo.userRole ? '' : 'hidden' : '' : 'hidden' : !item.option?.login ? '' : 'hidden' \">\r\n <li (click)=\"invokeNavigate(item)\" nz-menu-item nz-tooltip\r\n nzTooltipPlacement=\"right\"\r\n [nzTooltipTitle]=\"isCollapsed && !isMobile ? item?.label?.caption : ''\">\r\n <span nz-icon [nzType]=\"item.icon\"></span>\r\n <span>{{ item?.label?.caption }}</span>\r\n </li>\r\n </span>\r\n </ng-container>\r\n <ng-template #submenu>\r\n <span\r\n [class]=\"item.option?.login === true && (environment?.login?.type === 'oktaAuth' || environment?.login?.type === 'keycloak' || environment?.login?.type === 'jwtToken') ? loginedUserInfo?.name ? '' : 'hidden' : item.option?.login === false ? loginedUserInfo?.name ? 'hidden' : '' : ''\">\r\n <li nz-submenu *ngIf=\"item?.option?.action != 'logout'\"\r\n [nzTitle]=\"item?.label?.caption\"\r\n [nzIcon]=\"item.icon\">\r\n <ul>\r\n <ng-container *ngFor=\"let child of item.submenu\">\r\n <li (click)=\"invokeNavigate(child)\" *ngIf=\"!child.submenu\"\r\n nz-menu-item nz-tooltip\r\n nzTooltipPlacement=\"right\"\r\n [nzTooltipTitle]=\"isCollapsed && !isMobile ? child?.label?.caption : ''\">\r\n <span nz-icon [nzType]=\"child?.icon\"></span>\r\n <span>{{ child?.label?.caption }}</span>\r\n </li>\r\n <ng-container *ngIf=\"child.submenu\">\r\n <li nz-submenu [nzTitle]=\"child?.label?.caption\"\r\n [nzIcon]=\"child?.icon\">\r\n <ul>\r\n <ng-container *ngFor=\"let subChild of child.submenu\">\r\n <li (click)=\"invokeNavigate(subChild)\" nz-menu-item>\r\n <ng-container\r\n *ngIf=\"subChild?.option?.setImageUrl; else antIcon\">\r\n <img *ngIf=\"isUrl(subChild?.icon)\"\r\n [src]=\"subChild?.icon\" alt=\"icon\"\r\n style=\"width: 16px; height: 16px; margin-right: 8px;\" />\r\n </ng-container>\r\n <ng-template #antIcon>\r\n <span nz-icon [nzType]=\"subChild?.icon\"></span>\r\n </ng-template>\r\n <span>{{ subChild?.label?.caption }}</span>\r\n @if(subChild?.option?.numberedPagination &&\r\n !scrollPagination){\r\n <span nz-icon\r\n nzType=\"check\" nzTheme=\"outline\"\r\n class=\"pagination-check\">\r\n </span>\r\n }\r\n @if(subChild?.option?.scrollPagination &&\r\n scrollPagination){\r\n <span nz-icon\r\n nzType=\"check\" nzTheme=\"outline\"\r\n class=\"pagination-check\">\r\n </span>\r\n }\r\n @if(subChild?.option?.changeLang &&\r\n subChild?.option?.data_url === activeLanguage){\r\n <span nz-icon nzType=\"check\" nzTheme=\"outline\"\r\n class=\"pagination-check\">\r\n </span>\r\n }\r\n </li>\r\n </ng-container>\r\n </ul>\r\n </li>\r\n </ng-container>\r\n </ng-container>\r\n </ul>\r\n </li>\r\n </span>\r\n </ng-template>\r\n </ng-container>\r\n\r\n <!-- separator -->\r\n <li *ngIf=\"settingMenu.submenu\" class=\"menu-separator\"></li>\r\n\r\n <!-- settings menu -->\r\n <ng-container *ngIf=\"!settingMenu.submenu; else settingSubmenu\"\r\n style=\"position: absolute; bottom: 0;\">\r\n <li (click)=\"invokeNavigate(settingMenu)\" nz-menu-item nz-tooltip\r\n nzTooltipPlacement=\"right\"\r\n [nzTooltipTitle]=\"isCollapsed && !isMobile ? settingMenu?.label?.caption : ''\">\r\n <span nz-icon [nzType]=\"settingMenu.icon\"></span>\r\n <span>{{ settingMenu?.label?.caption }}</span>\r\n </li>\r\n </ng-container>\r\n <ng-template #settingSubmenu>\r\n <li nz-submenu [nzTitle]=\"settingMenu?.label?.caption\"\r\n [nzIcon]=\"settingMenu.icon\">\r\n <ul>\r\n <ng-container *ngFor=\"let child of settingMenu.submenu\">\r\n <li (click)=\"invokeNavigate(child)\" *ngIf=\"!child.submenu\"\r\n nz-menu-item nz-tooltip\r\n nzTooltipPlacement=\"right\"\r\n [nzTooltipTitle]=\"isCollapsed && !isMobile ? child?.label?.caption : ''\">\r\n <span nz-icon [nzType]=\"child?.icon\"></span>\r\n <span>{{ child?.label?.caption }}</span>\r\n </li>\r\n <ng-container *ngIf=\"child.submenu\">\r\n <li nz-submenu [nzTitle]=\"child?.label?.caption\"\r\n [nzIcon]=\"child?.icon\">\r\n <ul style=\"width: 260px;\">\r\n <ng-container\r\n *ngFor=\"let subChild of child.submenu; let i = index\">\r\n <li (click)=\"invokeNavigate(subChild, child)\"\r\n nz-menu-item>\r\n <span\r\n [class]=\"child?.label?.caption === 'Languages' ? '' : 'menu-row'\">\r\n <ng-container\r\n *ngIf=\"subChild?.option?.changeProject; else antIcon\">\r\n <img [src]=\"subChild?.icon\" alt=\"icon\"\r\n style=\"width: 16px; height: 16px; margin-right: 8px;\" />\r\n </ng-container>\r\n <ng-template #antIcon>\r\n <span nz-icon [nzType]=\"subChild?.icon\"></span>\r\n </ng-template>\r\n <span>{{ subChild?.label?.caption }}</span>\r\n <!-- Delete icon (prevent click bubbling) -->\r\n <span *ngIf=\"child?.label?.caption != 'Languages'\"\r\n nz-icon nzType=\"delete\" class=\"delete-icon\"\r\n style=\"padding-left: 5%;\"\r\n (click)=\"deleteMenuItem(subChild, i, $event)\"></span>\r\n </span>\r\n </li>\r\n </ng-container>\r\n </ul>\r\n </li>\r\n </ng-container>\r\n </ng-container>\r\n </ul>\r\n </li>\r\n </ng-template>\r\n </ul>\r\n @if (!isCollapsed && appVersionInfo?.appVersion) {\r\n <div class=\"app-version-footer\">\r\n <div class=\"app-version-footer__item\">\r\n <span class=\"app-version-footer__label\">Version</span>\r\n <span class=\"app-version-footer__value\">{{ appVersionInfo.appVersion }}</span>\r\n </div>\r\n <div class=\"app-version-footer__item\">\r\n <span class=\"app-version-footer__label\">Build</span>\r\n <span class=\"app-version-footer__value\">{{ appVersionInfo.appBuild || '-' }}</span>\r\n </div>\r\n <div class=\"app-version-footer__item\">\r\n <span class=\"app-version-footer__label\">Build No.</span>\r\n <span class=\"app-version-footer__value\">{{ appVersionInfo.appBuildNumber || '-' }}</span>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n", styles: [":host{position:relative;display:block;flex:0 0 auto;width:250px;height:100%;min-height:0;overflow:visible;transition:width .3s ease}.left-wrap.sidemenu{position:relative;width:250px;height:100%;min-height:0;overflow:visible;transition:width .3s ease,transform .3s ease}.side-menu{position:relative;display:flex;flex-direction:column;width:250px;height:100%;min-height:0;color:#fff;background-color:#0d1b2a;overflow:hidden;transition:width .3s ease,box-shadow .3s ease}:host:has(.left-wrap.sidemenu.collapsed){width:64px}.left-wrap.sidemenu.collapsed,.left-wrap.sidemenu.collapsed .side-menu{width:64px}.menu-separator{flex:0 0 auto;height:1px;margin:15px 12px 6px;background-color:#ffffff26;border:0;list-style:none}.user-profile{flex:0 0 auto;padding:20px 10px 16px;margin-top:40px;text-align:center;border-bottom:1px solid rgba(255,255,255,.1);overflow:hidden;transition:padding .25s ease,margin .25s ease,opacity .25s ease,max-height .25s ease}.user-profile .avatar{display:block;width:64px;height:64px;margin:0 auto 8px;object-fit:cover;border:2px solid #ffffff;border-radius:50%;transition:width .25s ease,height .25s ease,margin .25s ease}.user-profile .details{min-width:0;overflow:hidden;transition:opacity .2s ease}.user-profile .name{color:#fff;font-size:14px;font-weight:600;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.user-profile .email{margin:2px 0 8px;color:#aaa;font-size:12px;line-height:1.4;overflow-wrap:anywhere;word-break:break-word}.user-profile .actions{display:flex;align-items:center;justify-content:center;gap:6px}.user-profile .actions button{margin:0;color:#fff}.left-wrap.sidemenu.collapsed .user-profile{padding:12px 8px}.left-wrap.sidemenu.collapsed .user-profile .avatar{width:40px;height:40px;margin-bottom:0}.left-wrap.sidemenu.collapsed .user-profile .details,.left-wrap.sidemenu.collapsed .user-profile .name,.left-wrap.sidemenu.collapsed .user-profile .email,.left-wrap.sidemenu.collapsed .user-profile .actions{display:none}.side-menu ul[nz-menu]{flex:1 1 auto;width:100%;min-height:0;margin:0;padding:4px 0 8px;overflow-x:hidden;overflow-y:auto;background-color:transparent;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.28) transparent;scroll-behavior:smooth}.side-menu ul[nz-menu]::-webkit-scrollbar{width:6px}.side-menu ul[nz-menu]::-webkit-scrollbar-track{background:transparent}.side-menu ul[nz-menu]::-webkit-scrollbar-thumb{background-color:#ffffff47;border-radius:999px}.side-menu ul[nz-menu]::-webkit-scrollbar-thumb:hover{background-color:#ffffff6b}.menu-row{display:flex;align-items:center;width:100%;min-width:0}.menu-text{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.delete-icon{flex:0 0 auto;margin-left:auto;color:#ffffffb3;cursor:pointer;transition:color .18s ease,transform .18s ease}.delete-icon:hover{color:#ff7875;transform:scale(1.08)}.projects{position:static;flex:0 0 auto;width:100%;margin-top:auto;background-color:#0d1b2a}.side-toggle-handle{position:absolute;top:24px;right:-14px;z-index:10000;display:flex;align-items:center;justify-content:center;width:28px;height:48px;padding:0;color:#fff;background-color:#0d1b2a;border:0;border-radius:0 6px 6px 0;box-shadow:2px 0 6px #00000040;cursor:pointer;transform:translateY(-50%);transition:background-color .2s ease,box-shadow .2s ease}.side-toggle-handle:hover{background-color:#142a40;box-shadow:3px 0 9px #0000004d}.side-toggle-handle:focus-visible{outline:3px solid rgba(24,144,255,.45);outline-offset:2px}.side-toggle-handle span{color:#fff;font-size:14px;line-height:1;transition:transform .25s ease}.collapse-btn{position:absolute;top:8px;right:8px;z-index:10001}:host(:not(.sidemenu-mobile)) .left-wrap.sidemenu.collapsed:hover{transform:translate(0)}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed{width:64px!important}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-item,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title{display:flex!important;align-items:center!important;justify-content:center!important;width:45px!important;height:32px!important;margin-inline:0!important;padding-inline:0!important;overflow:hidden!important}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .ant-menu-title-content,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .menu-text,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .delete-icon,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .ant-menu-submenu-arrow{display:none!important;width:0!important;min-width:0!important;max-width:0!important;margin:0!important;padding:0!important;visibility:hidden!important;overflow:hidden!important;text-overflow:clip!important}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .ant-menu-item-icon,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-item>.anticon,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title>.anticon{display:inline-flex!important;flex:0 0 auto;align-items:center;justify-content:center;width:auto!important;min-width:18px;margin:0!important;padding:0!important;color:inherit;font-size:18px;line-height:1;visibility:visible!important}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .menu-row{display:contents}:host(.sidemenu-mobile){position:absolute;top:0;bottom:0;left:0;z-index:9999;width:250px;height:100%}:host(.sidemenu-mobile):has(.left-wrap.sidemenu.collapsed){width:0}:host(.sidemenu-mobile) .left-wrap.sidemenu{width:250px;height:100%}:host(.sidemenu-mobile) .side-menu,:host(.sidemenu-mobile) .side-menu.open{width:250px;height:100%;box-shadow:8px 0 22px #0000003d}:host(.sidemenu-mobile) .left-wrap.sidemenu.collapsed{width:250px;transform:translate(-250px)}:host(.sidemenu-mobile) .left-wrap.sidemenu.collapsed .side-menu{width:250px;box-shadow:none}:host(.sidemenu-mobile) .left-wrap.sidemenu.collapsed .side-toggle-handle{right:-28px}:host(.sidemenu-mobile) .side-toggle-handle{top:0;right:0;padding:0;transform:none!important}:host(.sidemenu-mobile) .collapse-btn{top:8px;right:8px}:host(.sidemenu-mobile) .user-profile{margin-top:40px}:host(.sidemenu-mobile) .user-profile .avatar{width:48px;height:48px}:host(.sidemenu-mobile) .left-wrap.sidemenu.collapsed .user-profile{max-height:0;padding:0;margin:0;opacity:0;overflow:hidden;border-bottom:0}@media(prefers-reduced-motion:reduce){:host,.left-wrap.sidemenu,.side-menu,.user-profile,.user-profile .avatar,.side-toggle-handle,.side-toggle-handle span{transition:none}.side-menu ul[nz-menu]{scroll-behavior:auto}}.app-version-footer{flex-shrink:0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;padding:10px 12px;border-top:1px solid rgba(255,255,255,.15);color:#ffffffa6;font-size:11px;line-height:1.4;text-align:center}.app-version-footer__item{display:flex;flex-direction:column;min-width:0}.app-version-footer__label{font-size:10px;opacity:.7}.app-version-footer__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pagination-check{position:absolute;top:50%;right:42px;transform:translateY(-50%)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i5.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i5.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: PlatformModule }, { kind: "ngmodule", type:
|
|
8529
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: SideMenuComponent, deps: [{ token: i1$5.NzI18nService }, { token: MobileKeycloakService }, { token: Couchdb }, { token: i7.OAuthService }, { token: i1.Platform }, { token: RequestService }, { token: UtilityService }, { token: i1$4.DialogService }, { token: NotificationService }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Component });
|
|
8530
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.2", type: SideMenuComponent, isStandalone: true, selector: "vl-side-menu", inputs: { menuItems: "menuItems", userInfo: "userInfo", settings: "settings", projects: "projects", tenantName: "tenantName", environment: "environment", projectSeleted: "projectSeleted", appVersionInfo: "appVersionInfo" }, outputs: { languageChange: "languageChange", invokeMenu: "invokeMenu", menuToggle: "menuToggle", invokeProject: "invokeProject", errorMessage: "errorMessage", deleteProject: "deleteProject", paginationType: "paginationType" }, host: { listeners: { "window:resize": "onResizeMobileMode()", "document:click": "onDocumentClick($event)" }, properties: { "class.sidemenu-mobile": "this.mobileMode" } }, viewQueries: [{ propertyName: "sideMenu", first: true, predicate: ["sideMenu"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: the active language has a check mark, like the active pagination type\r\n - fix: all check marks (pagination, language) stand in one column at the right edge; the\r\n pagination marks followed their caption at a fixed distance and landed elsewhere on iOS\r\n - change: the version footer has three columns - version, build (source commit) and build\r\n number (appBuildNumber), the three values build.sh and the web Docker build produce -->\r\n<!-- Shuaib 2026-09-24\r\n - change: sign-in visibility checks now include jwtToken alongside keycloak and oktaAuth. -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: a @contract comment on the option key, singular since 2026-09-16, and on the sign-in\r\n branches, which know keycloak and oktaAuth but not jwtToken -->\r\n<div class=\"left-wrap sidemenu\" [class.collapsed]=\"isCollapsed\"\r\n [ngStyle]=\"{ 'z-index': !isCollapsed && isMobile ? 999999 : 'auto' }\"\r\n #sideMenu>\r\n <!-- Move toggle button OUTSIDE menu container -->\r\n <div class=\"side-toggle-handle\" id=\"toggle-id\" (click)=\"toggleCollapsed()\"\r\n [class.collapsed]=\"isCollapsed\">\r\n <span id=\"toggle-icon\" nz-icon\r\n [nzType]=\"isCollapsed ? 'caret-right' : 'caret-left'\"></span>\r\n </div>\r\n <div class=\"side-menu\" id=\"sidemenuwrap\"\r\n [ngStyle]=\"{ width: (isMobile && isCollapsed) ? '1px' : isCollapsed ? '60px' : '' }\">\r\n <!-- user content -->\r\n <ng-container *ngIf=\"loginedUserInfo && loginedUserInfo?.name\">\r\n <div class=\"user-profile\" [class.collapsed]=\"isCollapsed\">\r\n <img class=\"avatar\" [src]=\"loginedUserInfo?.image\" alt=\"User\" />\r\n <div [class]=\"isMobile ? '' : 'details'\" *ngIf=\"!isCollapsed\">\r\n <div class=\"name\">{{ loginedUserInfo.name }}</div>\r\n <div class=\"email\">{{ loginedUserInfo?.email }}</div>\r\n <div class=\"actions\" *ngIf=\"loginedUserInfo?.actions?.length\">\r\n <button *ngFor=\"let action of loginedUserInfo?.actions\" nz-button\r\n nzType=\"link\" nzSize=\"small\"\r\n (click)=\"invokeNavigate(action)\">\r\n <span>{{ action?.label?.caption }}</span>\r\n <span nz-icon [nzType]=\"action.icon\"></span>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <!-- @contract decision: this container reads option (singular), like every other one\r\n reality: it read options (plural) until 2026-09-16. A menu file written for the old\r\n spelling is ignored without an error.\r\n\r\n @contract decision: sign-in visibility supports keycloak, oktaAuth and jwtToken.\r\n A JWT project uses the decoded user's login state, like the other providers.\r\n seen: the login type comparisons in the lines below -->\r\n <!-- dynamic menu -->\r\n <ul nz-menu nzMode=\"inline\" nzTheme=\"dark\"\r\n [nzInlineCollapsed]=\"isCollapsed\">\r\n <ng-container *ngFor=\"let item of menuItems\">\r\n <ng-container\r\n *ngIf=\"!item.submenu && item?.option?.action != 'logout'; else submenu\">\r\n <span\r\n [class]=\"environment?.login?.type === 'oktaAuth' || environment?.login?.type === 'keycloak' || environment?.login?.type === 'jwtToken' ? item.option?.login === true ? loginedUserInfo?.name ? '' : 'hidden' : item.option?.login === false ? loginedUserInfo?.name ? 'hidden' : '' : '' : userInfo?.userId ? item.option?.login ? item.option.role ? item.option.role == userInfo.userRole ? '' : 'hidden' : '' : 'hidden' : !item.option?.login ? '' : 'hidden' \">\r\n <li (click)=\"invokeNavigate(item)\" nz-menu-item nz-tooltip\r\n nzTooltipPlacement=\"right\"\r\n [nzTooltipTitle]=\"isCollapsed && !isMobile ? item?.label?.caption : ''\">\r\n <span nz-icon [nzType]=\"item.icon\"></span>\r\n <span>{{ item?.label?.caption }}</span>\r\n </li>\r\n </span>\r\n </ng-container>\r\n <ng-template #submenu>\r\n <span\r\n [class]=\"item.option?.login === true && (environment?.login?.type === 'oktaAuth' || environment?.login?.type === 'keycloak' || environment?.login?.type === 'jwtToken') ? loginedUserInfo?.name ? '' : 'hidden' : item.option?.login === false ? loginedUserInfo?.name ? 'hidden' : '' : ''\">\r\n <li nz-submenu *ngIf=\"item?.option?.action != 'logout'\"\r\n [nzTitle]=\"item?.label?.caption\"\r\n [nzIcon]=\"item.icon\">\r\n <ul>\r\n <ng-container *ngFor=\"let child of item.submenu\">\r\n <li (click)=\"invokeNavigate(child)\" *ngIf=\"!child.submenu\"\r\n nz-menu-item nz-tooltip\r\n nzTooltipPlacement=\"right\"\r\n [nzTooltipTitle]=\"isCollapsed && !isMobile ? child?.label?.caption : ''\">\r\n <span nz-icon [nzType]=\"child?.icon\"></span>\r\n <span>{{ child?.label?.caption }}</span>\r\n </li>\r\n <ng-container *ngIf=\"child.submenu\">\r\n <li nz-submenu [nzTitle]=\"child?.label?.caption\"\r\n [nzIcon]=\"child?.icon\">\r\n <ul>\r\n <ng-container *ngFor=\"let subChild of child.submenu\">\r\n <li (click)=\"invokeNavigate(subChild)\" nz-menu-item>\r\n <ng-container\r\n *ngIf=\"subChild?.option?.setImageUrl; else antIcon\">\r\n <img *ngIf=\"isUrl(subChild?.icon)\"\r\n [src]=\"subChild?.icon\" alt=\"icon\"\r\n style=\"width: 16px; height: 16px; margin-right: 8px;\" />\r\n </ng-container>\r\n <ng-template #antIcon>\r\n <span nz-icon [nzType]=\"subChild?.icon\"></span>\r\n </ng-template>\r\n <span>{{ subChild?.label?.caption }}</span>\r\n @if(subChild?.option?.numberedPagination &&\r\n !scrollPagination){\r\n <span nz-icon\r\n nzType=\"check\" nzTheme=\"outline\"\r\n class=\"pagination-check\">\r\n </span>\r\n }\r\n @if(subChild?.option?.scrollPagination &&\r\n scrollPagination){\r\n <span nz-icon\r\n nzType=\"check\" nzTheme=\"outline\"\r\n class=\"pagination-check\">\r\n </span>\r\n }\r\n @if(subChild?.option?.changeLang &&\r\n subChild?.option?.data_url === activeLanguage){\r\n <span nz-icon nzType=\"check\" nzTheme=\"outline\"\r\n class=\"pagination-check\">\r\n </span>\r\n }\r\n </li>\r\n </ng-container>\r\n </ul>\r\n </li>\r\n </ng-container>\r\n </ng-container>\r\n </ul>\r\n </li>\r\n </span>\r\n </ng-template>\r\n </ng-container>\r\n\r\n <!-- separator -->\r\n <li *ngIf=\"settingMenu.submenu\" class=\"menu-separator\"></li>\r\n\r\n <!-- settings menu -->\r\n <ng-container *ngIf=\"!settingMenu.submenu; else settingSubmenu\"\r\n style=\"position: absolute; bottom: 0;\">\r\n <li (click)=\"invokeNavigate(settingMenu)\" nz-menu-item nz-tooltip\r\n nzTooltipPlacement=\"right\"\r\n [nzTooltipTitle]=\"isCollapsed && !isMobile ? settingMenu?.label?.caption : ''\">\r\n <span nz-icon [nzType]=\"settingMenu.icon\"></span>\r\n <span>{{ settingMenu?.label?.caption }}</span>\r\n </li>\r\n </ng-container>\r\n <ng-template #settingSubmenu>\r\n <li nz-submenu [nzTitle]=\"settingMenu?.label?.caption\"\r\n [nzIcon]=\"settingMenu.icon\">\r\n <ul>\r\n <ng-container *ngFor=\"let child of settingMenu.submenu\">\r\n <li (click)=\"invokeNavigate(child)\" *ngIf=\"!child.submenu\"\r\n nz-menu-item nz-tooltip\r\n nzTooltipPlacement=\"right\"\r\n [nzTooltipTitle]=\"isCollapsed && !isMobile ? child?.label?.caption : ''\">\r\n <span nz-icon [nzType]=\"child?.icon\"></span>\r\n <span>{{ child?.label?.caption }}</span>\r\n </li>\r\n <ng-container *ngIf=\"child.submenu\">\r\n <li nz-submenu [nzTitle]=\"child?.label?.caption\"\r\n [nzIcon]=\"child?.icon\">\r\n <ul style=\"width: 260px;\">\r\n <ng-container\r\n *ngFor=\"let subChild of child.submenu; let i = index\">\r\n <li (click)=\"invokeNavigate(subChild, child)\"\r\n nz-menu-item>\r\n <span\r\n [class]=\"child?.label?.caption === 'Languages' ? '' : 'menu-row'\">\r\n <ng-container\r\n *ngIf=\"subChild?.option?.changeProject; else antIcon\">\r\n <img [src]=\"subChild?.icon\" alt=\"icon\"\r\n style=\"width: 16px; height: 16px; margin-right: 8px;\" />\r\n </ng-container>\r\n <ng-template #antIcon>\r\n <span nz-icon [nzType]=\"subChild?.icon\"></span>\r\n </ng-template>\r\n <span>{{ subChild?.label?.caption }}</span>\r\n <!-- Delete icon (prevent click bubbling) -->\r\n <span *ngIf=\"child?.label?.caption != 'Languages'\"\r\n nz-icon nzType=\"delete\" class=\"delete-icon\"\r\n style=\"padding-left: 5%;\"\r\n (click)=\"deleteMenuItem(subChild, i, $event)\"></span>\r\n </span>\r\n </li>\r\n </ng-container>\r\n </ul>\r\n </li>\r\n </ng-container>\r\n </ng-container>\r\n </ul>\r\n </li>\r\n </ng-template>\r\n </ul>\r\n @if (!isCollapsed && appVersionInfo?.appVersion) {\r\n <div class=\"app-version-footer\">\r\n <div class=\"app-version-footer__item\">\r\n <span class=\"app-version-footer__label\">Version</span>\r\n <span class=\"app-version-footer__value\">{{ appVersionInfo.appVersion }}</span>\r\n </div>\r\n <div class=\"app-version-footer__item\">\r\n <span class=\"app-version-footer__label\">Build</span>\r\n <span class=\"app-version-footer__value\">{{ appVersionInfo.appBuild || '-' }}</span>\r\n </div>\r\n <div class=\"app-version-footer__item\">\r\n <span class=\"app-version-footer__label\">Build No.</span>\r\n <span class=\"app-version-footer__value\">{{ appVersionInfo.appBuildNumber || '-' }}</span>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n", styles: [":host{position:relative;display:block;flex:0 0 auto;width:250px;height:100%;min-height:0;overflow:visible;transition:width .3s ease}.left-wrap.sidemenu{position:relative;width:250px;height:100%;min-height:0;overflow:visible;transition:width .3s ease,transform .3s ease}.side-menu{position:relative;display:flex;flex-direction:column;width:250px;height:100%;min-height:0;color:#fff;background-color:#0d1b2a;overflow:hidden;transition:width .3s ease,box-shadow .3s ease}:host:has(.left-wrap.sidemenu.collapsed){width:64px}.left-wrap.sidemenu.collapsed,.left-wrap.sidemenu.collapsed .side-menu{width:64px}.menu-separator{flex:0 0 auto;height:1px;margin:15px 12px 6px;background-color:#ffffff26;border:0;list-style:none}.user-profile{flex:0 0 auto;padding:20px 10px 16px;margin-top:40px;text-align:center;border-bottom:1px solid rgba(255,255,255,.1);overflow:hidden;transition:padding .25s ease,margin .25s ease,opacity .25s ease,max-height .25s ease}.user-profile .avatar{display:block;width:64px;height:64px;margin:0 auto 8px;object-fit:cover;border:2px solid #ffffff;border-radius:50%;transition:width .25s ease,height .25s ease,margin .25s ease}.user-profile .details{min-width:0;overflow:hidden;transition:opacity .2s ease}.user-profile .name{color:#fff;font-size:14px;font-weight:600;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.user-profile .email{margin:2px 0 8px;color:#aaa;font-size:12px;line-height:1.4;overflow-wrap:anywhere;word-break:break-word}.user-profile .actions{display:flex;align-items:center;justify-content:center;gap:6px}.user-profile .actions button{margin:0;color:#fff}.left-wrap.sidemenu.collapsed .user-profile{padding:12px 8px}.left-wrap.sidemenu.collapsed .user-profile .avatar{width:40px;height:40px;margin-bottom:0}.left-wrap.sidemenu.collapsed .user-profile .details,.left-wrap.sidemenu.collapsed .user-profile .name,.left-wrap.sidemenu.collapsed .user-profile .email,.left-wrap.sidemenu.collapsed .user-profile .actions{display:none}.side-menu ul[nz-menu]{flex:1 1 auto;width:100%;min-height:0;margin:0;padding:4px 0 8px;overflow-x:hidden;overflow-y:auto;background-color:transparent;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.28) transparent;scroll-behavior:smooth}.side-menu ul[nz-menu]::-webkit-scrollbar{width:6px}.side-menu ul[nz-menu]::-webkit-scrollbar-track{background:transparent}.side-menu ul[nz-menu]::-webkit-scrollbar-thumb{background-color:#ffffff47;border-radius:999px}.side-menu ul[nz-menu]::-webkit-scrollbar-thumb:hover{background-color:#ffffff6b}.menu-row{display:flex;align-items:center;width:100%;min-width:0}.menu-text{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.delete-icon{flex:0 0 auto;margin-left:auto;color:#ffffffb3;cursor:pointer;transition:color .18s ease,transform .18s ease}.delete-icon:hover{color:#ff7875;transform:scale(1.08)}.projects{position:static;flex:0 0 auto;width:100%;margin-top:auto;background-color:#0d1b2a}.side-toggle-handle{position:absolute;top:24px;right:-14px;z-index:10000;display:flex;align-items:center;justify-content:center;width:28px;height:48px;padding:0;color:#fff;background-color:#0d1b2a;border:0;border-radius:0 6px 6px 0;box-shadow:2px 0 6px #00000040;cursor:pointer;transform:translateY(-50%);transition:background-color .2s ease,box-shadow .2s ease}.side-toggle-handle:hover{background-color:#142a40;box-shadow:3px 0 9px #0000004d}.side-toggle-handle:focus-visible{outline:3px solid rgba(24,144,255,.45);outline-offset:2px}.side-toggle-handle span{color:#fff;font-size:14px;line-height:1;transition:transform .25s ease}.collapse-btn{position:absolute;top:8px;right:8px;z-index:10001}:host(:not(.sidemenu-mobile)) .left-wrap.sidemenu.collapsed:hover{transform:translate(0)}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed{width:64px!important}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-item,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title{display:flex!important;align-items:center!important;justify-content:center!important;width:45px!important;height:32px!important;margin-inline:0!important;padding-inline:0!important;overflow:hidden!important}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .ant-menu-title-content,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .menu-text,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .delete-icon,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .ant-menu-submenu-arrow{display:none!important;width:0!important;min-width:0!important;max-width:0!important;margin:0!important;padding:0!important;visibility:hidden!important;overflow:hidden!important;text-overflow:clip!important}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .ant-menu-item-icon,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-item>.anticon,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title>.anticon{display:inline-flex!important;flex:0 0 auto;align-items:center;justify-content:center;width:auto!important;min-width:18px;margin:0!important;padding:0!important;color:inherit;font-size:18px;line-height:1;visibility:visible!important}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .menu-row{display:contents}:host(.sidemenu-mobile){position:absolute;top:0;bottom:0;left:0;z-index:9999;width:250px;height:100%}:host(.sidemenu-mobile):has(.left-wrap.sidemenu.collapsed){width:0}:host(.sidemenu-mobile) .left-wrap.sidemenu{width:250px;height:100%}:host(.sidemenu-mobile) .side-menu,:host(.sidemenu-mobile) .side-menu.open{width:250px;height:100%;box-shadow:8px 0 22px #0000003d}:host(.sidemenu-mobile) .left-wrap.sidemenu.collapsed{width:250px;transform:translate(-250px)}:host(.sidemenu-mobile) .left-wrap.sidemenu.collapsed .side-menu{width:250px;box-shadow:none}:host(.sidemenu-mobile) .left-wrap.sidemenu.collapsed .side-toggle-handle{right:-28px}:host(.sidemenu-mobile) .side-toggle-handle{top:0;right:0;padding:0;transform:none!important}:host(.sidemenu-mobile) .collapse-btn{top:8px;right:8px}:host(.sidemenu-mobile) .user-profile{margin-top:40px}:host(.sidemenu-mobile) .user-profile .avatar{width:48px;height:48px}:host(.sidemenu-mobile) .left-wrap.sidemenu.collapsed .user-profile{max-height:0;padding:0;margin:0;opacity:0;overflow:hidden;border-bottom:0}@media(prefers-reduced-motion:reduce){:host,.left-wrap.sidemenu,.side-menu,.user-profile,.user-profile .avatar,.side-toggle-handle,.side-toggle-handle span{transition:none}.side-menu ul[nz-menu]{scroll-behavior:auto}}.app-version-footer{flex-shrink:0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;padding:10px 12px;border-top:1px solid rgba(255,255,255,.15);color:#ffffffa6;font-size:11px;line-height:1.4;text-align:center}.app-version-footer__item{display:flex;flex-direction:column;min-width:0}.app-version-footer__label{font-size:10px;opacity:.7}.app-version-footer__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pagination-check{position:absolute;top:50%;right:42px;transform:translateY(-50%)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: PlatformModule }, { kind: "ngmodule", type:
|
|
7335
8531
|
// NG-ZORRO
|
|
7336
8532
|
NzMenuModule }, { kind: "directive", type: i11$1.NzMenuDirective, selector: "[nz-menu]", inputs: ["nzInlineIndent", "nzTheme", "nzMode", "nzInlineCollapsed", "nzSelectable"], outputs: ["nzClick"], exportAs: ["nzMenu"] }, { kind: "component", type: i11$1.NzMenuItemComponent, selector: "[nz-menu-item]", inputs: ["nzPaddingLeft", "nzDisabled", "nzSelected", "nzDanger", "nzMatchRouterExact", "nzMatchRouter"], exportAs: ["nzMenuItem"] }, { kind: "component", type: i11$1.NzSubMenuComponent, selector: "[nz-submenu]", inputs: ["nzMenuClassName", "nzPaddingLeft", "nzTitle", "nzIcon", "nzTriggerSubMenuAction", "nzOpen", "nzDisabled", "nzPlacement"], outputs: ["nzOpenChange"], exportAs: ["nzSubmenu"] }, { kind: "ngmodule", type: NzIconModule }, { kind: "directive", type: i11.NzIconDirective, selector: "nz-icon,[nz-icon]", inputs: ["nzSpin", "nzRotate", "nzType", "nzTheme", "nzTwotoneColor", "nzIconfont"], exportAs: ["nzIcon"] }, { kind: "ngmodule", type: NzTooltipModule }, { kind: "directive", type: i13$1.NzTooltipDirective, selector: "[nz-tooltip]", inputs: ["nzTooltipTitle", "nzTooltipTitleContext", "nz-tooltip", "nzTooltipTrigger", "nzTooltipPlacement", "nzTooltipOrigin", "nzTooltipVisible", "nzTooltipMouseEnterDelay", "nzTooltipMouseLeaveDelay", "nzTooltipOverlayClassName", "nzTooltipOverlayStyle", "nzTooltipArrowPointAtCenter", "cdkConnectedOverlayPush", "nzTooltipColor"], outputs: ["nzTooltipVisibleChange"], exportAs: ["nzTooltip"] }, { kind: "ngmodule", type: NzButtonModule }, { kind: "component", type: i13.NzButtonComponent, selector: "button[nz-button], a[nz-button]", inputs: ["nzBlock", "nzGhost", "nzSearch", "nzLoading", "nzDanger", "disabled", "tabIndex", "nzType", "nzShape", "nzSize"], exportAs: ["nzButton"] }, { kind: "directive", type: i14.ɵNzTransitionPatchDirective, selector: "[nz-button], [nz-icon], nz-icon, [nz-menu-item], [nz-submenu], nz-select-top-control, nz-select-placeholder, nz-input-group", inputs: ["hidden"] }, { kind: "ngmodule", type:
|
|
7337
8533
|
// PrimeNG
|
|
@@ -7350,7 +8546,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
7350
8546
|
// PrimeNG
|
|
7351
8547
|
DynamicDialogModule
|
|
7352
8548
|
], template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - new: the active language has a check mark, like the active pagination type\r\n - fix: all check marks (pagination, language) stand in one column at the right edge; the\r\n pagination marks followed their caption at a fixed distance and landed elsewhere on iOS\r\n - change: the version footer has three columns - version, build (source commit) and build\r\n number (appBuildNumber), the three values build.sh and the web Docker build produce -->\r\n<!-- Shuaib 2026-09-24\r\n - change: sign-in visibility checks now include jwtToken alongside keycloak and oktaAuth. -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-09-19\r\n - new: a @contract comment on the option key, singular since 2026-09-16, and on the sign-in\r\n branches, which know keycloak and oktaAuth but not jwtToken -->\r\n<div class=\"left-wrap sidemenu\" [class.collapsed]=\"isCollapsed\"\r\n [ngStyle]=\"{ 'z-index': !isCollapsed && isMobile ? 999999 : 'auto' }\"\r\n #sideMenu>\r\n <!-- Move toggle button OUTSIDE menu container -->\r\n <div class=\"side-toggle-handle\" id=\"toggle-id\" (click)=\"toggleCollapsed()\"\r\n [class.collapsed]=\"isCollapsed\">\r\n <span id=\"toggle-icon\" nz-icon\r\n [nzType]=\"isCollapsed ? 'caret-right' : 'caret-left'\"></span>\r\n </div>\r\n <div class=\"side-menu\" id=\"sidemenuwrap\"\r\n [ngStyle]=\"{ width: (isMobile && isCollapsed) ? '1px' : isCollapsed ? '60px' : '' }\">\r\n <!-- user content -->\r\n <ng-container *ngIf=\"loginedUserInfo && loginedUserInfo?.name\">\r\n <div class=\"user-profile\" [class.collapsed]=\"isCollapsed\">\r\n <img class=\"avatar\" [src]=\"loginedUserInfo?.image\" alt=\"User\" />\r\n <div [class]=\"isMobile ? '' : 'details'\" *ngIf=\"!isCollapsed\">\r\n <div class=\"name\">{{ loginedUserInfo.name }}</div>\r\n <div class=\"email\">{{ loginedUserInfo?.email }}</div>\r\n <div class=\"actions\" *ngIf=\"loginedUserInfo?.actions?.length\">\r\n <button *ngFor=\"let action of loginedUserInfo?.actions\" nz-button\r\n nzType=\"link\" nzSize=\"small\"\r\n (click)=\"invokeNavigate(action)\">\r\n <span>{{ action?.label?.caption }}</span>\r\n <span nz-icon [nzType]=\"action.icon\"></span>\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <!-- @contract decision: this container reads option (singular), like every other one\r\n reality: it read options (plural) until 2026-09-16. A menu file written for the old\r\n spelling is ignored without an error.\r\n\r\n @contract decision: sign-in visibility supports keycloak, oktaAuth and jwtToken.\r\n A JWT project uses the decoded user's login state, like the other providers.\r\n seen: the login type comparisons in the lines below -->\r\n <!-- dynamic menu -->\r\n <ul nz-menu nzMode=\"inline\" nzTheme=\"dark\"\r\n [nzInlineCollapsed]=\"isCollapsed\">\r\n <ng-container *ngFor=\"let item of menuItems\">\r\n <ng-container\r\n *ngIf=\"!item.submenu && item?.option?.action != 'logout'; else submenu\">\r\n <span\r\n [class]=\"environment?.login?.type === 'oktaAuth' || environment?.login?.type === 'keycloak' || environment?.login?.type === 'jwtToken' ? item.option?.login === true ? loginedUserInfo?.name ? '' : 'hidden' : item.option?.login === false ? loginedUserInfo?.name ? 'hidden' : '' : '' : userInfo?.userId ? item.option?.login ? item.option.role ? item.option.role == userInfo.userRole ? '' : 'hidden' : '' : 'hidden' : !item.option?.login ? '' : 'hidden' \">\r\n <li (click)=\"invokeNavigate(item)\" nz-menu-item nz-tooltip\r\n nzTooltipPlacement=\"right\"\r\n [nzTooltipTitle]=\"isCollapsed && !isMobile ? item?.label?.caption : ''\">\r\n <span nz-icon [nzType]=\"item.icon\"></span>\r\n <span>{{ item?.label?.caption }}</span>\r\n </li>\r\n </span>\r\n </ng-container>\r\n <ng-template #submenu>\r\n <span\r\n [class]=\"item.option?.login === true && (environment?.login?.type === 'oktaAuth' || environment?.login?.type === 'keycloak' || environment?.login?.type === 'jwtToken') ? loginedUserInfo?.name ? '' : 'hidden' : item.option?.login === false ? loginedUserInfo?.name ? 'hidden' : '' : ''\">\r\n <li nz-submenu *ngIf=\"item?.option?.action != 'logout'\"\r\n [nzTitle]=\"item?.label?.caption\"\r\n [nzIcon]=\"item.icon\">\r\n <ul>\r\n <ng-container *ngFor=\"let child of item.submenu\">\r\n <li (click)=\"invokeNavigate(child)\" *ngIf=\"!child.submenu\"\r\n nz-menu-item nz-tooltip\r\n nzTooltipPlacement=\"right\"\r\n [nzTooltipTitle]=\"isCollapsed && !isMobile ? child?.label?.caption : ''\">\r\n <span nz-icon [nzType]=\"child?.icon\"></span>\r\n <span>{{ child?.label?.caption }}</span>\r\n </li>\r\n <ng-container *ngIf=\"child.submenu\">\r\n <li nz-submenu [nzTitle]=\"child?.label?.caption\"\r\n [nzIcon]=\"child?.icon\">\r\n <ul>\r\n <ng-container *ngFor=\"let subChild of child.submenu\">\r\n <li (click)=\"invokeNavigate(subChild)\" nz-menu-item>\r\n <ng-container\r\n *ngIf=\"subChild?.option?.setImageUrl; else antIcon\">\r\n <img *ngIf=\"isUrl(subChild?.icon)\"\r\n [src]=\"subChild?.icon\" alt=\"icon\"\r\n style=\"width: 16px; height: 16px; margin-right: 8px;\" />\r\n </ng-container>\r\n <ng-template #antIcon>\r\n <span nz-icon [nzType]=\"subChild?.icon\"></span>\r\n </ng-template>\r\n <span>{{ subChild?.label?.caption }}</span>\r\n @if(subChild?.option?.numberedPagination &&\r\n !scrollPagination){\r\n <span nz-icon\r\n nzType=\"check\" nzTheme=\"outline\"\r\n class=\"pagination-check\">\r\n </span>\r\n }\r\n @if(subChild?.option?.scrollPagination &&\r\n scrollPagination){\r\n <span nz-icon\r\n nzType=\"check\" nzTheme=\"outline\"\r\n class=\"pagination-check\">\r\n </span>\r\n }\r\n @if(subChild?.option?.changeLang &&\r\n subChild?.option?.data_url === activeLanguage){\r\n <span nz-icon nzType=\"check\" nzTheme=\"outline\"\r\n class=\"pagination-check\">\r\n </span>\r\n }\r\n </li>\r\n </ng-container>\r\n </ul>\r\n </li>\r\n </ng-container>\r\n </ng-container>\r\n </ul>\r\n </li>\r\n </span>\r\n </ng-template>\r\n </ng-container>\r\n\r\n <!-- separator -->\r\n <li *ngIf=\"settingMenu.submenu\" class=\"menu-separator\"></li>\r\n\r\n <!-- settings menu -->\r\n <ng-container *ngIf=\"!settingMenu.submenu; else settingSubmenu\"\r\n style=\"position: absolute; bottom: 0;\">\r\n <li (click)=\"invokeNavigate(settingMenu)\" nz-menu-item nz-tooltip\r\n nzTooltipPlacement=\"right\"\r\n [nzTooltipTitle]=\"isCollapsed && !isMobile ? settingMenu?.label?.caption : ''\">\r\n <span nz-icon [nzType]=\"settingMenu.icon\"></span>\r\n <span>{{ settingMenu?.label?.caption }}</span>\r\n </li>\r\n </ng-container>\r\n <ng-template #settingSubmenu>\r\n <li nz-submenu [nzTitle]=\"settingMenu?.label?.caption\"\r\n [nzIcon]=\"settingMenu.icon\">\r\n <ul>\r\n <ng-container *ngFor=\"let child of settingMenu.submenu\">\r\n <li (click)=\"invokeNavigate(child)\" *ngIf=\"!child.submenu\"\r\n nz-menu-item nz-tooltip\r\n nzTooltipPlacement=\"right\"\r\n [nzTooltipTitle]=\"isCollapsed && !isMobile ? child?.label?.caption : ''\">\r\n <span nz-icon [nzType]=\"child?.icon\"></span>\r\n <span>{{ child?.label?.caption }}</span>\r\n </li>\r\n <ng-container *ngIf=\"child.submenu\">\r\n <li nz-submenu [nzTitle]=\"child?.label?.caption\"\r\n [nzIcon]=\"child?.icon\">\r\n <ul style=\"width: 260px;\">\r\n <ng-container\r\n *ngFor=\"let subChild of child.submenu; let i = index\">\r\n <li (click)=\"invokeNavigate(subChild, child)\"\r\n nz-menu-item>\r\n <span\r\n [class]=\"child?.label?.caption === 'Languages' ? '' : 'menu-row'\">\r\n <ng-container\r\n *ngIf=\"subChild?.option?.changeProject; else antIcon\">\r\n <img [src]=\"subChild?.icon\" alt=\"icon\"\r\n style=\"width: 16px; height: 16px; margin-right: 8px;\" />\r\n </ng-container>\r\n <ng-template #antIcon>\r\n <span nz-icon [nzType]=\"subChild?.icon\"></span>\r\n </ng-template>\r\n <span>{{ subChild?.label?.caption }}</span>\r\n <!-- Delete icon (prevent click bubbling) -->\r\n <span *ngIf=\"child?.label?.caption != 'Languages'\"\r\n nz-icon nzType=\"delete\" class=\"delete-icon\"\r\n style=\"padding-left: 5%;\"\r\n (click)=\"deleteMenuItem(subChild, i, $event)\"></span>\r\n </span>\r\n </li>\r\n </ng-container>\r\n </ul>\r\n </li>\r\n </ng-container>\r\n </ng-container>\r\n </ul>\r\n </li>\r\n </ng-template>\r\n </ul>\r\n @if (!isCollapsed && appVersionInfo?.appVersion) {\r\n <div class=\"app-version-footer\">\r\n <div class=\"app-version-footer__item\">\r\n <span class=\"app-version-footer__label\">Version</span>\r\n <span class=\"app-version-footer__value\">{{ appVersionInfo.appVersion }}</span>\r\n </div>\r\n <div class=\"app-version-footer__item\">\r\n <span class=\"app-version-footer__label\">Build</span>\r\n <span class=\"app-version-footer__value\">{{ appVersionInfo.appBuild || '-' }}</span>\r\n </div>\r\n <div class=\"app-version-footer__item\">\r\n <span class=\"app-version-footer__label\">Build No.</span>\r\n <span class=\"app-version-footer__value\">{{ appVersionInfo.appBuildNumber || '-' }}</span>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n", styles: [":host{position:relative;display:block;flex:0 0 auto;width:250px;height:100%;min-height:0;overflow:visible;transition:width .3s ease}.left-wrap.sidemenu{position:relative;width:250px;height:100%;min-height:0;overflow:visible;transition:width .3s ease,transform .3s ease}.side-menu{position:relative;display:flex;flex-direction:column;width:250px;height:100%;min-height:0;color:#fff;background-color:#0d1b2a;overflow:hidden;transition:width .3s ease,box-shadow .3s ease}:host:has(.left-wrap.sidemenu.collapsed){width:64px}.left-wrap.sidemenu.collapsed,.left-wrap.sidemenu.collapsed .side-menu{width:64px}.menu-separator{flex:0 0 auto;height:1px;margin:15px 12px 6px;background-color:#ffffff26;border:0;list-style:none}.user-profile{flex:0 0 auto;padding:20px 10px 16px;margin-top:40px;text-align:center;border-bottom:1px solid rgba(255,255,255,.1);overflow:hidden;transition:padding .25s ease,margin .25s ease,opacity .25s ease,max-height .25s ease}.user-profile .avatar{display:block;width:64px;height:64px;margin:0 auto 8px;object-fit:cover;border:2px solid #ffffff;border-radius:50%;transition:width .25s ease,height .25s ease,margin .25s ease}.user-profile .details{min-width:0;overflow:hidden;transition:opacity .2s ease}.user-profile .name{color:#fff;font-size:14px;font-weight:600;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.user-profile .email{margin:2px 0 8px;color:#aaa;font-size:12px;line-height:1.4;overflow-wrap:anywhere;word-break:break-word}.user-profile .actions{display:flex;align-items:center;justify-content:center;gap:6px}.user-profile .actions button{margin:0;color:#fff}.left-wrap.sidemenu.collapsed .user-profile{padding:12px 8px}.left-wrap.sidemenu.collapsed .user-profile .avatar{width:40px;height:40px;margin-bottom:0}.left-wrap.sidemenu.collapsed .user-profile .details,.left-wrap.sidemenu.collapsed .user-profile .name,.left-wrap.sidemenu.collapsed .user-profile .email,.left-wrap.sidemenu.collapsed .user-profile .actions{display:none}.side-menu ul[nz-menu]{flex:1 1 auto;width:100%;min-height:0;margin:0;padding:4px 0 8px;overflow-x:hidden;overflow-y:auto;background-color:transparent;scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.28) transparent;scroll-behavior:smooth}.side-menu ul[nz-menu]::-webkit-scrollbar{width:6px}.side-menu ul[nz-menu]::-webkit-scrollbar-track{background:transparent}.side-menu ul[nz-menu]::-webkit-scrollbar-thumb{background-color:#ffffff47;border-radius:999px}.side-menu ul[nz-menu]::-webkit-scrollbar-thumb:hover{background-color:#ffffff6b}.menu-row{display:flex;align-items:center;width:100%;min-width:0}.menu-text{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.delete-icon{flex:0 0 auto;margin-left:auto;color:#ffffffb3;cursor:pointer;transition:color .18s ease,transform .18s ease}.delete-icon:hover{color:#ff7875;transform:scale(1.08)}.projects{position:static;flex:0 0 auto;width:100%;margin-top:auto;background-color:#0d1b2a}.side-toggle-handle{position:absolute;top:24px;right:-14px;z-index:10000;display:flex;align-items:center;justify-content:center;width:28px;height:48px;padding:0;color:#fff;background-color:#0d1b2a;border:0;border-radius:0 6px 6px 0;box-shadow:2px 0 6px #00000040;cursor:pointer;transform:translateY(-50%);transition:background-color .2s ease,box-shadow .2s ease}.side-toggle-handle:hover{background-color:#142a40;box-shadow:3px 0 9px #0000004d}.side-toggle-handle:focus-visible{outline:3px solid rgba(24,144,255,.45);outline-offset:2px}.side-toggle-handle span{color:#fff;font-size:14px;line-height:1;transition:transform .25s ease}.collapse-btn{position:absolute;top:8px;right:8px;z-index:10001}:host(:not(.sidemenu-mobile)) .left-wrap.sidemenu.collapsed:hover{transform:translate(0)}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed{width:64px!important}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-item,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-item,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-item-group>.ant-menu-item-group-list>.ant-menu-submenu>.ant-menu-submenu-title{display:flex!important;align-items:center!important;justify-content:center!important;width:45px!important;height:32px!important;margin-inline:0!important;padding-inline:0!important;overflow:hidden!important}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .ant-menu-title-content,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .menu-text,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .delete-icon,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .ant-menu-submenu-arrow{display:none!important;width:0!important;min-width:0!important;max-width:0!important;margin:0!important;padding:0!important;visibility:hidden!important;overflow:hidden!important;text-overflow:clip!important}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .ant-menu-item-icon,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-item>.anticon,:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed>.ant-menu-submenu>.ant-menu-submenu-title>.anticon{display:inline-flex!important;flex:0 0 auto;align-items:center;justify-content:center;width:auto!important;min-width:18px;margin:0!important;padding:0!important;color:inherit;font-size:18px;line-height:1;visibility:visible!important}:host(:not(.sidemenu-mobile)) ::ng-deep .ant-menu.ant-menu-inline-collapsed .menu-row{display:contents}:host(.sidemenu-mobile){position:absolute;top:0;bottom:0;left:0;z-index:9999;width:250px;height:100%}:host(.sidemenu-mobile):has(.left-wrap.sidemenu.collapsed){width:0}:host(.sidemenu-mobile) .left-wrap.sidemenu{width:250px;height:100%}:host(.sidemenu-mobile) .side-menu,:host(.sidemenu-mobile) .side-menu.open{width:250px;height:100%;box-shadow:8px 0 22px #0000003d}:host(.sidemenu-mobile) .left-wrap.sidemenu.collapsed{width:250px;transform:translate(-250px)}:host(.sidemenu-mobile) .left-wrap.sidemenu.collapsed .side-menu{width:250px;box-shadow:none}:host(.sidemenu-mobile) .left-wrap.sidemenu.collapsed .side-toggle-handle{right:-28px}:host(.sidemenu-mobile) .side-toggle-handle{top:0;right:0;padding:0;transform:none!important}:host(.sidemenu-mobile) .collapse-btn{top:8px;right:8px}:host(.sidemenu-mobile) .user-profile{margin-top:40px}:host(.sidemenu-mobile) .user-profile .avatar{width:48px;height:48px}:host(.sidemenu-mobile) .left-wrap.sidemenu.collapsed .user-profile{max-height:0;padding:0;margin:0;opacity:0;overflow:hidden;border-bottom:0}@media(prefers-reduced-motion:reduce){:host,.left-wrap.sidemenu,.side-menu,.user-profile,.user-profile .avatar,.side-toggle-handle,.side-toggle-handle span{transition:none}.side-menu ul[nz-menu]{scroll-behavior:auto}}.app-version-footer{flex-shrink:0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;padding:10px 12px;border-top:1px solid rgba(255,255,255,.15);color:#ffffffa6;font-size:11px;line-height:1.4;text-align:center}.app-version-footer__item{display:flex;flex-direction:column;min-width:0}.app-version-footer__label{font-size:10px;opacity:.7}.app-version-footer__value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pagination-check{position:absolute;top:50%;right:42px;transform:translateY(-50%)}\n"] }]
|
|
7353
|
-
}], ctorParameters: () => [{ type: i1$
|
|
8549
|
+
}], ctorParameters: () => [{ type: i1$5.NzI18nService }, { type: MobileKeycloakService }, { type: Couchdb }, { type: i7.OAuthService }, { type: i1.Platform }, { type: RequestService }, { type: UtilityService }, { type: i1$4.DialogService }, { type: NotificationService }, { type: i0.Injector }], propDecorators: { menuItems: [{
|
|
7354
8550
|
type: Input
|
|
7355
8551
|
}], userInfo: [{
|
|
7356
8552
|
type: Input
|
|
@@ -7538,6 +8734,69 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
7538
8734
|
}]
|
|
7539
8735
|
}], ctorParameters: () => [{ type: i1.Platform }] });
|
|
7540
8736
|
|
|
8737
|
+
// Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-14
|
|
8738
|
+
// - change: login, refresh and logout run through MobileOidcService - the same OpenID Connect flow
|
|
8739
|
+
// as Keycloak; the endpoints come from the issuer's discovery document instead of login.mobile.
|
|
8740
|
+
// The logout follows login.required: a project with an optional login (luv2shop) restarts on its
|
|
8741
|
+
// start page, signed out
|
|
8742
|
+
// Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13
|
|
8743
|
+
// - new: Okta sign-in on the devices with a refresh token. The login asks for offline_access, the
|
|
8744
|
+
// tokens (access, refresh, id) are kept with the user, an expired access token is renewed with
|
|
8745
|
+
// the refresh token (grant_type=refresh_token, public client with PKCE - no secret) and the
|
|
8746
|
+
// logout revokes the refresh token. Before, only the access token was kept: after its lifetime
|
|
8747
|
+
// (1 hour in luv2shop) the backend calls failed until the user signed in again
|
|
8748
|
+
// - new: signOut ends the sign-in without a browser window: refresh and access token are revoked
|
|
8749
|
+
// and the next login asks Okta for the password again (prompt=login). The logout page in the
|
|
8750
|
+
// in-app browser stayed white after its redirect to the app scheme and did not close
|
|
8751
|
+
class MobileOktaService {
|
|
8752
|
+
utilityService;
|
|
8753
|
+
oidc;
|
|
8754
|
+
constructor(utilityService, oidc) {
|
|
8755
|
+
this.utilityService = utilityService;
|
|
8756
|
+
this.oidc = oidc;
|
|
8757
|
+
}
|
|
8758
|
+
/**
|
|
8759
|
+
* The Okta authorization server as an OIDC provider, from login.mobile of the project. The issuer
|
|
8760
|
+
* is login.mobile.issuer, else login.web.issuer, else derived from the authorize URL
|
|
8761
|
+
* (https://…/oauth2/default/v1/authorize → https://…/oauth2/default).
|
|
8762
|
+
*/
|
|
8763
|
+
provider(environment) {
|
|
8764
|
+
const mobile = environment?.login?.mobile ?? {};
|
|
8765
|
+
const issuer = mobile.issuer
|
|
8766
|
+
|| environment?.login?.web?.issuer
|
|
8767
|
+
|| String(mobile.authorizationBaseUrl ?? '').replace(/\/v1\/authorize.*$/, '');
|
|
8768
|
+
return {
|
|
8769
|
+
issuer,
|
|
8770
|
+
clientId: mobile.clientId,
|
|
8771
|
+
redirectUri: mobile.redirectUrl,
|
|
8772
|
+
scope: mobile.scope,
|
|
8773
|
+
// Always: the refresh token keeps the sign-in beyond the access token's lifetime.
|
|
8774
|
+
offlineAccess: true
|
|
8775
|
+
};
|
|
8776
|
+
}
|
|
8777
|
+
login(environment) {
|
|
8778
|
+
return this.oidc.login(this.provider(environment));
|
|
8779
|
+
}
|
|
8780
|
+
refreshTokens(environment, stored) {
|
|
8781
|
+
return this.oidc.refresh(this.provider(environment), stored);
|
|
8782
|
+
}
|
|
8783
|
+
/**
|
|
8784
|
+
* Logout with the shared flow. login.required decides where the restarted app goes: true → the
|
|
8785
|
+
* loader until the sign-in opens, otherwise (default for Okta) the start page, signed out.
|
|
8786
|
+
*/
|
|
8787
|
+
signOut(environment, stored) {
|
|
8788
|
+
return this.oidc.signOut(this.provider(environment), stored, 'loginedUser', key => this.utilityService.removeLocalStorage(key), { loginRequired: environment?.login?.required === true });
|
|
8789
|
+
}
|
|
8790
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MobileOktaService, deps: [{ token: UtilityService }, { token: MobileOidcService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
8791
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MobileOktaService, providedIn: 'root' });
|
|
8792
|
+
}
|
|
8793
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImport: i0, type: MobileOktaService, decorators: [{
|
|
8794
|
+
type: Injectable,
|
|
8795
|
+
args: [{
|
|
8796
|
+
providedIn: 'root'
|
|
8797
|
+
}]
|
|
8798
|
+
}], ctorParameters: () => [{ type: UtilityService }, { type: MobileOidcService }] });
|
|
8799
|
+
|
|
7541
8800
|
// Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07
|
|
7542
8801
|
// - new: letter layouts per language and a symbol page for the custom keyboards. The keyboard had
|
|
7543
8802
|
// one hard-wired QWERTY without umlauts or punctuation, so German text such as a comment could
|
|
@@ -7719,9 +8978,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
7719
8978
|
args: [{ selector: 'vl-custom-keyboard', standalone: true, template: "<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13\r\n - change: the space key has no label (it said \"Space\" in every language); a label per language\r\n would have to be maintained for each new project language -->\r\n<!-- Faramarz Yousefian and Agent Toni (Claude Opus 5) 2026-10-07\r\n - new: a ?123 / ABC key switches string keyboards between letters and the symbol page -->\r\n@if (keyboard.activeSession(); as session) {\r\n<section class=\"custom-keyboard\" data-vl-custom-keyboard [class.custom-keyboard--numeric]=\"\r\n session.type === 'numeric' ||\r\n session.type === 'date' ||\r\n session.type === 'time'\" [class.custom-keyboard--opening]=\"keyboard.interactionBlocked()\"\r\n [attr.aria-label]=\"session.type + ' keyboard'\" (pointerdown)=\"$event.preventDefault()\">\r\n\r\n @for (row of rows; track $index) {\r\n <div class=\"custom-keyboard__row\">\r\n @for (key of row; track key) {\r\n <button type=\"button\" class=\"custom-keyboard__key\" (click)=\"enterKey(key)\">\r\n {{ key }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- One mobile-style action row for every keyboard type -->\r\n <div class=\"custom-keyboard__actions\" [class.custom-keyboard__actions--compact]=\"\r\n session.type !== 'alpha' &&\r\n session.type !== 'string'\r\n \" [class.custom-keyboard__actions--symbols]=\"hasSymbolPage\">\r\n\r\n @if (\r\n session.type === 'alpha' ||\r\n session.type === 'string'\r\n ) {\r\n <button type=\"button\" class=\"custom-keyboard__action-button custom-keyboard__shift\" [class.active]=\"uppercase()\"\r\n [disabled]=\"symbols()\" aria-label=\"Change letter case\" title=\"Shift\" (click)=\"toggleCase()\">\r\n <i class=\"pi pi-arrow-up\"></i>\r\n </button>\r\n\r\n @if (hasSymbolPage) {\r\n <button type=\"button\" class=\"custom-keyboard__action-button custom-keyboard__page\"\r\n [attr.aria-label]=\"symbols() ? 'Letters' : 'Digits and symbols'\" (click)=\"toggleSymbols()\">\r\n {{ symbols() ? 'ABC' : '?123' }}\r\n </button>\r\n }\r\n\r\n <button type=\"button\" class=\"custom-keyboard__action-button custom-keyboard__space\" aria-label=\"Space\" (click)=\"enterSpace()\">\r\n </button>\r\n }\r\n\r\n <button type=\"button\" class=\"custom-keyboard__action-button custom-keyboard__delete\"\r\n aria-label=\"Delete previous character\" title=\"Delete\" (click)=\"keyboard.backspace()\">\r\n <i class=\"pi pi-delete-left\"></i>\r\n </button>\r\n\r\n <button type=\"button\" class=\"custom-keyboard__action-button custom-keyboard__done\" aria-label=\"Done\"\r\n title=\"Done\" (click)=\"keyboard.close()\">\r\n <i class=\"pi pi-check\"></i>\r\n </button>\r\n </div>\r\n</section>\r\n}\r\n", styles: [":host{display:contents}.custom-keyboard{position:fixed;right:0;bottom:0;left:0;z-index:200000;display:block;width:100%;max-height:min(52dvh,390px);padding:8px 6px calc(8px + env(safe-area-inset-bottom,0px));background:var(--vl-keyboard-background, var(--vl-page-background, #ffffff));box-shadow:0 -6px 20px #0f172a38;box-sizing:border-box;overflow-x:hidden;overflow-y:auto;user-select:none;-webkit-user-select:none;touch-action:manipulation;-webkit-overflow-scrolling:touch}.custom-keyboard__row{display:flex;justify-content:center;gap:4px;width:100%;margin-bottom:6px}.custom-keyboard__key{display:flex;flex:1 1 0;width:auto;max-width:52px;min-width:0;height:42px;align-items:center;justify-content:center;padding:0;color:#111827;font-size:16px;font-weight:700;line-height:1;background:#fff;border:1px solid #d1d5db;border-radius:5px;box-shadow:0 1px 2px #0f172a47;cursor:pointer;appearance:none;-webkit-appearance:none;touch-action:manipulation;transition:transform 80ms ease,background-color 80ms ease}.custom-keyboard__key:active{background:#e6f4ff;transform:scale(.96)}.custom-keyboard--numeric .custom-keyboard__key{max-width:92px;height:46px;font-size:18px}.custom-keyboard__actions{display:grid;grid-template-columns:46px minmax(90px,1fr) 46px 54px;gap:6px;width:100%;margin-top:4px;padding:0 4px}.custom-keyboard__actions--compact{grid-template-columns:minmax(52px,1fr) minmax(52px,1fr)}.custom-keyboard__actions--symbols{grid-template-columns:46px 54px minmax(70px,1fr) 46px 54px}.custom-keyboard__action-button{display:flex;width:100%;min-width:0;height:42px;align-items:center;justify-content:center;padding:0 8px;color:#172033;font-size:15px;font-weight:700;line-height:1;border:0;border-radius:6px;box-shadow:0 1px 2px #0f172a40;cursor:pointer;appearance:none;-webkit-appearance:none;touch-action:manipulation;transition:transform 80ms ease,background-color 80ms ease}.custom-keyboard__action-button i{font-size:17px;line-height:1;pointer-events:none}.custom-keyboard__action-button:active{transform:scale(.96)}.custom-keyboard__shift{color:#172033;background:#aeb6c2}.custom-keyboard__shift.active{color:#fff;background:#64748b}.custom-keyboard__space{color:#172033;background:#fff}.custom-keyboard__delete{color:#172033;background:#aeb6c2}.custom-keyboard__done{color:#fff;background:#1890ff}.custom-keyboard__page{color:#172033;font-size:14px;background:#aeb6c2}.custom-keyboard__shift:disabled{opacity:.45}.custom-keyboard__key:focus-visible,.custom-keyboard__action-button:focus-visible{outline:3px solid rgba(24,144,255,.35);outline-offset:1px}@media(max-height:650px){.custom-keyboard{max-height:58dvh;padding-top:6px}.custom-keyboard__row{gap:3px;margin-bottom:4px}.custom-keyboard__key{height:35px;font-size:14px}.custom-keyboard--numeric .custom-keyboard__key{height:38px;font-size:16px}.custom-keyboard__action-button{height:36px}}@media(max-width:360px){.custom-keyboard{padding-right:4px;padding-left:4px}.custom-keyboard__row{gap:3px}.custom-keyboard__key{font-size:14px}.custom-keyboard__actions{grid-template-columns:42px minmax(70px,1fr) 42px 48px;gap:4px;padding-right:2px;padding-left:2px}.custom-keyboard__actions--compact{grid-template-columns:minmax(48px,1fr) minmax(48px,1fr)}.custom-keyboard__actions--symbols{grid-template-columns:40px 48px minmax(56px,1fr) 40px 46px}}.custom-keyboard--opening .custom-keyboard__key,.custom-keyboard--opening .custom-keyboard__action-button{pointer-events:none}.custom-keyboard--opening{cursor:default}::ng-deep .picker-field{position:relative}::ng-deep .picker-field__icon{position:absolute;right:0;top:0;height:100%;width:2.5rem;border:0;background:transparent;cursor:pointer}::ng-deep .picker-field--active input{border-color:var(--modal-field-focus-color, var(--p-primary-color, #10b981))!important;outline:none!important;box-shadow:var(--modal-field-focus-shadow, 0 0 0 2px rgba(16, 185, 129, .16))!important}::ng-deep .p-datepicker-panel.model-datepicker-panel--docked,::ng-deep .model-datepicker-panel--docked{position:fixed!important;background:var(--vl-keyboard-background, var(--vl-page-background, #ffffff))!important;inset:auto 0 0!important;width:100%!important;min-width:100%!important;max-width:100%!important;max-height:min(52dvh,420px)!important;margin:0!important;padding-bottom:env(safe-area-inset-bottom,0px)!important;overflow-y:auto!important;transform:none!important;border-radius:12px 12px 0 0!important;box-shadow:0 -6px 20px #0f172a38!important;box-sizing:border-box!important}::ng-deep .model-timepicker-panel.p-datepicker-panel:not(.model-datepicker-panel--docked){width:auto!important;min-width:0!important;padding:4px 8px!important;border-radius:8px!important}::ng-deep .model-timepicker-panel .p-datepicker-time-picker{gap:4px!important;padding:0!important;border:0!important}::ng-deep .model-timepicker-panel .p-datepicker-hour-picker,::ng-deep .model-timepicker-panel .p-datepicker-minute-picker{gap:0!important}::ng-deep .model-timepicker-panel .p-datepicker-hour-picker span,::ng-deep .model-timepicker-panel .p-datepicker-minute-picker span,::ng-deep .model-timepicker-panel .p-datepicker-separator{font-size:1rem!important;line-height:1.2!important}::ng-deep .model-timepicker-panel .p-datepicker-increment-button,::ng-deep .model-timepicker-panel .p-datepicker-decrement-button{width:2rem!important;height:1.75rem!important}::ng-deep body.vl-side-dock .model-datepicker-panel--docked{inset:0 0 0 auto!important;width:var(--vl-side-dock-width, 50vw)!important;min-width:0!important;max-width:var(--vl-side-dock-width, 50vw)!important;height:100dvh!important;max-height:100dvh!important;min-height:0!important;padding-top:env(safe-area-inset-top,0px)!important;padding-right:env(safe-area-inset-right,0px)!important;border-left:1px solid #e5e7eb!important;box-shadow:-6px 0 20px #0f172a2e!important}.custom-keyboard__key{height:clamp(26px,5.2dvh,42px)}.custom-keyboard--numeric .custom-keyboard__key{height:clamp(28px,5.6dvh,46px)}.custom-keyboard__action-button{height:clamp(28px,5.2dvh,42px)}.custom-keyboard__row{margin-bottom:clamp(3px,.8dvh,6px)}::ng-deep body.vl-side-dock .custom-keyboard{inset:0 0 0 auto;width:var(--vl-side-dock-width, 50vw);max-height:none;height:100dvh;display:flex;flex-direction:column;justify-content:flex-start;padding-top:env(safe-area-inset-top,0px);padding-right:max(6px,env(safe-area-inset-right,0px));box-shadow:-6px 0 20px #0f172a2e}.custom-keyboard,::ng-deep .model-datepicker-panel--docked{border-top-left-radius:var(--p-dialog-border-radius, 12px)!important;border-top-right-radius:var(--p-dialog-border-radius, 12px)!important;overflow:hidden}::ng-deep .model-datepicker-panel--docked .p-datepicker-header{padding-top:clamp(2px,.6dvh,8px)!important;padding-bottom:clamp(2px,.6dvh,8px)!important}::ng-deep .model-datepicker-panel--docked .p-datepicker-day-cell,::ng-deep .model-datepicker-panel--docked .p-datepicker-weekday-cell{padding:1px!important}::ng-deep .model-datepicker-panel--docked .p-datepicker-day{width:var(--vl-day-size, clamp(28px, 4.4dvh, 40px))!important;height:var(--vl-day-size, clamp(28px, 4.4dvh, 40px))!important}::ng-deep body.vl-side-dock .model-datepicker-panel--docked,::ng-deep body.vl-side-dock .custom-keyboard{top:var(--vl-side-dock-top, max(8px, env(safe-area-inset-top, 0px)))!important;bottom:var(--vl-side-dock-bottom, max(8px, env(safe-area-inset-bottom, 0px)))!important;right:max(8px,env(safe-area-inset-right,0px))!important;left:auto!important;width:calc(var(--vl-side-dock-width, 50vw) - max(8px,env(safe-area-inset-right,0px)))!important;max-width:none!important;height:auto!important;max-height:none!important;padding-top:6px!important;padding-right:6px!important;border-radius:var(--p-dialog-border-radius, 12px)!important;overflow:hidden!important;box-shadow:var(--p-dialog-shadow, 0 20px 25px -5px rgba(0, 0, 0, .1), 0 8px 10px -6px rgba(0, 0, 0, .1))!important}::ng-deep body.vl-custom-keyboard-open .custom-keyboard,::ng-deep body.vl-custom-keyboard-open .model-datepicker-panel--docked{left:var(--vl-dock-left, 0px)!important;right:var(--vl-dock-right, 0px)!important;width:auto!important;min-width:0!important;max-width:none!important;bottom:var(--vl-dock-bottom-gap)!important;padding-bottom:8px!important;border-radius:var(--p-dialog-border-radius, 12px)!important;box-shadow:var(--p-dialog-shadow, 0 20px 25px -5px rgba(0, 0, 0, .1), 0 8px 10px -6px rgba(0, 0, 0, .1))!important}::ng-deep body{--vl-dock-bottom-gap: max(8px, env(safe-area-inset-bottom, 0px))}\n"] }]
|
|
7720
8979
|
}], ctorParameters: () => [] });
|
|
7721
8980
|
|
|
8981
|
+
// Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-14
|
|
8982
|
+
// - new: exports MobileOidcService (one OpenID Connect flow for Keycloak and Okta on the devices)
|
|
7722
8983
|
// Faramarz Yousefian and Agent Toni (Claude Opus 5.5) 2026-10-13
|
|
7723
8984
|
// - new: exports native-platform (isNativeMobile/isNativePhone), PlaceholderPipe,
|
|
7724
8985
|
// RequestTimeoutService and RemoteListService
|
|
8986
|
+
// - new: exports MobileOktaService (Okta sign-in on the devices with refresh token)
|
|
7725
8987
|
/*
|
|
7726
8988
|
* Public API Surface of view-toolkit
|
|
7727
8989
|
*/
|
|
@@ -7730,5 +8992,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.2", ngImpor
|
|
|
7730
8992
|
* Generated bundle index. Do not edit.
|
|
7731
8993
|
*/
|
|
7732
8994
|
|
|
7733
|
-
export { AppVersionResetService, CardComponent, ConfigService, Couchdb, CustomKeyboardComponent, CustomKeyboardDirective, CustomKeyboardService, FormComponent, GridComponent, HeaderMenuComponent, LogsComponent, MobileKeycloakService, ModelComponent, NetworkLogService, NotificationService, PaginationComponent, PlaceholderPipe, REQUEST_STAGES, RemoteListService, RequestService, RequestTimeoutError, RequestTimeoutService, SIDE_DOCK_CLASS, SideMenuComponent, UtilityService, ViewToolkitModule, isNativeMobile, isNativePhone, normalizeCustomKeyboardType, resolveCustomKeyboardType, resolvePlaceholder, sideDockWidth, stagedRequest };
|
|
8995
|
+
export { AppVersionResetService, CardComponent, ConfigService, Couchdb, CustomKeyboardComponent, CustomKeyboardDirective, CustomKeyboardService, FormComponent, GridComponent, HeaderMenuComponent, LogsComponent, MobileKeycloakService, MobileOidcService, MobileOktaService, ModelComponent, NetworkLogService, NotificationService, PaginationComponent, PlaceholderPipe, REQUEST_STAGES, RemoteListService, RequestService, RequestTimeoutError, RequestTimeoutService, SIDE_DOCK_CLASS, SideMenuComponent, UtilityService, ViewToolkitModule, isNativeMobile, isNativePhone, normalizeCustomKeyboardType, resolveCustomKeyboardType, resolvePlaceholder, sideDockWidth, stagedRequest };
|
|
7734
8996
|
//# sourceMappingURL=yomotech-view-toolkit.mjs.map
|