@mintplayer/ng-spark 22.0.6 → 22.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/mintplayer-ng-spark-models.mjs +13 -1
- package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
- package/fesm2022/mintplayer-ng-spark-po-form.mjs +225 -149
- package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
- package/package.json +1 -1
- package/types/mintplayer-ng-spark-models.d.ts +16 -1
- package/types/mintplayer-ng-spark-po-form.d.ts +81 -26
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, input,
|
|
3
|
-
import * as i1 from '@angular/common';
|
|
2
|
+
import { inject, input, output, signal, computed, ChangeDetectionStrategy, Component, model, effect } from '@angular/core';
|
|
3
|
+
import * as i1$1 from '@angular/common';
|
|
4
4
|
import { CommonModule, NgTemplateOutlet, NgComponentOutlet } from '@angular/common';
|
|
5
|
-
import * as
|
|
5
|
+
import * as i1 from '@angular/forms';
|
|
6
6
|
import { FormsModule } from '@angular/forms';
|
|
7
7
|
import { moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle, CdkDragPreview } from '@angular/cdk/drag-drop';
|
|
8
8
|
import { Color } from '@mintplayer/ng-bootstrap';
|
|
@@ -14,16 +14,218 @@ import { BsButtonTypeDirective } from '@mintplayer/ng-bootstrap/button-type';
|
|
|
14
14
|
import { BsSelectComponent, BsSelectOption } from '@mintplayer/ng-bootstrap/select';
|
|
15
15
|
import { InMemoryTreeSelectProvider, BsTreeSelectComponent } from '@mintplayer/ng-bootstrap/tree-select';
|
|
16
16
|
import { BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective } from '@mintplayer/ng-bootstrap/modal';
|
|
17
|
-
import { DatatableSettings, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective } from '@mintplayer/ng-bootstrap/datatable';
|
|
18
17
|
import { BsCheckboxComponent } from '@mintplayer/ng-bootstrap/checkbox';
|
|
19
18
|
import { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';
|
|
20
19
|
import { BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective } from '@mintplayer/ng-bootstrap/tab-control';
|
|
21
20
|
import { BsTableComponent } from '@mintplayer/ng-bootstrap/table';
|
|
22
21
|
import { SparkService, SparkLanguageService } from '@mintplayer/ng-spark/services';
|
|
23
|
-
import { TranslateKeyPipe, ResolveTranslationPipe,
|
|
24
|
-
import { ELookupDisplayType, hasShowedOnFlag, ShowedOn
|
|
22
|
+
import { TranslateKeyPipe, ResolveTranslationPipe, ReferenceAttrValuePipe, InputTypePipe, LookupDisplayTypePipe, LookupOptionsPipe, AsDetailDisplayValuePipe, AsDetailTypePipe, AsDetailColumnsPipe, AsDetailCellValuePipe, CanCreateDetailRowPipe, CanDeleteDetailRowPipe, InlineRefOptionsPipe, ErrorForAttributePipe } from '@mintplayer/ng-spark/pipes';
|
|
23
|
+
import { resolveTranslation, ELookupDisplayType, EReferenceDisplayType, hasShowedOnFlag, ShowedOn } from '@mintplayer/ng-spark/models';
|
|
25
24
|
import { SparkIconComponent } from '@mintplayer/ng-spark/icon';
|
|
26
25
|
import { SPARK_ATTRIBUTE_RENDERERS } from '@mintplayer/ng-spark/renderers';
|
|
26
|
+
import { DatatableSettings, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective } from '@mintplayer/ng-bootstrap/datatable';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Reusable Reference value picker: a readonly textbox showing the current selection's
|
|
30
|
+
* breadcrumb/name plus a "…" button that opens a searchable modal grid over the candidate
|
|
31
|
+
* `options`. Per-instance state (no single top-level slot), so it works for top-level
|
|
32
|
+
* reference attributes and for individual inline AsDetail reference cells alike.
|
|
33
|
+
*
|
|
34
|
+
* The parent pre-loads `options` (the query result) and passes `referenceType` (the target
|
|
35
|
+
* CLR type); the component lazily loads that EntityType's metadata on first open to render
|
|
36
|
+
* the grid's column headers. Emits the picked id via `valueChange`.
|
|
37
|
+
*/
|
|
38
|
+
class SparkReferencePickerComponent {
|
|
39
|
+
sparkService = inject(SparkService);
|
|
40
|
+
lang = inject(SparkLanguageService);
|
|
41
|
+
/** Currently selected referenced id (or null when unset). */
|
|
42
|
+
value = input(null, /* @ts-ignore */
|
|
43
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
44
|
+
/** Candidate items to pick from (the full query result, pre-loaded by the parent). */
|
|
45
|
+
options = input([], /* @ts-ignore */
|
|
46
|
+
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
47
|
+
/** Target entity type's CLR type name; used to load the grid's column headers. */
|
|
48
|
+
referenceType = input(undefined, /* @ts-ignore */
|
|
49
|
+
...(ngDevMode ? [{ debugName: "referenceType" }] : /* istanbul ignore next */ []));
|
|
50
|
+
/** Renders the field with the Bootstrap is-invalid state. */
|
|
51
|
+
isInvalid = input(false, /* @ts-ignore */
|
|
52
|
+
...(ngDevMode ? [{ debugName: "isInvalid" }] : /* istanbul ignore next */ []));
|
|
53
|
+
/** Optional id forwarded to the readonly input (for label association). */
|
|
54
|
+
inputId = input(undefined, /* @ts-ignore */
|
|
55
|
+
...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
|
|
56
|
+
/** Optional resolved label appended to the modal header ("Select {title}"). */
|
|
57
|
+
title = input('', /* @ts-ignore */
|
|
58
|
+
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
59
|
+
valueChange = output();
|
|
60
|
+
colors = Color;
|
|
61
|
+
showModal = signal(false, /* @ts-ignore */
|
|
62
|
+
...(ngDevMode ? [{ debugName: "showModal" }] : /* istanbul ignore next */ []));
|
|
63
|
+
entityType = signal(null, /* @ts-ignore */
|
|
64
|
+
...(ngDevMode ? [{ debugName: "entityType" }] : /* istanbul ignore next */ []));
|
|
65
|
+
pagination = signal(undefined, /* @ts-ignore */
|
|
66
|
+
...(ngDevMode ? [{ debugName: "pagination" }] : /* istanbul ignore next */ []));
|
|
67
|
+
settings = signal(new DatatableSettings({
|
|
68
|
+
perPage: { values: [10, 25, 50], selected: 10 },
|
|
69
|
+
page: { values: [1], selected: 1 },
|
|
70
|
+
sortColumns: []
|
|
71
|
+
}), /* @ts-ignore */
|
|
72
|
+
...(ngDevMode ? [{ debugName: "settings" }] : /* istanbul ignore next */ []));
|
|
73
|
+
searchTerm = '';
|
|
74
|
+
visibleAttributes = computed(() => {
|
|
75
|
+
return this.entityType()?.attributes
|
|
76
|
+
.filter(a => a.isVisible)
|
|
77
|
+
.sort((a, b) => a.order - b.order) || [];
|
|
78
|
+
}, /* @ts-ignore */
|
|
79
|
+
...(ngDevMode ? [{ debugName: "visibleAttributes" }] : /* istanbul ignore next */ []));
|
|
80
|
+
// Typed rows so the datatable generic infers PersistentObject.
|
|
81
|
+
rows = computed(() => this.pagination()?.data ?? [], /* @ts-ignore */
|
|
82
|
+
...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
83
|
+
displayValue = computed(() => {
|
|
84
|
+
const id = this.value();
|
|
85
|
+
if (!id)
|
|
86
|
+
return this.lang.t('notSelected');
|
|
87
|
+
const selected = this.options().find(o => o.id === id);
|
|
88
|
+
return selected?.breadcrumb || selected?.name || id;
|
|
89
|
+
}, /* @ts-ignore */
|
|
90
|
+
...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
91
|
+
async open() {
|
|
92
|
+
this.searchTerm = '';
|
|
93
|
+
this.settings.set(new DatatableSettings({
|
|
94
|
+
perPage: { values: [10, 25, 50], selected: 10 },
|
|
95
|
+
page: { values: [1], selected: 1 },
|
|
96
|
+
sortColumns: []
|
|
97
|
+
}));
|
|
98
|
+
// Lazily resolve the target entity type for the grid's column headers.
|
|
99
|
+
const refType = this.referenceType();
|
|
100
|
+
if (!this.entityType() && refType) {
|
|
101
|
+
const types = await this.sparkService.getEntityTypes();
|
|
102
|
+
this.entityType.set(types.find(t => t.clrType === refType) || null);
|
|
103
|
+
}
|
|
104
|
+
this.applyFilter();
|
|
105
|
+
this.showModal.set(true);
|
|
106
|
+
}
|
|
107
|
+
onSearchChange() {
|
|
108
|
+
this.settings().page.selected = 1;
|
|
109
|
+
this.applyFilter();
|
|
110
|
+
}
|
|
111
|
+
applyFilter() {
|
|
112
|
+
let filteredItems = this.options();
|
|
113
|
+
if (this.searchTerm.trim()) {
|
|
114
|
+
const term = this.searchTerm.toLowerCase().trim();
|
|
115
|
+
filteredItems = this.options().filter(item => {
|
|
116
|
+
if (item.name?.toLowerCase().includes(term))
|
|
117
|
+
return true;
|
|
118
|
+
if (item.breadcrumb?.toLowerCase().includes(term))
|
|
119
|
+
return true;
|
|
120
|
+
return item.attributes.some(attr => {
|
|
121
|
+
const value = attr.breadcrumb || attr.value;
|
|
122
|
+
if (value == null)
|
|
123
|
+
return false;
|
|
124
|
+
return String(value).toLowerCase().includes(term);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const totalPages = Math.ceil(filteredItems.length / this.settings().perPage.selected) || 1;
|
|
129
|
+
this.pagination.set({
|
|
130
|
+
data: filteredItems,
|
|
131
|
+
totalRecords: filteredItems.length,
|
|
132
|
+
totalPages: totalPages,
|
|
133
|
+
perPage: this.settings().perPage.selected,
|
|
134
|
+
page: this.settings().page.selected
|
|
135
|
+
});
|
|
136
|
+
this.settings().page.values = Array.from({ length: totalPages }, (_, i) => i + 1);
|
|
137
|
+
if (this.settings().page.selected > totalPages) {
|
|
138
|
+
this.settings().page.selected = 1;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
clearSearch() {
|
|
142
|
+
this.searchTerm = '';
|
|
143
|
+
this.onSearchChange();
|
|
144
|
+
}
|
|
145
|
+
select(item) {
|
|
146
|
+
this.valueChange.emit(item.id ?? null);
|
|
147
|
+
this.close();
|
|
148
|
+
}
|
|
149
|
+
close() {
|
|
150
|
+
this.showModal.set(false);
|
|
151
|
+
this.searchTerm = '';
|
|
152
|
+
}
|
|
153
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkReferencePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
154
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkReferencePickerComponent, isStandalone: true, selector: "spark-reference-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, referenceType: { classPropertyName: "referenceType", publicName: "referenceType", isSignal: true, isRequired: false, transformFunction: null }, isInvalid: { classPropertyName: "isInvalid", publicName: "isInvalid", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<bs-input-group>\n <input\n type=\"text\"\n [id]=\"inputId()\"\n [value]=\"displayValue()\"\n readonly\n [class.is-invalid]=\"isInvalid()\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"open()\">\n ...\n </button>\n</bs-input-group>\n\n<bs-modal [isOpen]=\"showModal()\" (isOpenChange)=\"!$event && close()\">\n <div *bsModal class=\"reference-modal\">\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.select' | t }} {{ title() }}</h5>\n </div>\n\n <div bsModalBody>\n <!-- bs-modal portals its content into a CDK overlay outside the host form, so the\n modal needs its own <bs-form> context for inputs to pick up .form-control styles. -->\n <bs-form>\n @if (entityType()) {\n <!-- Search box -->\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"6\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'common.search' | t\"\n [(ngModel)]=\"searchTerm\"\n (ngModelChange)=\"onSearchChange()\">\n @if (searchTerm) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSearch()\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n <div [md]=\"6\" class=\"text-end\">\n @if (searchTerm && pagination()) {\n <span class=\"text-muted\">\n {{ pagination()!.totalRecords }} {{ pagination()!.totalRecords === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n\n <bs-datatable\n [(settings)]=\"settings\"\n [data]=\"rows()\"\n (rowClick)=\"select($event.row)\">\n @for (attr of visibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ attr.label | resolveTranslation:attr.name }}\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @for (attr of visibleAttributes(); track attr.id) {\n <td>{{ $any(item) | referenceAttrValue:attr.name }}</td>\n }\n </ng-container>\n </bs-datatable>\n } @else {\n <div class=\"text-center p-3\">\n <bs-spinner />\n </div>\n }\n </bs-form>\n </div>\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"close()\">{{ 'common.cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsGridComponent, selector: "bs-grid", inputs: ["stopFullWidthAt"] }, { kind: "directive", type: BsGridRowDirective, selector: "[bsRow]" }, { kind: "directive", type: BsGridColumnDirective, selector: "[xxs],[xs],[sm],[md],[lg],[xl],[xxl]", inputs: ["xxs", "xs", "sm", "md", "lg", "xl", "xxl"] }, { kind: "component", type: BsInputGroupComponent, selector: "bs-input-group" }, { kind: "directive", type: BsButtonTypeDirective, selector: "button[color],input[type=\"button\"][color],input[type=\"submit\"][color],a[color]", inputs: ["color"] }, { kind: "component", type: BsModalHostComponent, selector: "bs-modal", inputs: ["isOpen", "closeOnEscape", "scrollable"], outputs: ["isOpenChange"] }, { kind: "directive", type: BsModalDirective, selector: "[bsModal]" }, { kind: "directive", type: BsModalHeaderDirective, selector: "[bsModalHeader]" }, { kind: "directive", type: BsModalBodyDirective, selector: "[bsModalBody]" }, { kind: "directive", type: BsModalFooterDirective, selector: "[bsModalFooter]" }, { kind: "component", type: BsDatatableComponent, selector: "bs-datatable", inputs: ["columns", "data", "fetch", "settings", "selectionMode", "selectable", "selection", "rowKey", "resizableColumns", "pagination", "virtualScroll", "itemSize", "virtualBuffer", "isResponsive", "compareWith", "tree", "idKey", "childCountKey", "treeIndent", "expandedIds", "selectionStrategy"], outputs: ["settingsChange", "selectionChange", "rowClick", "rowDblClick", "rowContextMenu", "expandedIdsChange", "rowExpand", "rowCollapse"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable"] }, { kind: "directive", type: BsRowTemplateDirective, selector: "[bsRowTemplate]" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: ReferenceAttrValuePipe, name: "referenceAttrValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
155
|
+
}
|
|
156
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkReferencePickerComponent, decorators: [{
|
|
157
|
+
type: Component,
|
|
158
|
+
args: [{ selector: 'spark-reference-picker', imports: [FormsModule, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsInputGroupComponent, BsButtonTypeDirective, BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsSpinnerComponent, SparkIconComponent, TranslateKeyPipe, ResolveTranslationPipe, ReferenceAttrValuePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-input-group>\n <input\n type=\"text\"\n [id]=\"inputId()\"\n [value]=\"displayValue()\"\n readonly\n [class.is-invalid]=\"isInvalid()\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"open()\">\n ...\n </button>\n</bs-input-group>\n\n<bs-modal [isOpen]=\"showModal()\" (isOpenChange)=\"!$event && close()\">\n <div *bsModal class=\"reference-modal\">\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.select' | t }} {{ title() }}</h5>\n </div>\n\n <div bsModalBody>\n <!-- bs-modal portals its content into a CDK overlay outside the host form, so the\n modal needs its own <bs-form> context for inputs to pick up .form-control styles. -->\n <bs-form>\n @if (entityType()) {\n <!-- Search box -->\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"6\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'common.search' | t\"\n [(ngModel)]=\"searchTerm\"\n (ngModelChange)=\"onSearchChange()\">\n @if (searchTerm) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSearch()\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n <div [md]=\"6\" class=\"text-end\">\n @if (searchTerm && pagination()) {\n <span class=\"text-muted\">\n {{ pagination()!.totalRecords }} {{ pagination()!.totalRecords === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n\n <bs-datatable\n [(settings)]=\"settings\"\n [data]=\"rows()\"\n (rowClick)=\"select($event.row)\">\n @for (attr of visibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ attr.label | resolveTranslation:attr.name }}\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @for (attr of visibleAttributes(); track attr.id) {\n <td>{{ $any(item) | referenceAttrValue:attr.name }}</td>\n }\n </ng-container>\n </bs-datatable>\n } @else {\n <div class=\"text-center p-3\">\n <bs-spinner />\n </div>\n }\n </bs-form>\n </div>\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"close()\">{{ 'common.cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n" }]
|
|
159
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], referenceType: [{ type: i0.Input, args: [{ isSignal: true, alias: "referenceType", required: false }] }], isInvalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "isInvalid", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Reusable LookupReference value picker: a readonly textbox showing the current selection's
|
|
163
|
+
* translated label plus a "…" button that opens a searchable modal list over the active
|
|
164
|
+
* `options`. Per-instance state, so it serves both top-level lookup attributes (display type
|
|
165
|
+
* Modal) and individual inline AsDetail lookup cells. Emits the picked key via `valueChange`.
|
|
166
|
+
*/
|
|
167
|
+
class SparkLookupPickerComponent {
|
|
168
|
+
/** Currently selected lookup key (or null when unset). */
|
|
169
|
+
value = input(null, /* @ts-ignore */
|
|
170
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
171
|
+
/** Active lookup values to pick from. */
|
|
172
|
+
options = input([], /* @ts-ignore */
|
|
173
|
+
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
174
|
+
/** Renders the field with the Bootstrap is-invalid state. */
|
|
175
|
+
isInvalid = input(false, /* @ts-ignore */
|
|
176
|
+
...(ngDevMode ? [{ debugName: "isInvalid" }] : /* istanbul ignore next */ []));
|
|
177
|
+
/** Optional id forwarded to the readonly input (for label association). */
|
|
178
|
+
inputId = input(undefined, /* @ts-ignore */
|
|
179
|
+
...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
|
|
180
|
+
/** Optional resolved label appended to the modal header ("Select {title}"). */
|
|
181
|
+
title = input('', /* @ts-ignore */
|
|
182
|
+
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
183
|
+
valueChange = output();
|
|
184
|
+
colors = Color;
|
|
185
|
+
showModal = signal(false, /* @ts-ignore */
|
|
186
|
+
...(ngDevMode ? [{ debugName: "showModal" }] : /* istanbul ignore next */ []));
|
|
187
|
+
searchTerm = signal('', /* @ts-ignore */
|
|
188
|
+
...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
|
|
189
|
+
displayValue = computed(() => {
|
|
190
|
+
const key = this.value();
|
|
191
|
+
if (key == null || key === '')
|
|
192
|
+
return '';
|
|
193
|
+
const selected = this.options().find(o => o.key === String(key));
|
|
194
|
+
if (!selected)
|
|
195
|
+
return String(key);
|
|
196
|
+
return resolveTranslation(selected.values) || selected.key;
|
|
197
|
+
}, /* @ts-ignore */
|
|
198
|
+
...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
199
|
+
filteredItems = computed(() => {
|
|
200
|
+
if (!this.searchTerm().trim()) {
|
|
201
|
+
return this.options();
|
|
202
|
+
}
|
|
203
|
+
const term = this.searchTerm().toLowerCase().trim();
|
|
204
|
+
return this.options().filter(item => {
|
|
205
|
+
const translation = resolveTranslation(item.values);
|
|
206
|
+
return translation.toLowerCase().includes(term) || item.key.toLowerCase().includes(term);
|
|
207
|
+
});
|
|
208
|
+
}, /* @ts-ignore */
|
|
209
|
+
...(ngDevMode ? [{ debugName: "filteredItems" }] : /* istanbul ignore next */ []));
|
|
210
|
+
open() {
|
|
211
|
+
this.searchTerm.set('');
|
|
212
|
+
this.showModal.set(true);
|
|
213
|
+
}
|
|
214
|
+
select(item) {
|
|
215
|
+
this.valueChange.emit(item.key);
|
|
216
|
+
this.close();
|
|
217
|
+
}
|
|
218
|
+
close() {
|
|
219
|
+
this.showModal.set(false);
|
|
220
|
+
this.searchTerm.set('');
|
|
221
|
+
}
|
|
222
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkLookupPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
223
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkLookupPickerComponent, isStandalone: true, selector: "spark-lookup-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, isInvalid: { classPropertyName: "isInvalid", publicName: "isInvalid", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<bs-input-group>\n <input\n type=\"text\"\n [id]=\"inputId()\"\n [value]=\"displayValue()\"\n readonly\n [class.is-invalid]=\"isInvalid()\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"open()\">\n ...\n </button>\n</bs-input-group>\n\n<bs-modal [isOpen]=\"showModal()\" (isOpenChange)=\"!$event && close()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.select' | t }} {{ title() }}</h5>\n </div>\n\n <div bsModalBody>\n <!-- bs-modal portals its content into a CDK overlay outside the host form, so the\n modal needs its own <bs-form> context for inputs to pick up .form-control styles. -->\n <bs-form>\n <!-- Search box -->\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [col]>\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'common.search' | t\"\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\">\n @if (searchTerm()) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"searchTerm.set('')\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n </div>\n </bs-grid>\n\n <!-- List of items -->\n <bs-table [striped]=\"true\" [hover]=\"true\">\n <tbody class=\"align-middle\">\n @for (item of filteredItems(); track item.key) {\n <tr\n [class.table-primary]=\"value() === item.key\"\n (click)=\"select(item)\"\n style=\"cursor: pointer;\">\n <td>{{ item.values | resolveTranslation:item.key }}</td>\n </tr>\n } @empty {\n <tr>\n <td class=\"text-center text-muted\">{{ 'common.noItemsFound' | t }}</td>\n </tr>\n }\n </tbody>\n </bs-table>\n </bs-form>\n </div>\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"close()\">{{ 'common.cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsGridComponent, selector: "bs-grid", inputs: ["stopFullWidthAt"] }, { kind: "directive", type: BsGridRowDirective, selector: "[bsRow]" }, { kind: "directive", type: BsGridColDirective, selector: "[col]", inputs: ["col"] }, { kind: "component", type: BsInputGroupComponent, selector: "bs-input-group" }, { kind: "directive", type: BsButtonTypeDirective, selector: "button[color],input[type=\"button\"][color],input[type=\"submit\"][color],a[color]", inputs: ["color"] }, { kind: "component", type: BsModalHostComponent, selector: "bs-modal", inputs: ["isOpen", "closeOnEscape", "scrollable"], outputs: ["isOpenChange"] }, { kind: "directive", type: BsModalDirective, selector: "[bsModal]" }, { kind: "directive", type: BsModalHeaderDirective, selector: "[bsModalHeader]" }, { kind: "directive", type: BsModalBodyDirective, selector: "[bsModalBody]" }, { kind: "directive", type: BsModalFooterDirective, selector: "[bsModalFooter]" }, { kind: "component", type: BsTableComponent, selector: "bs-table", inputs: ["isResponsive", "striped", "hover", "border", "ariaRowCount"] }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
224
|
+
}
|
|
225
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkLookupPickerComponent, decorators: [{
|
|
226
|
+
type: Component,
|
|
227
|
+
args: [{ selector: 'spark-lookup-picker', imports: [FormsModule, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColDirective, BsInputGroupComponent, BsButtonTypeDirective, BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective, BsTableComponent, SparkIconComponent, TranslateKeyPipe, ResolveTranslationPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-input-group>\n <input\n type=\"text\"\n [id]=\"inputId()\"\n [value]=\"displayValue()\"\n readonly\n [class.is-invalid]=\"isInvalid()\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"open()\">\n ...\n </button>\n</bs-input-group>\n\n<bs-modal [isOpen]=\"showModal()\" (isOpenChange)=\"!$event && close()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.select' | t }} {{ title() }}</h5>\n </div>\n\n <div bsModalBody>\n <!-- bs-modal portals its content into a CDK overlay outside the host form, so the\n modal needs its own <bs-form> context for inputs to pick up .form-control styles. -->\n <bs-form>\n <!-- Search box -->\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [col]>\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'common.search' | t\"\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\">\n @if (searchTerm()) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"searchTerm.set('')\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n </div>\n </bs-grid>\n\n <!-- List of items -->\n <bs-table [striped]=\"true\" [hover]=\"true\">\n <tbody class=\"align-middle\">\n @for (item of filteredItems(); track item.key) {\n <tr\n [class.table-primary]=\"value() === item.key\"\n (click)=\"select(item)\"\n style=\"cursor: pointer;\">\n <td>{{ item.values | resolveTranslation:item.key }}</td>\n </tr>\n } @empty {\n <tr>\n <td class=\"text-center text-muted\">{{ 'common.noItemsFound' | t }}</td>\n </tr>\n }\n </tbody>\n </bs-table>\n </bs-form>\n </div>\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"close()\">{{ 'common.cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n" }]
|
|
228
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], isInvalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "isInvalid", required: false }] }], inputId: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
|
|
27
229
|
|
|
28
230
|
class SparkPoFormComponent {
|
|
29
231
|
sparkService = inject(SparkService);
|
|
@@ -75,34 +277,11 @@ class SparkPoFormComponent {
|
|
|
75
277
|
// Reference options for columns within array AsDetail types (keyed by parent attr name, then column name)
|
|
76
278
|
asDetailReferenceOptions = signal({}, /* @ts-ignore */
|
|
77
279
|
...(ngDevMode ? [{ debugName: "asDetailReferenceOptions" }] : /* istanbul ignore next */ []));
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
showReferenceModal = signal(false, /* @ts-ignore */
|
|
82
|
-
...(ngDevMode ? [{ debugName: "showReferenceModal" }] : /* istanbul ignore next */ []));
|
|
83
|
-
referenceModalItems = signal([], /* @ts-ignore */
|
|
84
|
-
...(ngDevMode ? [{ debugName: "referenceModalItems" }] : /* istanbul ignore next */ []));
|
|
85
|
-
referenceModalEntityType = signal(null, /* @ts-ignore */
|
|
86
|
-
...(ngDevMode ? [{ debugName: "referenceModalEntityType" }] : /* istanbul ignore next */ []));
|
|
87
|
-
referenceModalPagination = signal(undefined, /* @ts-ignore */
|
|
88
|
-
...(ngDevMode ? [{ debugName: "referenceModalPagination" }] : /* istanbul ignore next */ []));
|
|
89
|
-
referenceModalSettings = signal(new DatatableSettings({
|
|
90
|
-
perPage: { values: [10, 25, 50], selected: 10 },
|
|
91
|
-
page: { values: [1], selected: 1 },
|
|
92
|
-
sortColumns: []
|
|
93
|
-
}), /* @ts-ignore */
|
|
94
|
-
...(ngDevMode ? [{ debugName: "referenceModalSettings" }] : /* istanbul ignore next */ []));
|
|
95
|
-
referenceSearchTerm = '';
|
|
96
|
-
// Modal state for LookupReference selection (Modal display type)
|
|
97
|
-
editingLookupAttr = signal(null, /* @ts-ignore */
|
|
98
|
-
...(ngDevMode ? [{ debugName: "editingLookupAttr" }] : /* istanbul ignore next */ []));
|
|
99
|
-
showLookupModal = signal(false, /* @ts-ignore */
|
|
100
|
-
...(ngDevMode ? [{ debugName: "showLookupModal" }] : /* istanbul ignore next */ []));
|
|
101
|
-
lookupModalItems = signal([], /* @ts-ignore */
|
|
102
|
-
...(ngDevMode ? [{ debugName: "lookupModalItems" }] : /* istanbul ignore next */ []));
|
|
103
|
-
lookupSearchTerm = signal('', /* @ts-ignore */
|
|
104
|
-
...(ngDevMode ? [{ debugName: "lookupSearchTerm" }] : /* istanbul ignore next */ []));
|
|
280
|
+
// Reference/Lookup picking is owned by the standalone spark-reference-picker /
|
|
281
|
+
// spark-lookup-picker components (per-instance modal state); the form just feeds them
|
|
282
|
+
// options and writes the emitted value back into formData / the row.
|
|
105
283
|
ELookupDisplayType = ELookupDisplayType;
|
|
284
|
+
EReferenceDisplayType = EReferenceDisplayType;
|
|
106
285
|
editableAttributes = computed(() => {
|
|
107
286
|
return this.entityType()?.attributes
|
|
108
287
|
.filter(a => a.isVisible && !a.isReadOnly && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))
|
|
@@ -137,27 +316,6 @@ class SparkPoFormComponent {
|
|
|
137
316
|
attrsForGroup(group) {
|
|
138
317
|
return this.editableAttributes().filter(a => a.group === group.id);
|
|
139
318
|
}
|
|
140
|
-
referenceVisibleAttributes = computed(() => {
|
|
141
|
-
return this.referenceModalEntityType()?.attributes
|
|
142
|
-
.filter(a => a.isVisible)
|
|
143
|
-
.sort((a, b) => a.order - b.order) || [];
|
|
144
|
-
}, /* @ts-ignore */
|
|
145
|
-
...(ngDevMode ? [{ debugName: "referenceVisibleAttributes" }] : /* istanbul ignore next */ []));
|
|
146
|
-
// Typed rows for the reference-modal datatable so its generic infers
|
|
147
|
-
// PersistentObject (a `?? []` inline binding would degrade to `unknown`).
|
|
148
|
-
referenceModalRows = computed(() => this.referenceModalPagination()?.data ?? [], /* @ts-ignore */
|
|
149
|
-
...(ngDevMode ? [{ debugName: "referenceModalRows" }] : /* istanbul ignore next */ []));
|
|
150
|
-
filteredLookupItems = computed(() => {
|
|
151
|
-
if (!this.lookupSearchTerm().trim()) {
|
|
152
|
-
return this.lookupModalItems();
|
|
153
|
-
}
|
|
154
|
-
const term = this.lookupSearchTerm().toLowerCase().trim();
|
|
155
|
-
return this.lookupModalItems().filter(item => {
|
|
156
|
-
const translation = resolveTranslation(item.values);
|
|
157
|
-
return translation.toLowerCase().includes(term) || item.key.toLowerCase().includes(term);
|
|
158
|
-
});
|
|
159
|
-
}, /* @ts-ignore */
|
|
160
|
-
...(ngDevMode ? [{ debugName: "filteredLookupItems" }] : /* istanbul ignore next */ []));
|
|
161
319
|
constructor() {
|
|
162
320
|
effect(() => {
|
|
163
321
|
const et = this.entityType();
|
|
@@ -287,34 +445,21 @@ class SparkPoFormComponent {
|
|
|
287
445
|
}));
|
|
288
446
|
this.lookupReferenceOptions.set(this.toRecord(entries));
|
|
289
447
|
}
|
|
290
|
-
getReferenceOptions(attr) {
|
|
291
|
-
return this.referenceOptions()[attr.name] || [];
|
|
292
|
-
}
|
|
293
448
|
getLookupOptions(attr) {
|
|
294
449
|
const lookupRef = attr.lookupReferenceType ? this.lookupReferenceOptions()[attr.lookupReferenceType] : null;
|
|
295
450
|
return lookupRef?.values.filter(v => v.isActive) || [];
|
|
296
451
|
}
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
this.
|
|
301
|
-
|
|
302
|
-
this.
|
|
303
|
-
}
|
|
304
|
-
selectLookupItem(item) {
|
|
305
|
-
const attr = this.editingLookupAttr();
|
|
306
|
-
if (attr) {
|
|
307
|
-
const data = { ...this.formData() };
|
|
308
|
-
data[attr.name] = item.key;
|
|
309
|
-
this.formData.set(data);
|
|
310
|
-
}
|
|
311
|
-
this.closeLookupModal();
|
|
452
|
+
// Write the value emitted by a top-level spark-reference-picker / spark-lookup-picker
|
|
453
|
+
// back into formData (the same write the old in-form modal selectors performed).
|
|
454
|
+
onReferenceValueChange(attr, id) {
|
|
455
|
+
const data = { ...this.formData() };
|
|
456
|
+
data[attr.name] = id;
|
|
457
|
+
this.formData.set(data);
|
|
312
458
|
}
|
|
313
|
-
|
|
314
|
-
this.
|
|
315
|
-
|
|
316
|
-
this.
|
|
317
|
-
this.lookupSearchTerm.set('');
|
|
459
|
+
onLookupValueChange(attr, key) {
|
|
460
|
+
const data = { ...this.formData() };
|
|
461
|
+
data[attr.name] = key;
|
|
462
|
+
this.formData.set(data);
|
|
318
463
|
}
|
|
319
464
|
getEditRendererComponent(attr) {
|
|
320
465
|
if (!attr.renderer)
|
|
@@ -466,86 +611,17 @@ class SparkPoFormComponent {
|
|
|
466
611
|
data[attr.name] = arr;
|
|
467
612
|
this.formData.set(data);
|
|
468
613
|
}
|
|
469
|
-
// Reference modal methods
|
|
470
|
-
async openReferenceSelector(attr) {
|
|
471
|
-
this.editingReferenceAttr.set(attr);
|
|
472
|
-
this.referenceSearchTerm = '';
|
|
473
|
-
this.referenceModalItems.set(this.getReferenceOptions(attr));
|
|
474
|
-
const types = await this.sparkService.getEntityTypes();
|
|
475
|
-
this.referenceModalEntityType.set(types.find(t => t.clrType === attr.referenceType) || null);
|
|
476
|
-
this.referenceModalSettings.set(new DatatableSettings({
|
|
477
|
-
perPage: { values: [10, 25, 50], selected: 10 },
|
|
478
|
-
page: { values: [1], selected: 1 },
|
|
479
|
-
sortColumns: []
|
|
480
|
-
}));
|
|
481
|
-
this.applyReferenceFilter();
|
|
482
|
-
this.showReferenceModal.set(true);
|
|
483
|
-
}
|
|
484
|
-
onReferenceSearchChange() {
|
|
485
|
-
this.referenceModalSettings().page.selected = 1;
|
|
486
|
-
this.applyReferenceFilter();
|
|
487
|
-
}
|
|
488
|
-
applyReferenceFilter() {
|
|
489
|
-
let filteredItems = this.referenceModalItems();
|
|
490
|
-
if (this.referenceSearchTerm.trim()) {
|
|
491
|
-
const term = this.referenceSearchTerm.toLowerCase().trim();
|
|
492
|
-
filteredItems = this.referenceModalItems().filter(item => {
|
|
493
|
-
if (item.name?.toLowerCase().includes(term))
|
|
494
|
-
return true;
|
|
495
|
-
if (item.breadcrumb?.toLowerCase().includes(term))
|
|
496
|
-
return true;
|
|
497
|
-
return item.attributes.some(attr => {
|
|
498
|
-
const value = attr.breadcrumb || attr.value;
|
|
499
|
-
if (value == null)
|
|
500
|
-
return false;
|
|
501
|
-
return String(value).toLowerCase().includes(term);
|
|
502
|
-
});
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
const totalPages = Math.ceil(filteredItems.length / this.referenceModalSettings().perPage.selected) || 1;
|
|
506
|
-
this.referenceModalPagination.set({
|
|
507
|
-
data: filteredItems,
|
|
508
|
-
totalRecords: filteredItems.length,
|
|
509
|
-
totalPages: totalPages,
|
|
510
|
-
perPage: this.referenceModalSettings().perPage.selected,
|
|
511
|
-
page: this.referenceModalSettings().page.selected
|
|
512
|
-
});
|
|
513
|
-
this.referenceModalSettings().page.values = Array.from({ length: totalPages }, (_, i) => i + 1);
|
|
514
|
-
if (this.referenceModalSettings().page.selected > totalPages) {
|
|
515
|
-
this.referenceModalSettings().page.selected = 1;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
clearReferenceSearch() {
|
|
519
|
-
this.referenceSearchTerm = '';
|
|
520
|
-
this.onReferenceSearchChange();
|
|
521
|
-
}
|
|
522
|
-
selectReferenceItem(item) {
|
|
523
|
-
const attr = this.editingReferenceAttr();
|
|
524
|
-
if (attr) {
|
|
525
|
-
const data = { ...this.formData() };
|
|
526
|
-
data[attr.name] = item.id;
|
|
527
|
-
this.formData.set(data);
|
|
528
|
-
}
|
|
529
|
-
this.closeReferenceModal();
|
|
530
|
-
}
|
|
531
|
-
closeReferenceModal() {
|
|
532
|
-
this.showReferenceModal.set(false);
|
|
533
|
-
this.editingReferenceAttr.set(null);
|
|
534
|
-
this.referenceModalItems.set([]);
|
|
535
|
-
this.referenceModalEntityType.set(null);
|
|
536
|
-
this.referenceSearchTerm = '';
|
|
537
|
-
}
|
|
538
614
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkPoFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
539
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkPoFormComponent, isStandalone: true, selector: "spark-po-form", inputs: { entityType: { classPropertyName: "entityType", publicName: "entityType", isSignal: true, isRequired: false, transformFunction: null }, formData: { classPropertyName: "formData", publicName: "formData", isSignal: true, isRequired: false, transformFunction: null }, validationErrors: { classPropertyName: "validationErrors", publicName: "validationErrors", isSignal: true, isRequired: false, transformFunction: null }, showButtons: { classPropertyName: "showButtons", publicName: "showButtons", isSignal: true, isRequired: false, transformFunction: null }, isSaving: { classPropertyName: "isSaving", publicName: "isSaving", isSignal: true, isRequired: false, transformFunction: null }, parentId: { classPropertyName: "parentId", publicName: "parentId", isSignal: true, isRequired: false, transformFunction: null }, parentType: { classPropertyName: "parentType", publicName: "parentType", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formData: "formDataChange", save: "save", cancel: "cancel" }, ngImport: i0, template: "<bs-form>\n @if (entityType()) {\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"tabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #tabContent let-tab>\n @if (tab.id === '__default__') {\n @if (ungroupedAttributes().length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n @for (attr of ungroupedAttributes(); track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #attrField let-attr>\n <div bsRow class=\"mb-3\">\n <label [md]=\"4\" bsColFormLabel [for]=\"attr.name\">\n {{ attr.label | resolveTranslation:attr.name }}\n @if (attr.isRequired) {\n <span class=\"text-danger\">*</span>\n }\n </label>\n <div [md]=\"8\">\n @if (attr.dataType === 'boolean') {\n <bs-checkbox\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\">\n </bs-checkbox>\n } @else if (attr.lookupReferenceType) {\n @if ((attr | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | lookupDisplayValue:formData():lookupReferenceOptions()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openLookupSelector(attr)\">\n ...\n </button>\n </bs-input-group>\n } @else {\n <bs-select\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\"\n [id]=\"attr.name\"\n [class.is-invalid]=\"hasError(attr.name)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | lookupOptions:lookupReferenceOptions()); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n <bs-tree-select\n mode=\"multiple\"\n variant=\"textbox\"\n [showClear]=\"true\"\n [placeholder]=\"'common.search' | t\"\n [provider]=\"getReferenceProvider(attr)\"\n [ngModel]=\"referenceTreeValues()[attr.name] || []\"\n (ngModelChange)=\"onReferenceTreeChange(attr, $event)\"\n [ngModelOptions]=\"{ standalone: true }\">\n </bs-tree-select>\n } @else if (attr.dataType === 'Reference') {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | referenceDisplayValue:formData():referenceOptions()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openReferenceSelector(attr)\">\n ...\n </button>\n </bs-input-group>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray && attr.editMode === 'inline') {\n <!-- One source of truth for an inline cell, reused by the row and by the drag\n preview. The preview is a live Angular view (not a cloneNode), so its\n <bs-select> binds to row[col.name] and shows the value while dragging. -->\n <ng-template #inlineDetailCell let-row=\"row\" let-col=\"col\" let-attr=\"attr\" let-rowIndex=\"rowIndex\">\n <td>\n @if (col.isReadOnly) {\n <span class=\"text-muted\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n } @else if (getAsDetailCellEditRenderer(col); as editRenderer) {\n <ng-container *ngComponentOutlet=\"editRenderer; inputs: getAsDetailCellEditRendererInputs(row, col)\"></ng-container>\n } @else if (col.dataType === 'AsDetail') {\n <!-- Nested AsDetail can't flatten into a row: escalate to the row modal,\n where the recursive spark-po-form renders the nested structure. -->\n <div class=\"d-flex align-items-center gap-1\">\n <span class=\"text-truncate flex-grow-1\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\" (click)=\"editArrayItem(attr, rowIndex)\">\n <spark-icon name=\"pencil\" />\n </button>\n </div>\n } @else if (col.dataType === 'boolean') {\n <bs-checkbox\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n </bs-checkbox>\n } @else if (col.dataType === 'Reference' && col.query) {\n <bs-select\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | inlineRefOptions:col:asDetailReferenceOptions()); track option.id) {\n <option [ngValue]=\"option.id\">\n {{ option.breadcrumb || option.name || option.id }}\n </option>\n }\n </bs-select>\n } @else if (col.lookupReferenceType) {\n <bs-select\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of getLookupOptions(col); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n } @else {\n <input\n [type]=\"col.dataType | inputType\"\n [(ngModel)]=\"row[col.name]\"\n [required]=\"col.isRequired\"\n [step]=\"col.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\"\n (ngModelChange)=\"onFieldChange()\">\n }\n @if (inlineErrorMessage(attr, rowIndex, col); as errorMsg) {\n <div class=\"invalid-feedback d-block\">{{ errorMsg }}</div>\n }\n </td>\n </ng-template>\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 50px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index; let rowIndex = $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n <td class=\"text-nowrap\">\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"removeArrayItem(attr, rowIndex)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n\n <!-- Floating preview = a LIVE view of the row's cells (not CDK's cloneNode),\n so <bs-select> shows its value instead of blanking. The in-grid\n placeholder still uses CDK's default clone (exact row height) and is\n hidden via .cdk-drag-placeholder { visibility:hidden } in the styles,\n so the drop gap is blank without collapsing/shifting the other rows. -->\n <ng-template cdkDragPreview>\n <table class=\"table mb-0 bg-body shadow-sm spark-drag-preview\">\n <tbody class=\"align-middle\">\n <tr>\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"width: 32px\"><spark-icon name=\"grip-vertical\" /></td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n </tr>\n </tbody>\n </table>\n </ng-template>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addInlineRow(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 80px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (getAsDetailCellRendererComponent(col); as cellRenderer) {\n <ng-container *ngComponentOutlet=\"cellRenderer; inputs: getAsDetailCellRendererInputs(row, col)\"></ng-container>\n } @else {\n {{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}\n }\n </td>\n }\n <td class=\"text-nowrap\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary me-1\" (click)=\"editArrayItem(attr, $index)\">\n <spark-icon name=\"pencil\" />\n </button>\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"removeArrayItem(attr, $index)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addArrayItem(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail') {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | asDetailDisplayValue:formData():asDetailTypes()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openAsDetailEditor(attr)\">\n <spark-icon name=\"pencil\" />\n </button>\n </bs-input-group>\n } @else if (getEditRendererComponent(attr); as editComp) {\n <ng-container *ngComponentOutlet=\"editComp; inputs: getEditRendererInputs(attr)\"></ng-container>\n } @else {\n <input\n [type]=\"attr.dataType | inputType\"\n [id]=\"attr.name\"\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\"\n [name]=\"attr.name\"\n [required]=\"attr.isRequired\"\n [step]=\"attr.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasError(attr.name)\">\n }\n @if (attr.name | errorForAttribute:validationErrors(); as errorMsg) {\n <div class=\"invalid-feedback d-block\">\n {{ errorMsg }}\n </div>\n }\n </div>\n </div>\n </ng-template>\n\n @if (showButtons()) {\n <div bsRow class=\"mt-4\">\n <div [md]=\"4\"></div>\n <div [md]=\"8\" class=\"d-flex justify-content-end gap-2\">\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"onCancel()\" [disabled]=\"isSaving()\">{{ 'common.cancel' | t }}</button>\n <button type=\"submit\" [color]=\"colors.primary\" [disabled]=\"isSaving()\" (click)=\"onSave()\">\n @if (isSaving()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'common.save' | t }}\n </button>\n </div>\n </div>\n }\n </bs-grid>\n}\n\n<!-- Modal for editing AsDetail objects -->\n<bs-modal [isOpen]=\"showAsDetailModal()\" (isOpenChange)=\"!$event && closeAsDetailModal()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.edit' | t }} {{ editingAsDetailAttr()?.label | resolveTranslation:editingAsDetailAttr()?.name }}</h5>\n </div>\n\n @if (editingAsDetailAttr(); as attr) {\n <div bsModalBody>\n <spark-po-form\n [entityType]=\"attr | asDetailType:asDetailTypes()\"\n [(formData)]=\"asDetailFormData\"\n [parentId]=\"parentId()\"\n [parentType]=\"parentType()\">\n </spark-po-form>\n </div>\n }\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeAsDetailModal()\">{{ 'common.cancel' | t }}</button>\n <button type=\"button\" [color]=\"colors.primary\" (click)=\"saveAsDetailObject()\">{{ 'common.save' | t }}</button>\n </div>\n </div>\n</bs-modal>\n\n<!-- Modal for selecting Reference items -->\n<bs-modal [isOpen]=\"showReferenceModal()\" (isOpenChange)=\"!$event && closeReferenceModal()\">\n <div *bsModal class=\"reference-modal\">\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.select' | t }} {{ editingReferenceAttr()?.label | resolveTranslation:editingReferenceAttr()?.name }}</h5>\n </div>\n\n <div bsModalBody>\n @if (referenceModalEntityType()) {\n <!-- Search box -->\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"6\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'common.search' | t\"\n [(ngModel)]=\"referenceSearchTerm\"\n (ngModelChange)=\"onReferenceSearchChange()\">\n @if (referenceSearchTerm) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearReferenceSearch()\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n <div [md]=\"6\" class=\"text-end\">\n @if (referenceSearchTerm && referenceModalPagination()) {\n <span class=\"text-muted\">\n {{ referenceModalPagination()!.totalRecords }} {{ referenceModalPagination()!.totalRecords === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n\n <bs-datatable\n [(settings)]=\"referenceModalSettings\"\n [data]=\"referenceModalRows()\"\n (rowClick)=\"selectReferenceItem($event.row)\">\n @for (attr of referenceVisibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ attr.label | resolveTranslation:attr.name }}\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @for (attr of referenceVisibleAttributes(); track attr.id) {\n <td>{{ $any(item) | referenceAttrValue:attr.name }}</td>\n }\n </ng-container>\n </bs-datatable>\n } @else {\n <div class=\"text-center p-3\">\n <bs-spinner />\n </div>\n }\n </div>\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeReferenceModal()\">{{ 'common.cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n\n<!-- Modal for selecting LookupReference items -->\n<bs-modal [isOpen]=\"showLookupModal()\" (isOpenChange)=\"!$event && closeLookupModal()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.select' | t }} {{ editingLookupAttr()?.label | resolveTranslation:editingLookupAttr()?.name }}</h5>\n </div>\n\n <div bsModalBody>\n <!-- Search box -->\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [col]>\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'common.search' | t\"\n [ngModel]=\"lookupSearchTerm()\"\n (ngModelChange)=\"lookupSearchTerm.set($event)\">\n @if (lookupSearchTerm()) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"lookupSearchTerm.set('')\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n </div>\n </bs-grid>\n\n <!-- List of items -->\n <bs-table [striped]=\"true\" [hover]=\"true\">\n <tbody class=\"align-middle\">\n @for (item of filteredLookupItems(); track item.key) {\n <tr\n [class.table-primary]=\"formData()[editingLookupAttr()?.name ?? ''] === item.key\"\n (click)=\"selectLookupItem(item)\"\n style=\"cursor: pointer;\">\n <td>{{ item.values | resolveTranslation:item.key }}</td>\n </tr>\n } @empty {\n <tr>\n <td class=\"text-center text-muted\">{{ 'common.noItemsFound' | t }}</td>\n </tr>\n }\n </tbody>\n </bs-table>\n </div>\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeLookupModal()\">{{ 'common.cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n</bs-form>\n", styles: [":host ::ng-deep .cdk-drag-placeholder{visibility:hidden}\n"], dependencies: [{ kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving", "parentId", "parentType"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { 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: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["color", "outline"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["color", "navStyle"] }, { kind: "component", type: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsGridComponent, selector: "bs-grid", inputs: ["stopFullWidthAt"] }, { kind: "directive", type: BsGridRowDirective, selector: "[bsRow]" }, { kind: "directive", type: BsGridColumnDirective, selector: "[xxs],[xs],[sm],[md],[lg],[xl],[xxl]", inputs: ["xxs", "xs", "sm", "md", "lg", "xl", "xxl"] }, { kind: "directive", type: BsGridColDirective, selector: "[col]", inputs: ["col"] }, { kind: "directive", type: BsColFormLabelDirective, selector: "[bsColFormLabel]" }, { kind: "directive", type: BsButtonTypeDirective, selector: "button[color],input[type=\"button\"][color],input[type=\"submit\"][color],a[color]", inputs: ["color"] }, { kind: "component", type: BsInputGroupComponent, selector: "bs-input-group" }, { kind: "component", type: BsSelectComponent, selector: "bs-select", inputs: ["identifier", "size", "multiple", "numberVisible", "disabled", "ariaLabel"] }, { kind: "directive", type: BsSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "component", type: BsTreeSelectComponent, selector: "bs-tree-select", inputs: ["provider", "mode", "variant", "cascadeSelect", "placeholder", "showClear", "panelScrollHeight", "searchDebounceMs", "disabled", "value"], outputs: ["valueChange", "opened", "closed", "cleared"] }, { kind: "component", type: BsModalHostComponent, selector: "bs-modal", inputs: ["isOpen", "closeOnEscape", "scrollable"], outputs: ["isOpenChange"] }, { kind: "directive", type: BsModalDirective, selector: "[bsModal]" }, { kind: "directive", type: BsModalHeaderDirective, selector: "[bsModalHeader]" }, { kind: "directive", type: BsModalBodyDirective, selector: "[bsModalBody]" }, { kind: "directive", type: BsModalFooterDirective, selector: "[bsModalFooter]" }, { kind: "component", type: BsDatatableComponent, selector: "bs-datatable", inputs: ["columns", "data", "fetch", "settings", "selectionMode", "selectable", "selection", "rowKey", "resizableColumns", "pagination", "virtualScroll", "itemSize", "virtualBuffer", "isResponsive", "compareWith", "tree", "idKey", "childCountKey", "treeIndent", "expandedIds", "selectionStrategy"], outputs: ["settingsChange", "selectionChange", "rowClick", "rowDblClick", "rowContextMenu", "expandedIdsChange", "rowExpand", "rowCollapse"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable"] }, { kind: "directive", type: BsRowTemplateDirective, selector: "[bsRowTemplate]" }, { kind: "component", type: BsTableComponent, selector: "bs-table", inputs: ["isResponsive", "striped", "hover", "border", "ariaRowCount"] }, { kind: "component", type: BsCheckboxComponent, selector: "bs-checkbox", inputs: ["type", "isToggled", "indeterminate", "name", "value", "group"], outputs: ["isToggledChange", "indeterminateChange"] }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: BsTabControlComponent, selector: "bs-tab-control", inputs: ["border", "selectFirstTab", "tabsPosition"] }, { kind: "component", type: BsTabPageComponent, selector: "bs-tab-page", inputs: ["disabled"] }, { kind: "directive", type: BsTabPageHeaderDirective, selector: "[bsTabPageHeader]" }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: InputTypePipe, name: "inputType" }, { kind: "pipe", type: LookupDisplayValuePipe, name: "lookupDisplayValue" }, { kind: "pipe", type: LookupDisplayTypePipe, name: "lookupDisplayType" }, { kind: "pipe", type: LookupOptionsPipe, name: "lookupOptions" }, { kind: "pipe", type: ReferenceDisplayValuePipe, name: "referenceDisplayValue" }, { kind: "pipe", type: AsDetailDisplayValuePipe, name: "asDetailDisplayValue" }, { kind: "pipe", type: AsDetailTypePipe, name: "asDetailType" }, { kind: "pipe", type: AsDetailColumnsPipe, name: "asDetailColumns" }, { kind: "pipe", type: AsDetailCellValuePipe, name: "asDetailCellValue" }, { kind: "pipe", type: CanCreateDetailRowPipe, name: "canCreateDetailRow" }, { kind: "pipe", type: CanDeleteDetailRowPipe, name: "canDeleteDetailRow" }, { kind: "pipe", type: InlineRefOptionsPipe, name: "inlineRefOptions" }, { kind: "pipe", type: ReferenceAttrValuePipe, name: "referenceAttrValue" }, { kind: "pipe", type: ErrorForAttributePipe, name: "errorForAttribute" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
615
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: SparkPoFormComponent, isStandalone: true, selector: "spark-po-form", inputs: { entityType: { classPropertyName: "entityType", publicName: "entityType", isSignal: true, isRequired: false, transformFunction: null }, formData: { classPropertyName: "formData", publicName: "formData", isSignal: true, isRequired: false, transformFunction: null }, validationErrors: { classPropertyName: "validationErrors", publicName: "validationErrors", isSignal: true, isRequired: false, transformFunction: null }, showButtons: { classPropertyName: "showButtons", publicName: "showButtons", isSignal: true, isRequired: false, transformFunction: null }, isSaving: { classPropertyName: "isSaving", publicName: "isSaving", isSignal: true, isRequired: false, transformFunction: null }, parentId: { classPropertyName: "parentId", publicName: "parentId", isSignal: true, isRequired: false, transformFunction: null }, parentType: { classPropertyName: "parentType", publicName: "parentType", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formData: "formDataChange", save: "save", cancel: "cancel" }, ngImport: i0, template: "<bs-form>\n @if (entityType()) {\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"tabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #tabContent let-tab>\n @if (tab.id === '__default__') {\n @if (ungroupedAttributes().length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n @for (attr of ungroupedAttributes(); track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #attrField let-attr>\n <div bsRow class=\"mb-3\">\n <label [md]=\"4\" bsColFormLabel [for]=\"attr.name\">\n {{ attr.label | resolveTranslation:attr.name }}\n @if (attr.isRequired) {\n <span class=\"text-danger\">*</span>\n }\n </label>\n <div [md]=\"8\">\n @if (attr.dataType === 'boolean') {\n <bs-checkbox\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\">\n </bs-checkbox>\n } @else if (attr.lookupReferenceType) {\n @if ((attr | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <spark-lookup-picker\n [value]=\"formData()[attr.name]\"\n [options]=\"getLookupOptions(attr)\"\n [inputId]=\"attr.name\"\n [title]=\"attr.label | resolveTranslation:attr.name\"\n [isInvalid]=\"hasError(attr.name)\"\n (valueChange)=\"onLookupValueChange(attr, $event)\">\n </spark-lookup-picker>\n } @else {\n <bs-select\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\"\n [id]=\"attr.name\"\n [class.is-invalid]=\"hasError(attr.name)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | lookupOptions:lookupReferenceOptions()); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n <bs-tree-select\n mode=\"multiple\"\n variant=\"textbox\"\n [showClear]=\"true\"\n [placeholder]=\"'common.search' | t\"\n [provider]=\"getReferenceProvider(attr)\"\n [ngModel]=\"referenceTreeValues()[attr.name] || []\"\n (ngModelChange)=\"onReferenceTreeChange(attr, $event)\"\n [ngModelOptions]=\"{ standalone: true }\">\n </bs-tree-select>\n } @else if (attr.dataType === 'Reference') {\n <spark-reference-picker\n [value]=\"formData()[attr.name]\"\n [options]=\"referenceOptions()[attr.name] || []\"\n [referenceType]=\"attr.referenceType\"\n [inputId]=\"attr.name\"\n [title]=\"attr.label | resolveTranslation:attr.name\"\n [isInvalid]=\"hasError(attr.name)\"\n (valueChange)=\"onReferenceValueChange(attr, $event)\">\n </spark-reference-picker>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray && attr.editMode === 'inline') {\n <!-- One source of truth for an inline cell, reused by the row and by the drag\n preview. The preview is a live Angular view (not a cloneNode), so its\n <bs-select> binds to row[col.name] and shows the value while dragging. -->\n <ng-template #inlineDetailCell let-row=\"row\" let-col=\"col\" let-attr=\"attr\" let-rowIndex=\"rowIndex\">\n <td>\n @if (col.isReadOnly) {\n <span class=\"text-muted\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n } @else if (getAsDetailCellEditRenderer(col); as editRenderer) {\n <ng-container *ngComponentOutlet=\"editRenderer; inputs: getAsDetailCellEditRendererInputs(row, col)\"></ng-container>\n } @else if (col.dataType === 'AsDetail') {\n <!-- Nested AsDetail can't flatten into a row: escalate to the row modal,\n where the recursive spark-po-form renders the nested structure. -->\n <div class=\"d-flex align-items-center gap-1\">\n <span class=\"text-truncate flex-grow-1\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\" (click)=\"editArrayItem(attr, rowIndex)\">\n <spark-icon name=\"pencil\" />\n </button>\n </div>\n } @else if (col.dataType === 'boolean') {\n <bs-checkbox\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n </bs-checkbox>\n } @else if (col.dataType === 'Reference' && col.query) {\n @if (col.referenceDisplayType === EReferenceDisplayType.Modal) {\n <spark-reference-picker\n [value]=\"row[col.name]\"\n [options]=\"(attr | inlineRefOptions:col:asDetailReferenceOptions())\"\n [referenceType]=\"col.referenceType\"\n [title]=\"col.label | resolveTranslation:col.name\"\n [isInvalid]=\"hasInlineError(attr, rowIndex, col)\"\n (valueChange)=\"row[col.name] = $event; onFieldChange()\">\n </spark-reference-picker>\n } @else {\n <bs-select\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | inlineRefOptions:col:asDetailReferenceOptions()); track option.id) {\n <option [ngValue]=\"option.id\">\n {{ option.breadcrumb || option.name || option.id }}\n </option>\n }\n </bs-select>\n }\n } @else if (col.lookupReferenceType) {\n @if ((col | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <spark-lookup-picker\n [value]=\"row[col.name]\"\n [options]=\"getLookupOptions(col)\"\n [title]=\"col.label | resolveTranslation:col.name\"\n [isInvalid]=\"hasInlineError(attr, rowIndex, col)\"\n (valueChange)=\"row[col.name] = $event; onFieldChange()\">\n </spark-lookup-picker>\n } @else {\n <bs-select\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of getLookupOptions(col); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else {\n <input\n [type]=\"col.dataType | inputType\"\n [(ngModel)]=\"row[col.name]\"\n [required]=\"col.isRequired\"\n [step]=\"col.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\"\n (ngModelChange)=\"onFieldChange()\">\n }\n @if (inlineErrorMessage(attr, rowIndex, col); as errorMsg) {\n <div class=\"invalid-feedback d-block\">{{ errorMsg }}</div>\n }\n </td>\n </ng-template>\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 50px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index; let rowIndex = $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n <td class=\"text-nowrap\">\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"removeArrayItem(attr, rowIndex)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n\n <!-- Floating preview = a LIVE view of the row's cells (not CDK's cloneNode),\n so <bs-select> shows its value instead of blanking. The in-grid\n placeholder still uses CDK's default clone (exact row height) and is\n hidden via .cdk-drag-placeholder { visibility:hidden } in the styles,\n so the drop gap is blank without collapsing/shifting the other rows. -->\n <ng-template cdkDragPreview>\n <table class=\"table mb-0 bg-body shadow-sm spark-drag-preview\">\n <tbody class=\"align-middle\">\n <tr>\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"width: 32px\"><spark-icon name=\"grip-vertical\" /></td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n </tr>\n </tbody>\n </table>\n </ng-template>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addInlineRow(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 80px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (getAsDetailCellRendererComponent(col); as cellRenderer) {\n <ng-container *ngComponentOutlet=\"cellRenderer; inputs: getAsDetailCellRendererInputs(row, col)\"></ng-container>\n } @else {\n {{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}\n }\n </td>\n }\n <td class=\"text-nowrap\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary me-1\" (click)=\"editArrayItem(attr, $index)\">\n <spark-icon name=\"pencil\" />\n </button>\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"removeArrayItem(attr, $index)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addArrayItem(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail') {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | asDetailDisplayValue:formData():asDetailTypes()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openAsDetailEditor(attr)\">\n <spark-icon name=\"pencil\" />\n </button>\n </bs-input-group>\n } @else if (getEditRendererComponent(attr); as editComp) {\n <ng-container *ngComponentOutlet=\"editComp; inputs: getEditRendererInputs(attr)\"></ng-container>\n } @else {\n <input\n [type]=\"attr.dataType | inputType\"\n [id]=\"attr.name\"\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\"\n [name]=\"attr.name\"\n [required]=\"attr.isRequired\"\n [step]=\"attr.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasError(attr.name)\">\n }\n @if (attr.name | errorForAttribute:validationErrors(); as errorMsg) {\n <div class=\"invalid-feedback d-block\">\n {{ errorMsg }}\n </div>\n }\n </div>\n </div>\n </ng-template>\n\n @if (showButtons()) {\n <div bsRow class=\"mt-4\">\n <div [md]=\"4\"></div>\n <div [md]=\"8\" class=\"d-flex justify-content-end gap-2\">\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"onCancel()\" [disabled]=\"isSaving()\">{{ 'common.cancel' | t }}</button>\n <button type=\"submit\" [color]=\"colors.primary\" [disabled]=\"isSaving()\" (click)=\"onSave()\">\n @if (isSaving()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'common.save' | t }}\n </button>\n </div>\n </div>\n }\n </bs-grid>\n}\n\n<!-- Modal for editing AsDetail objects -->\n<bs-modal [isOpen]=\"showAsDetailModal()\" (isOpenChange)=\"!$event && closeAsDetailModal()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.edit' | t }} {{ editingAsDetailAttr()?.label | resolveTranslation:editingAsDetailAttr()?.name }}</h5>\n </div>\n\n @if (editingAsDetailAttr(); as attr) {\n <div bsModalBody>\n <spark-po-form\n [entityType]=\"attr | asDetailType:asDetailTypes()\"\n [(formData)]=\"asDetailFormData\"\n [parentId]=\"parentId()\"\n [parentType]=\"parentType()\">\n </spark-po-form>\n </div>\n }\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeAsDetailModal()\">{{ 'common.cancel' | t }}</button>\n <button type=\"button\" [color]=\"colors.primary\" (click)=\"saveAsDetailObject()\">{{ 'common.save' | t }}</button>\n </div>\n </div>\n</bs-modal>\n</bs-form>\n", styles: [":host ::ng-deep .cdk-drag-placeholder{visibility:hidden}\n"], dependencies: [{ kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving", "parentId", "parentType"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["color", "outline"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["color", "navStyle"] }, { kind: "component", type: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsGridComponent, selector: "bs-grid", inputs: ["stopFullWidthAt"] }, { kind: "directive", type: BsGridRowDirective, selector: "[bsRow]" }, { kind: "directive", type: BsGridColumnDirective, selector: "[xxs],[xs],[sm],[md],[lg],[xl],[xxl]", inputs: ["xxs", "xs", "sm", "md", "lg", "xl", "xxl"] }, { kind: "directive", type: BsColFormLabelDirective, selector: "[bsColFormLabel]" }, { kind: "directive", type: BsButtonTypeDirective, selector: "button[color],input[type=\"button\"][color],input[type=\"submit\"][color],a[color]", inputs: ["color"] }, { kind: "component", type: BsInputGroupComponent, selector: "bs-input-group" }, { kind: "component", type: BsSelectComponent, selector: "bs-select", inputs: ["identifier", "size", "multiple", "numberVisible", "disabled", "ariaLabel"] }, { kind: "directive", type: BsSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "component", type: BsTreeSelectComponent, selector: "bs-tree-select", inputs: ["provider", "mode", "variant", "cascadeSelect", "placeholder", "showClear", "panelScrollHeight", "searchDebounceMs", "disabled", "value"], outputs: ["valueChange", "opened", "closed", "cleared"] }, { kind: "component", type: BsModalHostComponent, selector: "bs-modal", inputs: ["isOpen", "closeOnEscape", "scrollable"], outputs: ["isOpenChange"] }, { kind: "directive", type: BsModalDirective, selector: "[bsModal]" }, { kind: "directive", type: BsModalHeaderDirective, selector: "[bsModalHeader]" }, { kind: "directive", type: BsModalBodyDirective, selector: "[bsModalBody]" }, { kind: "directive", type: BsModalFooterDirective, selector: "[bsModalFooter]" }, { kind: "component", type: BsTableComponent, selector: "bs-table", inputs: ["isResponsive", "striped", "hover", "border", "ariaRowCount"] }, { kind: "component", type: BsCheckboxComponent, selector: "bs-checkbox", inputs: ["type", "isToggled", "indeterminate", "name", "value", "group"], outputs: ["isToggledChange", "indeterminateChange"] }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: BsTabControlComponent, selector: "bs-tab-control", inputs: ["border", "selectFirstTab", "tabsPosition"] }, { kind: "component", type: BsTabPageComponent, selector: "bs-tab-page", inputs: ["disabled"] }, { kind: "directive", type: BsTabPageHeaderDirective, selector: "[bsTabPageHeader]" }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "component", type: SparkReferencePickerComponent, selector: "spark-reference-picker", inputs: ["value", "options", "referenceType", "isInvalid", "inputId", "title"], outputs: ["valueChange"] }, { kind: "component", type: SparkLookupPickerComponent, selector: "spark-lookup-picker", inputs: ["value", "options", "isInvalid", "inputId", "title"], outputs: ["valueChange"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: InputTypePipe, name: "inputType" }, { kind: "pipe", type: LookupDisplayTypePipe, name: "lookupDisplayType" }, { kind: "pipe", type: LookupOptionsPipe, name: "lookupOptions" }, { kind: "pipe", type: AsDetailDisplayValuePipe, name: "asDetailDisplayValue" }, { kind: "pipe", type: AsDetailTypePipe, name: "asDetailType" }, { kind: "pipe", type: AsDetailColumnsPipe, name: "asDetailColumns" }, { kind: "pipe", type: AsDetailCellValuePipe, name: "asDetailCellValue" }, { kind: "pipe", type: CanCreateDetailRowPipe, name: "canCreateDetailRow" }, { kind: "pipe", type: CanDeleteDetailRowPipe, name: "canDeleteDetailRow" }, { kind: "pipe", type: InlineRefOptionsPipe, name: "inlineRefOptions" }, { kind: "pipe", type: ErrorForAttributePipe, name: "errorForAttribute" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
540
616
|
}
|
|
541
617
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: SparkPoFormComponent, decorators: [{
|
|
542
618
|
type: Component,
|
|
543
|
-
args: [{ selector: 'spark-po-form', imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, FormsModule, CdkDropList, CdkDrag, CdkDragHandle, CdkDragPreview, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsGridColDirective, BsColFormLabelDirective, BsButtonTypeDirective, BsInputGroupComponent, BsSelectComponent, BsSelectOption, BsTreeSelectComponent, BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsTableComponent, BsCheckboxComponent, BsSpinnerComponent, BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective, SparkIconComponent, SparkPoFormComponent, TranslateKeyPipe, ResolveTranslationPipe, InputTypePipe, LookupDisplayValuePipe, LookupDisplayTypePipe, LookupOptionsPipe, ReferenceDisplayValuePipe, AsDetailDisplayValuePipe, AsDetailTypePipe, AsDetailColumnsPipe, AsDetailCellValuePipe, CanCreateDetailRowPipe, CanDeleteDetailRowPipe, InlineRefOptionsPipe, ReferenceAttrValuePipe, ErrorForAttributePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-form>\n @if (entityType()) {\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"tabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #tabContent let-tab>\n @if (tab.id === '__default__') {\n @if (ungroupedAttributes().length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n @for (attr of ungroupedAttributes(); track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #attrField let-attr>\n <div bsRow class=\"mb-3\">\n <label [md]=\"4\" bsColFormLabel [for]=\"attr.name\">\n {{ attr.label | resolveTranslation:attr.name }}\n @if (attr.isRequired) {\n <span class=\"text-danger\">*</span>\n }\n </label>\n <div [md]=\"8\">\n @if (attr.dataType === 'boolean') {\n <bs-checkbox\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\">\n </bs-checkbox>\n } @else if (attr.lookupReferenceType) {\n @if ((attr | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | lookupDisplayValue:formData():lookupReferenceOptions()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openLookupSelector(attr)\">\n ...\n </button>\n </bs-input-group>\n } @else {\n <bs-select\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\"\n [id]=\"attr.name\"\n [class.is-invalid]=\"hasError(attr.name)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | lookupOptions:lookupReferenceOptions()); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n <bs-tree-select\n mode=\"multiple\"\n variant=\"textbox\"\n [showClear]=\"true\"\n [placeholder]=\"'common.search' | t\"\n [provider]=\"getReferenceProvider(attr)\"\n [ngModel]=\"referenceTreeValues()[attr.name] || []\"\n (ngModelChange)=\"onReferenceTreeChange(attr, $event)\"\n [ngModelOptions]=\"{ standalone: true }\">\n </bs-tree-select>\n } @else if (attr.dataType === 'Reference') {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | referenceDisplayValue:formData():referenceOptions()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openReferenceSelector(attr)\">\n ...\n </button>\n </bs-input-group>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray && attr.editMode === 'inline') {\n <!-- One source of truth for an inline cell, reused by the row and by the drag\n preview. The preview is a live Angular view (not a cloneNode), so its\n <bs-select> binds to row[col.name] and shows the value while dragging. -->\n <ng-template #inlineDetailCell let-row=\"row\" let-col=\"col\" let-attr=\"attr\" let-rowIndex=\"rowIndex\">\n <td>\n @if (col.isReadOnly) {\n <span class=\"text-muted\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n } @else if (getAsDetailCellEditRenderer(col); as editRenderer) {\n <ng-container *ngComponentOutlet=\"editRenderer; inputs: getAsDetailCellEditRendererInputs(row, col)\"></ng-container>\n } @else if (col.dataType === 'AsDetail') {\n <!-- Nested AsDetail can't flatten into a row: escalate to the row modal,\n where the recursive spark-po-form renders the nested structure. -->\n <div class=\"d-flex align-items-center gap-1\">\n <span class=\"text-truncate flex-grow-1\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\" (click)=\"editArrayItem(attr, rowIndex)\">\n <spark-icon name=\"pencil\" />\n </button>\n </div>\n } @else if (col.dataType === 'boolean') {\n <bs-checkbox\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n </bs-checkbox>\n } @else if (col.dataType === 'Reference' && col.query) {\n <bs-select\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | inlineRefOptions:col:asDetailReferenceOptions()); track option.id) {\n <option [ngValue]=\"option.id\">\n {{ option.breadcrumb || option.name || option.id }}\n </option>\n }\n </bs-select>\n } @else if (col.lookupReferenceType) {\n <bs-select\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of getLookupOptions(col); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n } @else {\n <input\n [type]=\"col.dataType | inputType\"\n [(ngModel)]=\"row[col.name]\"\n [required]=\"col.isRequired\"\n [step]=\"col.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\"\n (ngModelChange)=\"onFieldChange()\">\n }\n @if (inlineErrorMessage(attr, rowIndex, col); as errorMsg) {\n <div class=\"invalid-feedback d-block\">{{ errorMsg }}</div>\n }\n </td>\n </ng-template>\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 50px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index; let rowIndex = $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n <td class=\"text-nowrap\">\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"removeArrayItem(attr, rowIndex)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n\n <!-- Floating preview = a LIVE view of the row's cells (not CDK's cloneNode),\n so <bs-select> shows its value instead of blanking. The in-grid\n placeholder still uses CDK's default clone (exact row height) and is\n hidden via .cdk-drag-placeholder { visibility:hidden } in the styles,\n so the drop gap is blank without collapsing/shifting the other rows. -->\n <ng-template cdkDragPreview>\n <table class=\"table mb-0 bg-body shadow-sm spark-drag-preview\">\n <tbody class=\"align-middle\">\n <tr>\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"width: 32px\"><spark-icon name=\"grip-vertical\" /></td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n </tr>\n </tbody>\n </table>\n </ng-template>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addInlineRow(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 80px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (getAsDetailCellRendererComponent(col); as cellRenderer) {\n <ng-container *ngComponentOutlet=\"cellRenderer; inputs: getAsDetailCellRendererInputs(row, col)\"></ng-container>\n } @else {\n {{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}\n }\n </td>\n }\n <td class=\"text-nowrap\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary me-1\" (click)=\"editArrayItem(attr, $index)\">\n <spark-icon name=\"pencil\" />\n </button>\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"removeArrayItem(attr, $index)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addArrayItem(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail') {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | asDetailDisplayValue:formData():asDetailTypes()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openAsDetailEditor(attr)\">\n <spark-icon name=\"pencil\" />\n </button>\n </bs-input-group>\n } @else if (getEditRendererComponent(attr); as editComp) {\n <ng-container *ngComponentOutlet=\"editComp; inputs: getEditRendererInputs(attr)\"></ng-container>\n } @else {\n <input\n [type]=\"attr.dataType | inputType\"\n [id]=\"attr.name\"\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\"\n [name]=\"attr.name\"\n [required]=\"attr.isRequired\"\n [step]=\"attr.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasError(attr.name)\">\n }\n @if (attr.name | errorForAttribute:validationErrors(); as errorMsg) {\n <div class=\"invalid-feedback d-block\">\n {{ errorMsg }}\n </div>\n }\n </div>\n </div>\n </ng-template>\n\n @if (showButtons()) {\n <div bsRow class=\"mt-4\">\n <div [md]=\"4\"></div>\n <div [md]=\"8\" class=\"d-flex justify-content-end gap-2\">\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"onCancel()\" [disabled]=\"isSaving()\">{{ 'common.cancel' | t }}</button>\n <button type=\"submit\" [color]=\"colors.primary\" [disabled]=\"isSaving()\" (click)=\"onSave()\">\n @if (isSaving()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'common.save' | t }}\n </button>\n </div>\n </div>\n }\n </bs-grid>\n}\n\n<!-- Modal for editing AsDetail objects -->\n<bs-modal [isOpen]=\"showAsDetailModal()\" (isOpenChange)=\"!$event && closeAsDetailModal()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.edit' | t }} {{ editingAsDetailAttr()?.label | resolveTranslation:editingAsDetailAttr()?.name }}</h5>\n </div>\n\n @if (editingAsDetailAttr(); as attr) {\n <div bsModalBody>\n <spark-po-form\n [entityType]=\"attr | asDetailType:asDetailTypes()\"\n [(formData)]=\"asDetailFormData\"\n [parentId]=\"parentId()\"\n [parentType]=\"parentType()\">\n </spark-po-form>\n </div>\n }\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeAsDetailModal()\">{{ 'common.cancel' | t }}</button>\n <button type=\"button\" [color]=\"colors.primary\" (click)=\"saveAsDetailObject()\">{{ 'common.save' | t }}</button>\n </div>\n </div>\n</bs-modal>\n\n<!-- Modal for selecting Reference items -->\n<bs-modal [isOpen]=\"showReferenceModal()\" (isOpenChange)=\"!$event && closeReferenceModal()\">\n <div *bsModal class=\"reference-modal\">\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.select' | t }} {{ editingReferenceAttr()?.label | resolveTranslation:editingReferenceAttr()?.name }}</h5>\n </div>\n\n <div bsModalBody>\n @if (referenceModalEntityType()) {\n <!-- Search box -->\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"6\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'common.search' | t\"\n [(ngModel)]=\"referenceSearchTerm\"\n (ngModelChange)=\"onReferenceSearchChange()\">\n @if (referenceSearchTerm) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearReferenceSearch()\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n <div [md]=\"6\" class=\"text-end\">\n @if (referenceSearchTerm && referenceModalPagination()) {\n <span class=\"text-muted\">\n {{ referenceModalPagination()!.totalRecords }} {{ referenceModalPagination()!.totalRecords === 1 ? ('common.resultFound' | t) : ('common.resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n\n <bs-datatable\n [(settings)]=\"referenceModalSettings\"\n [data]=\"referenceModalRows()\"\n (rowClick)=\"selectReferenceItem($event.row)\">\n @for (attr of referenceVisibleAttributes(); track attr.id) {\n <div *bsDatatableColumn=\"attr.name; sortable: true\">\n {{ attr.label | resolveTranslation:attr.name }}\n </div>\n }\n\n <ng-container *bsRowTemplate=\"let item\">\n @for (attr of referenceVisibleAttributes(); track attr.id) {\n <td>{{ $any(item) | referenceAttrValue:attr.name }}</td>\n }\n </ng-container>\n </bs-datatable>\n } @else {\n <div class=\"text-center p-3\">\n <bs-spinner />\n </div>\n }\n </div>\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeReferenceModal()\">{{ 'common.cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n\n<!-- Modal for selecting LookupReference items -->\n<bs-modal [isOpen]=\"showLookupModal()\" (isOpenChange)=\"!$event && closeLookupModal()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.select' | t }} {{ editingLookupAttr()?.label | resolveTranslation:editingLookupAttr()?.name }}</h5>\n </div>\n\n <div bsModalBody>\n <!-- Search box -->\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [col]>\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'common.search' | t\"\n [ngModel]=\"lookupSearchTerm()\"\n (ngModelChange)=\"lookupSearchTerm.set($event)\">\n @if (lookupSearchTerm()) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"lookupSearchTerm.set('')\">\n <spark-icon name=\"x-lg\" />\n </button>\n }\n </bs-input-group>\n </div>\n </div>\n </bs-grid>\n\n <!-- List of items -->\n <bs-table [striped]=\"true\" [hover]=\"true\">\n <tbody class=\"align-middle\">\n @for (item of filteredLookupItems(); track item.key) {\n <tr\n [class.table-primary]=\"formData()[editingLookupAttr()?.name ?? ''] === item.key\"\n (click)=\"selectLookupItem(item)\"\n style=\"cursor: pointer;\">\n <td>{{ item.values | resolveTranslation:item.key }}</td>\n </tr>\n } @empty {\n <tr>\n <td class=\"text-center text-muted\">{{ 'common.noItemsFound' | t }}</td>\n </tr>\n }\n </tbody>\n </bs-table>\n </div>\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeLookupModal()\">{{ 'common.cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n</bs-form>\n", styles: [":host ::ng-deep .cdk-drag-placeholder{visibility:hidden}\n"] }]
|
|
619
|
+
args: [{ selector: 'spark-po-form', imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, FormsModule, CdkDropList, CdkDrag, CdkDragHandle, CdkDragPreview, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsGridColDirective, BsColFormLabelDirective, BsButtonTypeDirective, BsInputGroupComponent, BsSelectComponent, BsSelectOption, BsTreeSelectComponent, BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective, BsTableComponent, BsCheckboxComponent, BsSpinnerComponent, BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective, SparkIconComponent, SparkPoFormComponent, SparkReferencePickerComponent, SparkLookupPickerComponent, TranslateKeyPipe, ResolveTranslationPipe, InputTypePipe, LookupDisplayTypePipe, LookupOptionsPipe, AsDetailDisplayValuePipe, AsDetailTypePipe, AsDetailColumnsPipe, AsDetailCellValuePipe, CanCreateDetailRowPipe, CanDeleteDetailRowPipe, InlineRefOptionsPipe, ErrorForAttributePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-form>\n @if (entityType()) {\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"tabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #tabContent let-tab>\n @if (tab.id === '__default__') {\n @if (ungroupedAttributes().length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n @for (attr of ungroupedAttributes(); track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #attrField let-attr>\n <div bsRow class=\"mb-3\">\n <label [md]=\"4\" bsColFormLabel [for]=\"attr.name\">\n {{ attr.label | resolveTranslation:attr.name }}\n @if (attr.isRequired) {\n <span class=\"text-danger\">*</span>\n }\n </label>\n <div [md]=\"8\">\n @if (attr.dataType === 'boolean') {\n <bs-checkbox\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\">\n </bs-checkbox>\n } @else if (attr.lookupReferenceType) {\n @if ((attr | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <spark-lookup-picker\n [value]=\"formData()[attr.name]\"\n [options]=\"getLookupOptions(attr)\"\n [inputId]=\"attr.name\"\n [title]=\"attr.label | resolveTranslation:attr.name\"\n [isInvalid]=\"hasError(attr.name)\"\n (valueChange)=\"onLookupValueChange(attr, $event)\">\n </spark-lookup-picker>\n } @else {\n <bs-select\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\"\n [id]=\"attr.name\"\n [class.is-invalid]=\"hasError(attr.name)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | lookupOptions:lookupReferenceOptions()); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n <bs-tree-select\n mode=\"multiple\"\n variant=\"textbox\"\n [showClear]=\"true\"\n [placeholder]=\"'common.search' | t\"\n [provider]=\"getReferenceProvider(attr)\"\n [ngModel]=\"referenceTreeValues()[attr.name] || []\"\n (ngModelChange)=\"onReferenceTreeChange(attr, $event)\"\n [ngModelOptions]=\"{ standalone: true }\">\n </bs-tree-select>\n } @else if (attr.dataType === 'Reference') {\n <spark-reference-picker\n [value]=\"formData()[attr.name]\"\n [options]=\"referenceOptions()[attr.name] || []\"\n [referenceType]=\"attr.referenceType\"\n [inputId]=\"attr.name\"\n [title]=\"attr.label | resolveTranslation:attr.name\"\n [isInvalid]=\"hasError(attr.name)\"\n (valueChange)=\"onReferenceValueChange(attr, $event)\">\n </spark-reference-picker>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray && attr.editMode === 'inline') {\n <!-- One source of truth for an inline cell, reused by the row and by the drag\n preview. The preview is a live Angular view (not a cloneNode), so its\n <bs-select> binds to row[col.name] and shows the value while dragging. -->\n <ng-template #inlineDetailCell let-row=\"row\" let-col=\"col\" let-attr=\"attr\" let-rowIndex=\"rowIndex\">\n <td>\n @if (col.isReadOnly) {\n <span class=\"text-muted\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n } @else if (getAsDetailCellEditRenderer(col); as editRenderer) {\n <ng-container *ngComponentOutlet=\"editRenderer; inputs: getAsDetailCellEditRendererInputs(row, col)\"></ng-container>\n } @else if (col.dataType === 'AsDetail') {\n <!-- Nested AsDetail can't flatten into a row: escalate to the row modal,\n where the recursive spark-po-form renders the nested structure. -->\n <div class=\"d-flex align-items-center gap-1\">\n <span class=\"text-truncate flex-grow-1\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\" (click)=\"editArrayItem(attr, rowIndex)\">\n <spark-icon name=\"pencil\" />\n </button>\n </div>\n } @else if (col.dataType === 'boolean') {\n <bs-checkbox\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n </bs-checkbox>\n } @else if (col.dataType === 'Reference' && col.query) {\n @if (col.referenceDisplayType === EReferenceDisplayType.Modal) {\n <spark-reference-picker\n [value]=\"row[col.name]\"\n [options]=\"(attr | inlineRefOptions:col:asDetailReferenceOptions())\"\n [referenceType]=\"col.referenceType\"\n [title]=\"col.label | resolveTranslation:col.name\"\n [isInvalid]=\"hasInlineError(attr, rowIndex, col)\"\n (valueChange)=\"row[col.name] = $event; onFieldChange()\">\n </spark-reference-picker>\n } @else {\n <bs-select\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | inlineRefOptions:col:asDetailReferenceOptions()); track option.id) {\n <option [ngValue]=\"option.id\">\n {{ option.breadcrumb || option.name || option.id }}\n </option>\n }\n </bs-select>\n }\n } @else if (col.lookupReferenceType) {\n @if ((col | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <spark-lookup-picker\n [value]=\"row[col.name]\"\n [options]=\"getLookupOptions(col)\"\n [title]=\"col.label | resolveTranslation:col.name\"\n [isInvalid]=\"hasInlineError(attr, rowIndex, col)\"\n (valueChange)=\"row[col.name] = $event; onFieldChange()\">\n </spark-lookup-picker>\n } @else {\n <bs-select\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of getLookupOptions(col); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else {\n <input\n [type]=\"col.dataType | inputType\"\n [(ngModel)]=\"row[col.name]\"\n [required]=\"col.isRequired\"\n [step]=\"col.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\"\n (ngModelChange)=\"onFieldChange()\">\n }\n @if (inlineErrorMessage(attr, rowIndex, col); as errorMsg) {\n <div class=\"invalid-feedback d-block\">{{ errorMsg }}</div>\n }\n </td>\n </ng-template>\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 50px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index; let rowIndex = $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n <td class=\"text-nowrap\">\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"removeArrayItem(attr, rowIndex)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n\n <!-- Floating preview = a LIVE view of the row's cells (not CDK's cloneNode),\n so <bs-select> shows its value instead of blanking. The in-grid\n placeholder still uses CDK's default clone (exact row height) and is\n hidden via .cdk-drag-placeholder { visibility:hidden } in the styles,\n so the drop gap is blank without collapsing/shifting the other rows. -->\n <ng-template cdkDragPreview>\n <table class=\"table mb-0 bg-body shadow-sm spark-drag-preview\">\n <tbody class=\"align-middle\">\n <tr>\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"width: 32px\"><spark-icon name=\"grip-vertical\" /></td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n </tr>\n </tbody>\n </table>\n </ng-template>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addInlineRow(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 80px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (getAsDetailCellRendererComponent(col); as cellRenderer) {\n <ng-container *ngComponentOutlet=\"cellRenderer; inputs: getAsDetailCellRendererInputs(row, col)\"></ng-container>\n } @else {\n {{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}\n }\n </td>\n }\n <td class=\"text-nowrap\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary me-1\" (click)=\"editArrayItem(attr, $index)\">\n <spark-icon name=\"pencil\" />\n </button>\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"removeArrayItem(attr, $index)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addArrayItem(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail') {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | asDetailDisplayValue:formData():asDetailTypes()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openAsDetailEditor(attr)\">\n <spark-icon name=\"pencil\" />\n </button>\n </bs-input-group>\n } @else if (getEditRendererComponent(attr); as editComp) {\n <ng-container *ngComponentOutlet=\"editComp; inputs: getEditRendererInputs(attr)\"></ng-container>\n } @else {\n <input\n [type]=\"attr.dataType | inputType\"\n [id]=\"attr.name\"\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\"\n [name]=\"attr.name\"\n [required]=\"attr.isRequired\"\n [step]=\"attr.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasError(attr.name)\">\n }\n @if (attr.name | errorForAttribute:validationErrors(); as errorMsg) {\n <div class=\"invalid-feedback d-block\">\n {{ errorMsg }}\n </div>\n }\n </div>\n </div>\n </ng-template>\n\n @if (showButtons()) {\n <div bsRow class=\"mt-4\">\n <div [md]=\"4\"></div>\n <div [md]=\"8\" class=\"d-flex justify-content-end gap-2\">\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"onCancel()\" [disabled]=\"isSaving()\">{{ 'common.cancel' | t }}</button>\n <button type=\"submit\" [color]=\"colors.primary\" [disabled]=\"isSaving()\" (click)=\"onSave()\">\n @if (isSaving()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'common.save' | t }}\n </button>\n </div>\n </div>\n }\n </bs-grid>\n}\n\n<!-- Modal for editing AsDetail objects -->\n<bs-modal [isOpen]=\"showAsDetailModal()\" (isOpenChange)=\"!$event && closeAsDetailModal()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.edit' | t }} {{ editingAsDetailAttr()?.label | resolveTranslation:editingAsDetailAttr()?.name }}</h5>\n </div>\n\n @if (editingAsDetailAttr(); as attr) {\n <div bsModalBody>\n <spark-po-form\n [entityType]=\"attr | asDetailType:asDetailTypes()\"\n [(formData)]=\"asDetailFormData\"\n [parentId]=\"parentId()\"\n [parentType]=\"parentType()\">\n </spark-po-form>\n </div>\n }\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeAsDetailModal()\">{{ 'common.cancel' | t }}</button>\n <button type=\"button\" [color]=\"colors.primary\" (click)=\"saveAsDetailObject()\">{{ 'common.save' | t }}</button>\n </div>\n </div>\n</bs-modal>\n</bs-form>\n", styles: [":host ::ng-deep .cdk-drag-placeholder{visibility:hidden}\n"] }]
|
|
544
620
|
}], ctorParameters: () => [], propDecorators: { entityType: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityType", required: false }] }], formData: [{ type: i0.Input, args: [{ isSignal: true, alias: "formData", required: false }] }, { type: i0.Output, args: ["formDataChange"] }], validationErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationErrors", required: false }] }], showButtons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showButtons", required: false }] }], isSaving: [{ type: i0.Input, args: [{ isSignal: true, alias: "isSaving", required: false }] }], parentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentId", required: false }] }], parentType: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentType", required: false }] }], save: [{ type: i0.Output, args: ["save"] }], cancel: [{ type: i0.Output, args: ["cancel"] }] } });
|
|
545
621
|
|
|
546
622
|
/**
|
|
547
623
|
* Generated bundle index. Do not edit.
|
|
548
624
|
*/
|
|
549
625
|
|
|
550
|
-
export { SparkPoFormComponent };
|
|
626
|
+
export { SparkLookupPickerComponent, SparkPoFormComponent, SparkReferencePickerComponent };
|
|
551
627
|
//# sourceMappingURL=mintplayer-ng-spark-po-form.mjs.map
|