@mintplayer/ng-spark 0.0.1 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,13 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, signal, Injectable, inject, Pipe, input, computed, ChangeDetectionStrategy, Component, Directive, model, output, effect, ContentChildren } from '@angular/core';
2
+ import { InjectionToken, signal, Injectable, inject, Pipe, input, computed, ChangeDetectionStrategy, Component, model, output, effect } from '@angular/core';
3
3
  import { HttpClient, HttpParams } from '@angular/common/http';
4
4
  import { firstValueFrom } from 'rxjs';
5
5
  import * as i1 from '@angular/common';
6
- import { CommonModule, NgTemplateOutlet } from '@angular/common';
6
+ import { CommonModule, NgTemplateOutlet, NgComponentOutlet } from '@angular/common';
7
7
  import * as i2 from '@angular/forms';
8
8
  import { FormsModule } from '@angular/forms';
9
9
  import { Color } from '@mintplayer/ng-bootstrap';
10
+ import { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';
10
11
  import { BsFormComponent, BsFormControlDirective } from '@mintplayer/ng-bootstrap/form';
11
12
  import { BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsGridColDirective, BsColFormLabelDirective } from '@mintplayer/ng-bootstrap/grid';
12
13
  import { BsInputGroupComponent } from '@mintplayer/ng-bootstrap/input-group';
@@ -15,13 +16,14 @@ import { BsSelectComponent, BsSelectOption } from '@mintplayer/ng-bootstrap/sele
15
16
  import { BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective } from '@mintplayer/ng-bootstrap/modal';
16
17
  import { DatatableSettings, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective } from '@mintplayer/ng-bootstrap/datatable';
17
18
  import { BsToggleButtonComponent } from '@mintplayer/ng-bootstrap/toggle-button';
19
+ import { BsSpinnerComponent } from '@mintplayer/ng-bootstrap/spinner';
20
+ import { BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective } from '@mintplayer/ng-bootstrap/tab-control';
18
21
  import { DomSanitizer } from '@angular/platform-browser';
19
22
  import { BsTableComponent } from '@mintplayer/ng-bootstrap/table';
20
23
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
21
24
  import * as i2$1 from '@angular/router';
22
25
  import { ActivatedRoute, Router, RouterModule } from '@angular/router';
23
26
  import { BsAlertComponent } from '@mintplayer/ng-bootstrap/alert';
24
- import { BsCardComponent, BsCardHeaderComponent } from '@mintplayer/ng-bootstrap/card';
25
27
  import { BsContainerComponent } from '@mintplayer/ng-bootstrap/container';
26
28
  import { BsButtonGroupComponent } from '@mintplayer/ng-bootstrap/button-group';
27
29
 
@@ -606,33 +608,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
606
608
  `, styles: [":host{display:inline-flex;align-items:center;justify-content:center}span{display:inline-flex;align-items:center}span ::ng-deep svg{width:1em;height:1em;fill:currentColor}\n"] }]
607
609
  }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }] } });
608
610
 
609
- class SparkFieldTemplateDirective {
610
- template;
611
- name = input.required({ ...(ngDevMode ? { debugName: "name" } : {}), alias: 'sparkFieldTemplate' });
612
- constructor(template) {
613
- this.template = template;
614
- }
615
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkFieldTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
616
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: SparkFieldTemplateDirective, isStandalone: true, selector: "[sparkFieldTemplate]", inputs: { name: { classPropertyName: "name", publicName: "sparkFieldTemplate", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
611
+ const SPARK_ATTRIBUTE_RENDERERS = new InjectionToken('SparkAttributeRenderers', { factory: () => [] });
612
+ /**
613
+ * Register custom attribute renderers globally.
614
+ *
615
+ * @example
616
+ * provideSparkAttributeRenderers([
617
+ * { name: 'video-player', detailComponent: VideoDetailComponent, columnComponent: VideoColumnComponent },
618
+ * { name: 'color-swatch', detailComponent: ColorDetailComponent, columnComponent: ColorColumnComponent },
619
+ * ])
620
+ */
621
+ function provideSparkAttributeRenderers(renderers) {
622
+ return {
623
+ provide: SPARK_ATTRIBUTE_RENDERERS,
624
+ useValue: renderers,
625
+ };
617
626
  }
618
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkFieldTemplateDirective, decorators: [{
619
- type: Directive,
620
- args: [{
621
- selector: '[sparkFieldTemplate]',
622
- standalone: true
623
- }]
624
- }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "sparkFieldTemplate", required: true }] }] } });
625
627
 
626
628
  class SparkPoFormComponent {
627
629
  sparkService = inject(SparkService);
628
630
  translations = inject(SparkLanguageService);
629
- fieldTemplates;
631
+ rendererRegistry = inject(SPARK_ATTRIBUTE_RENDERERS);
630
632
  entityType = input(null, ...(ngDevMode ? [{ debugName: "entityType" }] : []));
631
633
  formData = model({}, ...(ngDevMode ? [{ debugName: "formData" }] : []));
632
634
  validationErrors = input([], ...(ngDevMode ? [{ debugName: "validationErrors" }] : []));
633
635
  showButtons = input(false, ...(ngDevMode ? [{ debugName: "showButtons" }] : []));
634
636
  isSaving = input(false, ...(ngDevMode ? [{ debugName: "isSaving" }] : []));
635
- externalFieldTemplates = input([], ...(ngDevMode ? [{ debugName: "externalFieldTemplates" }] : []));
636
637
  save = output();
637
638
  cancel = output();
638
639
  colors = Color;
@@ -672,6 +673,32 @@ class SparkPoFormComponent {
672
673
  .filter(a => a.isVisible && !a.isReadOnly && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))
673
674
  .sort((a, b) => a.order - b.order) || [];
674
675
  }, ...(ngDevMode ? [{ debugName: "editableAttributes" }] : []));
676
+ static DEFAULT_TAB = { id: '__default__', name: 'Algemeen', label: { nl: 'Algemeen', en: 'General' }, order: 0 };
677
+ ungroupedAttributes = computed(() => {
678
+ const attrs = this.editableAttributes();
679
+ const groupIds = new Set((this.entityType()?.groups || []).map(g => g.id));
680
+ return attrs.filter(a => !a.group || !groupIds.has(a.group));
681
+ }, ...(ngDevMode ? [{ debugName: "ungroupedAttributes" }] : []));
682
+ resolvedTabs = computed(() => {
683
+ const et = this.entityType();
684
+ const definedTabs = et?.tabs?.length ? [...et.tabs].sort((a, b) => a.order - b.order) : [];
685
+ const hasUngroupedAttrs = this.ungroupedAttributes().length > 0;
686
+ const hasUntabbedGroups = (et?.groups || []).some(g => !g.tab);
687
+ if (hasUngroupedAttrs || hasUntabbedGroups || definedTabs.length === 0) {
688
+ return [SparkPoFormComponent.DEFAULT_TAB, ...definedTabs];
689
+ }
690
+ return definedTabs;
691
+ }, ...(ngDevMode ? [{ debugName: "resolvedTabs" }] : []));
692
+ groupsForTab(tab) {
693
+ const groups = this.entityType()?.groups || [];
694
+ if (tab.id === '__default__') {
695
+ return groups.filter(g => !g.tab).sort((a, b) => a.order - b.order);
696
+ }
697
+ return groups.filter(g => g.tab === tab.id).sort((a, b) => a.order - b.order);
698
+ }
699
+ attrsForGroup(group) {
700
+ return this.editableAttributes().filter(a => a.group === group.id);
701
+ }
675
702
  referenceVisibleAttributes = computed(() => {
676
703
  return this.referenceModalEntityType()?.attributes
677
704
  .filter(a => a.isVisible)
@@ -687,31 +714,6 @@ class SparkPoFormComponent {
687
714
  return translation.toLowerCase().includes(term) || item.key.toLowerCase().includes(term);
688
715
  });
689
716
  }, ...(ngDevMode ? [{ debugName: "filteredLookupItems" }] : []));
690
- getFieldTemplate(attr) {
691
- const allTemplates = [
692
- ...(this.fieldTemplates?.toArray() || []),
693
- ...this.externalFieldTemplates()
694
- ];
695
- // Priority 1: match by field name
696
- const byName = allTemplates.find(t => t.name() === attr.name);
697
- if (byName)
698
- return byName.template;
699
- // Priority 2: match by data type
700
- const byType = allTemplates.find(t => t.name() === attr.dataType);
701
- if (byType)
702
- return byType.template;
703
- return null;
704
- }
705
- getFieldTemplateContext(attr) {
706
- const errorEntry = this.validationErrors().find(e => e.attributeName === attr.name);
707
- return {
708
- $implicit: attr,
709
- formData: this.formData(),
710
- value: this.formData()[attr.name],
711
- hasError: this.hasError(attr.name),
712
- errorMessage: errorEntry?.errorMessage || null
713
- };
714
- }
715
717
  constructor() {
716
718
  effect(() => {
717
719
  const et = this.entityType();
@@ -805,6 +807,24 @@ class SparkPoFormComponent {
805
807
  this.lookupModalItems.set([]);
806
808
  this.lookupSearchTerm.set('');
807
809
  }
810
+ getEditRendererComponent(attr) {
811
+ if (!attr.renderer)
812
+ return null;
813
+ const reg = this.rendererRegistry.find(r => r.name === attr.renderer);
814
+ return reg?.editComponent ?? null;
815
+ }
816
+ getEditRendererInputs(attr) {
817
+ return {
818
+ value: this.formData()[attr.name],
819
+ attribute: attr,
820
+ options: attr.rendererOptions,
821
+ valueChange: (newValue) => {
822
+ const data = { ...this.formData() };
823
+ data[attr.name] = newValue;
824
+ this.formData.set(data);
825
+ },
826
+ };
827
+ }
808
828
  hasError(attrName) {
809
829
  return this.validationErrors().some(e => e.attributeName === attrName);
810
830
  }
@@ -952,21 +972,17 @@ class SparkPoFormComponent {
952
972
  this.referenceSearchTerm = '';
953
973
  }
954
974
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
955
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", 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 }, externalFieldTemplates: { classPropertyName: "externalFieldTemplates", publicName: "externalFieldTemplates", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formData: "formDataChange", save: "save", cancel: "cancel" }, queries: [{ propertyName: "fieldTemplates", predicate: SparkFieldTemplateDirective }], ngImport: i0, template: "<bs-form>\n @if (entityType()) {\n <bs-grid>\n @for (attr of editableAttributes(); track attr.id) {\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 (getFieldTemplate(attr); as customTpl) {\n <ng-container *ngTemplateOutlet=\"customTpl; context: getFieldTemplateContext(attr)\"></ng-container>\n } @else if (attr.dataType === 'boolean') {\n <bs-toggle-button\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\">\n </bs-toggle-button>\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\">{{ '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') {\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 <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\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>\n @for (row of formData()[attr.name] || []; track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (col.dataType === 'boolean') {\n <bs-toggle-button\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n </bs-toggle-button>\n } @else if (col.dataType === 'Reference' && col.query) {\n <bs-select\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n <option [ngValue]=\"null\">{{ '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 {\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 (ngModelChange)=\"onFieldChange()\">\n }\n </td>\n }\n <td class=\"text-nowrap\">\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.secondary\" (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 + 1\" class=\"text-center text-muted\">\n {{ '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\" /> {{ '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 @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>\n @for (row of formData()[attr.name] || []; track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</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 + 1\" class=\"text-center text-muted\">\n {{ '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\" /> {{ '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 {\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 }\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()\">{{ 'cancel' | t }}</button>\n <button type=\"submit\" [color]=\"colors.primary\" [disabled]=\"isSaving()\" (click)=\"onSave()\">\n @if (isSaving()) {\n <span class=\"spinner-border spinner-border-sm me-1\" role=\"status\" aria-hidden=\"true\"></span>\n }\n {{ '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\">{{ '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 </spark-po-form>\n </div>\n }\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeAsDetailModal()\">{{ 'cancel' | t }}</button>\n <button type=\"button\" [color]=\"colors.primary\" (click)=\"saveAsDetailObject()\">{{ '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\">{{ '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]=\"'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 ? ('resultFound' | t) : ('resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n\n <bs-datatable [(settings)]=\"referenceModalSettings\" (settingsChange)=\"applyReferenceFilter()\">\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 <tr *bsRowTemplate=\"let item of referenceModalPagination()\" (click)=\"selectReferenceItem(item)\" style=\"cursor: pointer;\">\n @for (attr of referenceVisibleAttributes(); track attr.id) {\n <td>{{ item | referenceAttrValue:attr.name }}</td>\n }\n </tr>\n </bs-datatable>\n } @else {\n <div class=\"text-center p-3\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n </div>\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeReferenceModal()\">{{ '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\">{{ '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]=\"'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>\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\">{{ '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()\">{{ 'cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n</bs-form>\n", dependencies: [{ kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving", "externalFieldTemplates"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "ngmodule", type: CommonModule }, { 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])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.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: "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"] }, { kind: "directive", type: BsSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "component", type: BsModalHostComponent, selector: "bs-modal", inputs: ["isOpen", "closeOnEscape"], 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: ["settings", "data"], outputs: ["settingsChange", "dataChange"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable"] }, { kind: "directive", type: BsRowTemplateDirective, selector: "[bsRowTemplate]", inputs: ["bsRowTemplateOf"] }, { kind: "component", type: BsTableComponent, selector: "bs-table", inputs: ["isResponsive", "striped", "hover"] }, { kind: "component", type: BsToggleButtonComponent, selector: "bs-toggle-button", inputs: ["type", "isToggled", "name", "value", "group"], outputs: ["isToggledChange"] }, { 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 });
975
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", 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 } }, 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-toggle-button\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\">\n </bs-toggle-button>\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\">{{ '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') {\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 <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\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>\n @for (row of formData()[attr.name] || []; track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (col.dataType === 'boolean') {\n <bs-toggle-button\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n </bs-toggle-button>\n } @else if (col.dataType === 'Reference' && col.query) {\n <bs-select\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n <option [ngValue]=\"null\">{{ '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 {\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 (ngModelChange)=\"onFieldChange()\">\n }\n </td>\n }\n <td class=\"text-nowrap\">\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.secondary\" (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 + 1\" class=\"text-center text-muted\">\n {{ '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\" /> {{ '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 @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>\n @for (row of formData()[attr.name] || []; track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</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 + 1\" class=\"text-center text-muted\">\n {{ '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\" /> {{ '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()\">{{ '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 {{ '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\">{{ '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 </spark-po-form>\n </div>\n }\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeAsDetailModal()\">{{ 'cancel' | t }}</button>\n <button type=\"button\" [color]=\"colors.primary\" (click)=\"saveAsDetailObject()\">{{ '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\">{{ '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]=\"'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 ? ('resultFound' | t) : ('resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n\n <bs-datatable [(settings)]=\"referenceModalSettings\" (settingsChange)=\"applyReferenceFilter()\">\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 <tr *bsRowTemplate=\"let item of referenceModalPagination()\" (click)=\"selectReferenceItem(item)\" style=\"cursor: pointer;\">\n @for (attr of referenceVisibleAttributes(); track attr.id) {\n <td>{{ item | referenceAttrValue:attr.name }}</td>\n }\n </tr>\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()\">{{ '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\">{{ '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]=\"'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>\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\">{{ '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()\">{{ 'cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n</bs-form>\n", dependencies: [{ kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving"], 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])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.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: "component", type: BsCardComponent, selector: "bs-card", inputs: ["rounded"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["noPadding"] }, { 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"] }, { kind: "directive", type: BsSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "component", type: BsModalHostComponent, selector: "bs-modal", inputs: ["isOpen", "closeOnEscape"], 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: ["settings", "data"], outputs: ["settingsChange", "dataChange"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable"] }, { kind: "directive", type: BsRowTemplateDirective, selector: "[bsRowTemplate]", inputs: ["bsRowTemplateOf"] }, { kind: "component", type: BsTableComponent, selector: "bs-table", inputs: ["isResponsive", "striped", "hover"] }, { kind: "component", type: BsToggleButtonComponent, selector: "bs-toggle-button", inputs: ["type", "isToggled", "name", "value", "group"], outputs: ["isToggledChange"] }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: BsTabControlComponent, selector: "bs-tab-control", inputs: ["border", "restrictDragging", "selectFirstTab", "tabsPosition", "allowDragDrop"] }, { 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 });
956
976
  }
957
977
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoFormComponent, decorators: [{
958
978
  type: Component,
959
- args: [{ selector: 'spark-po-form', imports: [CommonModule, NgTemplateOutlet, FormsModule, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsGridColDirective, BsColFormLabelDirective, BsButtonTypeDirective, BsInputGroupComponent, BsSelectComponent, BsSelectOption, BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsTableComponent, BsToggleButtonComponent, 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 @for (attr of editableAttributes(); track attr.id) {\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 (getFieldTemplate(attr); as customTpl) {\n <ng-container *ngTemplateOutlet=\"customTpl; context: getFieldTemplateContext(attr)\"></ng-container>\n } @else if (attr.dataType === 'boolean') {\n <bs-toggle-button\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\">\n </bs-toggle-button>\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\">{{ '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') {\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 <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\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>\n @for (row of formData()[attr.name] || []; track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (col.dataType === 'boolean') {\n <bs-toggle-button\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n </bs-toggle-button>\n } @else if (col.dataType === 'Reference' && col.query) {\n <bs-select\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n <option [ngValue]=\"null\">{{ '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 {\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 (ngModelChange)=\"onFieldChange()\">\n }\n </td>\n }\n <td class=\"text-nowrap\">\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.secondary\" (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 + 1\" class=\"text-center text-muted\">\n {{ '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\" /> {{ '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 @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>\n @for (row of formData()[attr.name] || []; track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</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 + 1\" class=\"text-center text-muted\">\n {{ '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\" /> {{ '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 {\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 }\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()\">{{ 'cancel' | t }}</button>\n <button type=\"submit\" [color]=\"colors.primary\" [disabled]=\"isSaving()\" (click)=\"onSave()\">\n @if (isSaving()) {\n <span class=\"spinner-border spinner-border-sm me-1\" role=\"status\" aria-hidden=\"true\"></span>\n }\n {{ '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\">{{ '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 </spark-po-form>\n </div>\n }\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeAsDetailModal()\">{{ 'cancel' | t }}</button>\n <button type=\"button\" [color]=\"colors.primary\" (click)=\"saveAsDetailObject()\">{{ '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\">{{ '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]=\"'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 ? ('resultFound' | t) : ('resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n\n <bs-datatable [(settings)]=\"referenceModalSettings\" (settingsChange)=\"applyReferenceFilter()\">\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 <tr *bsRowTemplate=\"let item of referenceModalPagination()\" (click)=\"selectReferenceItem(item)\" style=\"cursor: pointer;\">\n @for (attr of referenceVisibleAttributes(); track attr.id) {\n <td>{{ item | referenceAttrValue:attr.name }}</td>\n }\n </tr>\n </bs-datatable>\n } @else {\n <div class=\"text-center p-3\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n </div>\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeReferenceModal()\">{{ '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\">{{ '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]=\"'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>\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\">{{ '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()\">{{ 'cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n</bs-form>\n" }]
960
- }], ctorParameters: () => [], propDecorators: { fieldTemplates: [{
961
- type: ContentChildren,
962
- args: [SparkFieldTemplateDirective]
963
- }], 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 }] }], externalFieldTemplates: [{ type: i0.Input, args: [{ isSignal: true, alias: "externalFieldTemplates", required: false }] }], save: [{ type: i0.Output, args: ["save"] }], cancel: [{ type: i0.Output, args: ["cancel"] }] } });
979
+ args: [{ selector: 'spark-po-form', imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, FormsModule, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsGridColDirective, BsColFormLabelDirective, BsButtonTypeDirective, BsInputGroupComponent, BsSelectComponent, BsSelectOption, BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsTableComponent, BsToggleButtonComponent, 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-toggle-button\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange()\">\n </bs-toggle-button>\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\">{{ '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') {\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 <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\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>\n @for (row of formData()[attr.name] || []; track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (col.dataType === 'boolean') {\n <bs-toggle-button\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n </bs-toggle-button>\n } @else if (col.dataType === 'Reference' && col.query) {\n <bs-select\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onFieldChange()\">\n <option [ngValue]=\"null\">{{ '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 {\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 (ngModelChange)=\"onFieldChange()\">\n }\n </td>\n }\n <td class=\"text-nowrap\">\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.secondary\" (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 + 1\" class=\"text-center text-muted\">\n {{ '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\" /> {{ '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 @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>\n @for (row of formData()[attr.name] || []; track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</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 + 1\" class=\"text-center text-muted\">\n {{ '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\" /> {{ '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()\">{{ '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 {{ '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\">{{ '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 </spark-po-form>\n </div>\n }\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeAsDetailModal()\">{{ 'cancel' | t }}</button>\n <button type=\"button\" [color]=\"colors.primary\" (click)=\"saveAsDetailObject()\">{{ '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\">{{ '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]=\"'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 ? ('resultFound' | t) : ('resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n\n <bs-datatable [(settings)]=\"referenceModalSettings\" (settingsChange)=\"applyReferenceFilter()\">\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 <tr *bsRowTemplate=\"let item of referenceModalPagination()\" (click)=\"selectReferenceItem(item)\" style=\"cursor: pointer;\">\n @for (attr of referenceVisibleAttributes(); track attr.id) {\n <td>{{ item | referenceAttrValue:attr.name }}</td>\n }\n </tr>\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()\">{{ '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\">{{ '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]=\"'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>\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\">{{ '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()\">{{ 'cancel' | t }}</button>\n </div>\n </div>\n</bs-modal>\n</bs-form>\n" }]
980
+ }], 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 }] }], save: [{ type: i0.Output, args: ["save"] }], cancel: [{ type: i0.Output, args: ["cancel"] }] } });
964
981
 
965
982
  class SparkPoCreateComponent {
966
983
  route = inject(ActivatedRoute);
967
984
  router = inject(Router);
968
985
  sparkService = inject(SparkService);
969
- fieldTemplates;
970
986
  saved = output();
971
987
  cancelled = output();
972
988
  colors = Color;
@@ -1057,15 +1073,12 @@ class SparkPoCreateComponent {
1057
1073
  window.history.back();
1058
1074
  }
1059
1075
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoCreateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1060
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkPoCreateComponent, isStandalone: true, selector: "spark-po-create", outputs: { saved: "saved", cancelled: "cancelled" }, queries: [{ propertyName: "fieldTemplates", predicate: SparkFieldTemplateDirective }], ngImport: i0, template: "<bs-container>\n<div class=\"container\">\n @if (entityType(); as et) {\n <h2 class=\"mb-4\">{{ 'create' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage }}\n </bs-alert>\n }\n\n <bs-card>\n <bs-card-header>{{ 'properties' | t }}</bs-card-header>\n <div class=\"p-3\">\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [externalFieldTemplates]=\"fieldTemplates.toArray()\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n </div>\n </bs-card>\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["rounded"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["noPadding"] }, { kind: "component", type: BsContainerComponent, selector: "bs-container" }, { kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving", "externalFieldTemplates"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1076
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkPoCreateComponent, isStandalone: true, selector: "spark-po-create", outputs: { saved: "saved", cancelled: "cancelled" }, ngImport: i0, template: "<bs-container>\n<div class=\"container\">\n @if (entityType(); as et) {\n <h2 class=\"mb-4\">{{ 'create' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage }}\n </bs-alert>\n }\n\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsContainerComponent, selector: "bs-container" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1061
1077
  }
1062
1078
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoCreateComponent, decorators: [{
1063
1079
  type: Component,
1064
- args: [{ selector: 'spark-po-create', imports: [CommonModule, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsContainerComponent, SparkPoFormComponent, ResolveTranslationPipe, TranslateKeyPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-container>\n<div class=\"container\">\n @if (entityType(); as et) {\n <h2 class=\"mb-4\">{{ 'create' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage }}\n </bs-alert>\n }\n\n <bs-card>\n <bs-card-header>{{ 'properties' | t }}</bs-card-header>\n <div class=\"p-3\">\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [externalFieldTemplates]=\"fieldTemplates.toArray()\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n </div>\n </bs-card>\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n" }]
1065
- }], ctorParameters: () => [], propDecorators: { fieldTemplates: [{
1066
- type: ContentChildren,
1067
- args: [SparkFieldTemplateDirective]
1068
- }], saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
1080
+ args: [{ selector: 'spark-po-create', imports: [CommonModule, BsAlertComponent, BsContainerComponent, BsSpinnerComponent, SparkPoFormComponent, ResolveTranslationPipe, TranslateKeyPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-container>\n<div class=\"container\">\n @if (entityType(); as et) {\n <h2 class=\"mb-4\">{{ 'create' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage }}\n </bs-alert>\n }\n\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n" }]
1081
+ }], ctorParameters: () => [], propDecorators: { saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
1069
1082
 
1070
1083
  var sparkPoCreate_component = /*#__PURE__*/Object.freeze({
1071
1084
  __proto__: null,
@@ -1076,7 +1089,6 @@ class SparkPoEditComponent {
1076
1089
  route = inject(ActivatedRoute);
1077
1090
  router = inject(Router);
1078
1091
  sparkService = inject(SparkService);
1079
- fieldTemplates;
1080
1092
  saved = output();
1081
1093
  cancelled = output();
1082
1094
  colors = Color;
@@ -1185,15 +1197,12 @@ class SparkPoEditComponent {
1185
1197
  this.router.navigate(['/po', this.type, this.id]);
1186
1198
  }
1187
1199
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoEditComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1188
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkPoEditComponent, isStandalone: true, selector: "spark-po-edit", outputs: { saved: "saved", cancelled: "cancelled" }, queries: [{ propertyName: "fieldTemplates", predicate: SparkFieldTemplateDirective }], ngImport: i0, template: "<bs-container>\n<div class=\"container\">\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage }}\n </bs-alert>\n }\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'edit' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n <bs-card>\n <bs-card-header>{{ 'properties' | t }}</bs-card-header>\n <div class=\"p-3\">\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [externalFieldTemplates]=\"fieldTemplates.toArray()\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n </div>\n </bs-card>\n }\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["rounded"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["noPadding"] }, { kind: "component", type: BsContainerComponent, selector: "bs-container" }, { kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving", "externalFieldTemplates"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1200
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkPoEditComponent, isStandalone: true, selector: "spark-po-edit", outputs: { saved: "saved", cancelled: "cancelled" }, ngImport: i0, template: "<bs-container>\n<div class=\"container\">\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage }}\n </bs-alert>\n }\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'edit' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsContainerComponent, selector: "bs-container" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1189
1201
  }
1190
1202
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoEditComponent, decorators: [{
1191
1203
  type: Component,
1192
- args: [{ selector: 'spark-po-edit', imports: [CommonModule, BsAlertComponent, BsCardComponent, BsCardHeaderComponent, BsContainerComponent, SparkPoFormComponent, ResolveTranslationPipe, TranslateKeyPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-container>\n<div class=\"container\">\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage }}\n </bs-alert>\n }\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'edit' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n <bs-card>\n <bs-card-header>{{ 'properties' | t }}</bs-card-header>\n <div class=\"p-3\">\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n [externalFieldTemplates]=\"fieldTemplates.toArray()\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n </div>\n </bs-card>\n }\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n" }]
1193
- }], ctorParameters: () => [], propDecorators: { fieldTemplates: [{
1194
- type: ContentChildren,
1195
- args: [SparkFieldTemplateDirective]
1196
- }], saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
1204
+ args: [{ selector: 'spark-po-edit', imports: [CommonModule, BsAlertComponent, BsContainerComponent, BsSpinnerComponent, SparkPoFormComponent, ResolveTranslationPipe, TranslateKeyPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-container>\n<div class=\"container\">\n @for (error of generalErrors(); track error.errorMessage) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ error.errorMessage }}\n </bs-alert>\n }\n\n @if (entityType(); as et) {\n @if (item()) {\n <h2 class=\"mb-4\">{{ 'edit' | t }} {{ (et.description | resolveTranslation) || et.name }}</h2>\n\n <spark-po-form\n [entityType]=\"et\"\n [(formData)]=\"formData\"\n [validationErrors]=\"validationErrors()\"\n [showButtons]=\"true\"\n [isSaving]=\"isSaving()\"\n (save)=\"onSave()\"\n (cancel)=\"onCancel()\">\n </spark-po-form>\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n" }]
1205
+ }], ctorParameters: () => [], propDecorators: { saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
1197
1206
 
1198
1207
  var sparkPoEdit_component = /*#__PURE__*/Object.freeze({
1199
1208
  __proto__: null,
@@ -1307,29 +1316,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
1307
1316
  args: [{ name: 'referenceLinkRoute', standalone: true, pure: true }]
1308
1317
  }] });
1309
1318
 
1310
- class SparkDetailFieldTemplateDirective {
1311
- template;
1312
- name = input.required({ ...(ngDevMode ? { debugName: "name" } : {}), alias: 'sparkDetailFieldTemplate' });
1313
- constructor(template) {
1314
- this.template = template;
1315
- }
1316
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkDetailFieldTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
1317
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: SparkDetailFieldTemplateDirective, isStandalone: true, selector: "[sparkDetailFieldTemplate]", inputs: { name: { classPropertyName: "name", publicName: "sparkDetailFieldTemplate", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
1318
- }
1319
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkDetailFieldTemplateDirective, decorators: [{
1320
- type: Directive,
1321
- args: [{
1322
- selector: '[sparkDetailFieldTemplate]',
1323
- standalone: true
1324
- }]
1325
- }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "sparkDetailFieldTemplate", required: true }] }] } });
1326
-
1327
1319
  class SparkPoDetailComponent {
1328
1320
  route = inject(ActivatedRoute);
1329
1321
  router = inject(Router);
1330
1322
  sparkService = inject(SparkService);
1331
1323
  lang = inject(SparkLanguageService);
1332
- detailFieldTemplates;
1324
+ rendererRegistry = inject(SPARK_ATTRIBUTE_RENDERERS);
1333
1325
  showCustomActions = input(true, ...(ngDevMode ? [{ debugName: "showCustomActions" }] : []));
1334
1326
  extraActionsTemplate = input(null, ...(ngDevMode ? [{ debugName: "extraActionsTemplate" }] : []));
1335
1327
  extraContentTemplate = input(null, ...(ngDevMode ? [{ debugName: "extraContentTemplate" }] : []));
@@ -1386,23 +1378,48 @@ class SparkPoDetailComponent {
1386
1378
  .filter(a => a.isVisible && hasShowedOnFlag(a.showedOn, ShowedOn.PersistentObject))
1387
1379
  .sort((a, b) => a.order - b.order) || [];
1388
1380
  }, ...(ngDevMode ? [{ debugName: "visibleAttributes" }] : []));
1389
- getDetailFieldTemplate(attr) {
1390
- if (!this.detailFieldTemplates)
1381
+ static DEFAULT_TAB = { id: '__default__', name: 'Algemeen', label: { nl: 'Algemeen', en: 'General' }, order: 0 };
1382
+ ungroupedAttributes = computed(() => {
1383
+ const attrs = this.visibleAttributes();
1384
+ const groupIds = new Set((this.entityType()?.groups || []).map(g => g.id));
1385
+ return attrs.filter(a => !a.group || !groupIds.has(a.group));
1386
+ }, ...(ngDevMode ? [{ debugName: "ungroupedAttributes" }] : []));
1387
+ resolvedTabs = computed(() => {
1388
+ const et = this.entityType();
1389
+ const definedTabs = et?.tabs?.length ? [...et.tabs].sort((a, b) => a.order - b.order) : [];
1390
+ const hasUngroupedAttrs = this.ungroupedAttributes().length > 0;
1391
+ const hasUntabbedGroups = (et?.groups || []).some(g => !g.tab);
1392
+ if (hasUngroupedAttrs || hasUntabbedGroups || definedTabs.length === 0) {
1393
+ return [SparkPoDetailComponent.DEFAULT_TAB, ...definedTabs];
1394
+ }
1395
+ return definedTabs;
1396
+ }, ...(ngDevMode ? [{ debugName: "resolvedTabs" }] : []));
1397
+ groupsForTab(tab) {
1398
+ const groups = this.entityType()?.groups || [];
1399
+ if (tab.id === '__default__') {
1400
+ return groups.filter(g => !g.tab).sort((a, b) => a.order - b.order);
1401
+ }
1402
+ return groups.filter(g => g.tab === tab.id).sort((a, b) => a.order - b.order);
1403
+ }
1404
+ attrsForGroup(group) {
1405
+ return this.visibleAttributes().filter(a => a.group === group.id);
1406
+ }
1407
+ getDetailRendererComponent(attr) {
1408
+ if (!attr.renderer)
1391
1409
  return null;
1392
- const byName = this.detailFieldTemplates.find(t => t.name() === attr.name);
1393
- if (byName)
1394
- return byName.template;
1395
- const byType = this.detailFieldTemplates.find(t => t.name() === attr.dataType);
1396
- if (byType)
1397
- return byType.template;
1398
- return null;
1399
- }
1400
- getDetailFieldContext(attr, item) {
1410
+ return this.rendererRegistry.find(r => r.name === attr.renderer)?.detailComponent ?? null;
1411
+ }
1412
+ getDetailRendererInputs(attr, item) {
1401
1413
  const itemAttr = item.attributes.find(a => a.name === attr.name);
1414
+ const formData = {};
1415
+ for (const a of item.attributes) {
1416
+ formData[a.name] = a.value;
1417
+ }
1402
1418
  return {
1403
- $implicit: attr,
1404
- item,
1405
- value: itemAttr?.value
1419
+ value: itemAttr?.value,
1420
+ attribute: attr,
1421
+ options: attr.rendererOptions,
1422
+ formData,
1406
1423
  };
1407
1424
  }
1408
1425
  async loadLookupReferenceOptions() {
@@ -1475,43 +1492,23 @@ class SparkPoDetailComponent {
1475
1492
  window.history.back();
1476
1493
  }
1477
1494
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1478
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkPoDetailComponent, isStandalone: true, selector: "spark-po-detail", inputs: { showCustomActions: { classPropertyName: "showCustomActions", publicName: "showCustomActions", isSignal: true, isRequired: false, transformFunction: null }, extraActionsTemplate: { classPropertyName: "extraActionsTemplate", publicName: "extraActionsTemplate", isSignal: true, isRequired: false, transformFunction: null }, extraContentTemplate: { classPropertyName: "extraContentTemplate", publicName: "extraContentTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { edited: "edited", deleted: "deleted", customActionExecuted: "customActionExecuted" }, queries: [{ propertyName: "detailFieldTemplates", predicate: SparkDetailFieldTemplateDirective }], ngImport: i0, template: "<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"d-flex justify-content-between align-items-center mb-4\">\n <h2>{{ currentItem.breadcrumb || currentItem.name }}</h2>\n <bs-button-group>\n <button class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'back' | t }}\n </button>\n @if (canEdit()) {\n <button class=\"btn btn-primary\" (click)=\"onEdit()\">\n <spark-icon name=\"pencil\" /> {{ 'edit' | t }}\n </button>\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n }\n @if (showCustomActions()) {\n @for (action of customActions(); track action.name) {\n <button class=\"btn btn-outline-primary\" (click)=\"onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n }\n @if (canDelete()) {\n <button class=\"btn btn-danger\" (click)=\"onDelete()\">\n <spark-icon name=\"trash\" /> {{ 'delete' | t }}\n </button>\n }\n </bs-button-group>\n </div>\n\n <bs-card>\n <bs-card-header>{{ 'details' | t }}</bs-card-header>\n <div class=\"p-3\">\n <bs-grid>\n <dl bsRow>\n @for (attr of visibleAttributes(); track attr.id) {\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}</dt>\n <dd [sm]=\"9\">\n @if (getDetailFieldTemplate(attr); as customTpl) {\n <ng-container *ngTemplateOutlet=\"customTpl; context: getDetailFieldContext(attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (col.dataType === 'Reference' && col.referenceType) {\n @let route = (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes());\n @if (route) {\n <a [routerLink]=\"route\">{{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}</a>\n } @else {\n {{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}\n }\n } @else {\n {{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length\" class=\"text-center text-muted\">\n {{ 'noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n style=\"opacity: 1;\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n }\n </dl>\n </bs-grid>\n </div>\n </bs-card>\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsButtonGroupComponent, selector: "bs-button-group" }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["rounded"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["noPadding"] }, { kind: "component", type: BsContainerComponent, selector: "bs-container" }, { 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: BsTableComponent, selector: "bs-table", inputs: ["isResponsive", "striped", "hover"] }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: AttributeValuePipe, name: "attributeValue" }, { kind: "pipe", type: RawAttributeValuePipe, name: "rawAttributeValue" }, { kind: "pipe", type: AsDetailColumnsPipe, name: "asDetailColumns" }, { kind: "pipe", type: AsDetailCellValuePipe, name: "asDetailCellValue" }, { kind: "pipe", type: ArrayValuePipe, name: "arrayValue" }, { kind: "pipe", type: ReferenceLinkRoutePipe, name: "referenceLinkRoute" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1495
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkPoDetailComponent, isStandalone: true, selector: "spark-po-detail", inputs: { showCustomActions: { classPropertyName: "showCustomActions", publicName: "showCustomActions", isSignal: true, isRequired: false, transformFunction: null }, extraActionsTemplate: { classPropertyName: "extraActionsTemplate", publicName: "extraActionsTemplate", isSignal: true, isRequired: false, transformFunction: null }, extraContentTemplate: { classPropertyName: "extraContentTemplate", publicName: "extraContentTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { edited: "edited", deleted: "deleted", customActionExecuted: "customActionExecuted" }, ngImport: i0, template: "<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"d-flex justify-content-between align-items-center mb-4\">\n <h2>{{ currentItem.breadcrumb || currentItem.name }}</h2>\n <bs-button-group>\n <button class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'back' | t }}\n </button>\n @if (canEdit()) {\n <button class=\"btn btn-primary\" (click)=\"onEdit()\">\n <spark-icon name=\"pencil\" /> {{ 'edit' | t }}\n </button>\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n }\n @if (showCustomActions()) {\n @for (action of customActions(); track action.name) {\n <button class=\"btn btn-outline-primary\" (click)=\"onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n }\n @if (canDelete()) {\n <button class=\"btn btn-danger\" (click)=\"onDelete()\">\n <spark-icon name=\"trash\" /> {{ 'delete' | t }}\n </button>\n }\n </bs-button-group>\n </div>\n\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=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card style=\"display: block; margin: 1rem;\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\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 style=\"display: block; margin: 1rem;\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}</dt>\n <dd [sm]=\"9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (col.dataType === 'Reference' && col.referenceType) {\n @let route = (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes());\n @if (route) {\n <a [routerLink]=\"route\">{{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}</a>\n } @else {\n {{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}\n }\n } @else {\n {{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length\" class=\"text-center text-muted\">\n {{ 'noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n style=\"opacity: 1;\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n </ng-template>\n </bs-grid>\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n", dependencies: [{ 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: RouterModule }, { kind: "directive", type: i2$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsButtonGroupComponent, selector: "bs-button-group" }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["rounded"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["noPadding"] }, { kind: "component", type: BsContainerComponent, selector: "bs-container" }, { 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: BsTableComponent, selector: "bs-table", inputs: ["isResponsive", "striped", "hover"] }, { kind: "component", type: BsTabControlComponent, selector: "bs-tab-control", inputs: ["border", "restrictDragging", "selectFirstTab", "tabsPosition", "allowDragDrop"] }, { kind: "component", type: BsTabPageComponent, selector: "bs-tab-page", inputs: ["disabled"] }, { kind: "directive", type: BsTabPageHeaderDirective, selector: "[bsTabPageHeader]" }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: AttributeValuePipe, name: "attributeValue" }, { kind: "pipe", type: RawAttributeValuePipe, name: "rawAttributeValue" }, { kind: "pipe", type: AsDetailColumnsPipe, name: "asDetailColumns" }, { kind: "pipe", type: AsDetailCellValuePipe, name: "asDetailCellValue" }, { kind: "pipe", type: ArrayValuePipe, name: "arrayValue" }, { kind: "pipe", type: ReferenceLinkRoutePipe, name: "referenceLinkRoute" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1479
1496
  }
1480
1497
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkPoDetailComponent, decorators: [{
1481
1498
  type: Component,
1482
- args: [{ selector: 'spark-po-detail', imports: [CommonModule, NgTemplateOutlet, RouterModule, BsAlertComponent, BsButtonGroupComponent, BsCardComponent, BsCardHeaderComponent, BsContainerComponent, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsTableComponent, SparkIconComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe, RawAttributeValuePipe, AsDetailColumnsPipe, AsDetailCellValuePipe, ArrayValuePipe, ReferenceLinkRoutePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"d-flex justify-content-between align-items-center mb-4\">\n <h2>{{ currentItem.breadcrumb || currentItem.name }}</h2>\n <bs-button-group>\n <button class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'back' | t }}\n </button>\n @if (canEdit()) {\n <button class=\"btn btn-primary\" (click)=\"onEdit()\">\n <spark-icon name=\"pencil\" /> {{ 'edit' | t }}\n </button>\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n }\n @if (showCustomActions()) {\n @for (action of customActions(); track action.name) {\n <button class=\"btn btn-outline-primary\" (click)=\"onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n }\n @if (canDelete()) {\n <button class=\"btn btn-danger\" (click)=\"onDelete()\">\n <spark-icon name=\"trash\" /> {{ 'delete' | t }}\n </button>\n }\n </bs-button-group>\n </div>\n\n <bs-card>\n <bs-card-header>{{ 'details' | t }}</bs-card-header>\n <div class=\"p-3\">\n <bs-grid>\n <dl bsRow>\n @for (attr of visibleAttributes(); track attr.id) {\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}</dt>\n <dd [sm]=\"9\">\n @if (getDetailFieldTemplate(attr); as customTpl) {\n <ng-container *ngTemplateOutlet=\"customTpl; context: getDetailFieldContext(attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (col.dataType === 'Reference' && col.referenceType) {\n @let route = (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes());\n @if (route) {\n <a [routerLink]=\"route\">{{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}</a>\n } @else {\n {{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}\n }\n } @else {\n {{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length\" class=\"text-center text-muted\">\n {{ 'noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n style=\"opacity: 1;\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n }\n </dl>\n </bs-grid>\n </div>\n </bs-card>\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n" }]
1483
- }], ctorParameters: () => [], propDecorators: { detailFieldTemplates: [{
1484
- type: ContentChildren,
1485
- args: [SparkDetailFieldTemplateDirective]
1486
- }], showCustomActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCustomActions", required: false }] }], extraActionsTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraActionsTemplate", required: false }] }], extraContentTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraContentTemplate", required: false }] }], edited: [{ type: i0.Output, args: ["edited"] }], deleted: [{ type: i0.Output, args: ["deleted"] }], customActionExecuted: [{ type: i0.Output, args: ["customActionExecuted"] }] } });
1499
+ args: [{ selector: 'spark-po-detail', imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, RouterModule, BsAlertComponent, BsButtonGroupComponent, BsCardComponent, BsCardHeaderComponent, BsContainerComponent, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsTableComponent, BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective, BsSpinnerComponent, SparkIconComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe, RawAttributeValuePipe, AsDetailColumnsPipe, AsDetailCellValuePipe, ArrayValuePipe, ReferenceLinkRoutePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-container>\n<div class=\"container\">\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n } @else if (item(); as currentItem) {\n @if (entityType(); as et) {\n <div class=\"d-flex justify-content-between align-items-center mb-4\">\n <h2>{{ currentItem.breadcrumb || currentItem.name }}</h2>\n <bs-button-group>\n <button class=\"btn btn-outline-secondary\" (click)=\"onBack()\">\n <spark-icon name=\"arrow-left\" /> {{ 'back' | t }}\n </button>\n @if (canEdit()) {\n <button class=\"btn btn-primary\" (click)=\"onEdit()\">\n <spark-icon name=\"pencil\" /> {{ 'edit' | t }}\n </button>\n }\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n }\n @if (showCustomActions()) {\n @for (action of customActions(); track action.name) {\n <button class=\"btn btn-outline-primary\" (click)=\"onCustomAction(action)\">\n {{ action.displayName | resolveTranslation }}\n </button>\n }\n }\n @if (canDelete()) {\n <button class=\"btn btn-danger\" (click)=\"onDelete()\">\n <spark-icon name=\"trash\" /> {{ 'delete' | t }}\n </button>\n }\n </bs-button-group>\n </div>\n\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=\"detailTabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #detailTabContent let-tab>\n @if (tab.id === '__default__') {\n @let ungroupedAttrs = ungroupedAttributes();\n @if (ungroupedAttrs.length > 0) {\n <bs-card style=\"display: block; margin: 1rem;\">\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of ungroupedAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\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 style=\"display: block; margin: 1rem;\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n <dl bsRow>\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"detailAttrField; context: { $implicit: attr, item: currentItem }\"></ng-container>\n }\n </dl>\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #detailAttrField let-attr let-currentItem=\"item\">\n <dt [sm]=\"3\">{{ (attr.label | resolveTranslation) || attr.name }}</dt>\n <dd [sm]=\"9\">\n @if (getDetailRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getDetailRendererInputs(attr, currentItem)\"></ng-container>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\">\n <thead>\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ (col.label | resolveTranslation) || col.name }}</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of (attr.name | arrayValue:currentItem); track $index) {\n <tr>\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (col.dataType === 'Reference' && col.referenceType) {\n @let route = (col.referenceType | referenceLinkRoute:row[col.name]:allEntityTypes());\n @if (route) {\n <a [routerLink]=\"route\">{{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}</a>\n } @else {\n {{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}\n }\n } @else {\n {{ (row | asDetailCellValue:attr:col:asDetailReferenceOptions()) }}\n }\n </td>\n }\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length\" class=\"text-center text-muted\">\n {{ 'noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\"\n style=\"opacity: 1;\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded me-2\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n {{ colorVal }}\n } @else {\n -\n }\n } @else if (attr.dataType === 'Reference' && attr.referenceType) {\n @let refRoute = (attr.referenceType | referenceLinkRoute:(attr.name | rawAttributeValue:item()):allEntityTypes());\n @if (refRoute && (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes())) {\n <a [routerLink]=\"refRoute\">{{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) }}</a>\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n } @else {\n {{ (attr.name | attributeValue:item():entityType():lookupReferenceOptions():allEntityTypes()) || '-' }}\n }\n </dd>\n </ng-template>\n </bs-grid>\n @if (extraContentTemplate(); as extraContentTpl) {\n <ng-container *ngTemplateOutlet=\"extraContentTpl; context: { $implicit: currentItem, entityType: et }\"></ng-container>\n }\n }\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n" }]
1500
+ }], ctorParameters: () => [], propDecorators: { showCustomActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCustomActions", required: false }] }], extraActionsTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraActionsTemplate", required: false }] }], extraContentTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraContentTemplate", required: false }] }], edited: [{ type: i0.Output, args: ["edited"] }], deleted: [{ type: i0.Output, args: ["deleted"] }], customActionExecuted: [{ type: i0.Output, args: ["customActionExecuted"] }] } });
1487
1501
 
1488
1502
  var sparkPoDetail_component = /*#__PURE__*/Object.freeze({
1489
1503
  __proto__: null,
1490
1504
  SparkPoDetailComponent: SparkPoDetailComponent
1491
1505
  });
1492
1506
 
1493
- class SparkColumnTemplateDirective {
1494
- template;
1495
- name = input.required({ ...(ngDevMode ? { debugName: "name" } : {}), alias: 'sparkColumnTemplate' });
1496
- constructor(template) {
1497
- this.template = template;
1498
- }
1499
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkColumnTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
1500
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.6", type: SparkColumnTemplateDirective, isStandalone: true, selector: "[sparkColumnTemplate]", inputs: { name: { classPropertyName: "name", publicName: "sparkColumnTemplate", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 });
1501
- }
1502
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkColumnTemplateDirective, decorators: [{
1503
- type: Directive,
1504
- args: [{
1505
- selector: '[sparkColumnTemplate]',
1506
- standalone: true
1507
- }]
1508
- }], ctorParameters: () => [{ type: i0.TemplateRef }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "sparkColumnTemplate", required: true }] }] } });
1509
-
1510
1507
  class SparkQueryListComponent {
1511
1508
  route = inject(ActivatedRoute);
1512
1509
  router = inject(Router);
1513
1510
  sparkService = inject(SparkService);
1514
- columnTemplates;
1511
+ rendererRegistry = inject(SPARK_ATTRIBUTE_RENDERERS);
1515
1512
  extraActionsTemplate = input(null, ...(ngDevMode ? [{ debugName: "extraActionsTemplate" }] : []));
1516
1513
  rowClicked = output();
1517
1514
  createClicked = output();
@@ -1682,23 +1679,17 @@ class SparkQueryListComponent {
1682
1679
  .filter(a => a.isVisible && hasShowedOnFlag(a.showedOn, ShowedOn.Query))
1683
1680
  .sort((a, b) => a.order - b.order) || [];
1684
1681
  }, ...(ngDevMode ? [{ debugName: "visibleAttributes" }] : []));
1685
- getColumnTemplate(attr) {
1686
- if (!this.columnTemplates)
1682
+ getColumnRendererComponent(attr) {
1683
+ if (!attr.renderer)
1687
1684
  return null;
1688
- const byName = this.columnTemplates.find(t => t.name() === attr.name);
1689
- if (byName)
1690
- return byName.template;
1691
- const byType = this.columnTemplates.find(t => t.name() === attr.dataType);
1692
- if (byType)
1693
- return byType.template;
1694
- return null;
1695
- }
1696
- getColumnTemplateContext(item, attr) {
1685
+ return this.rendererRegistry.find(r => r.name === attr.renderer)?.columnComponent ?? null;
1686
+ }
1687
+ getColumnRendererInputs(item, attr) {
1697
1688
  const itemAttr = item.attributes.find(a => a.name === attr.name);
1698
1689
  return {
1699
- $implicit: item,
1700
- attr,
1701
- value: itemAttr?.value
1690
+ value: itemAttr?.value,
1691
+ attribute: attr,
1692
+ options: attr.rendererOptions,
1702
1693
  };
1703
1694
  }
1704
1695
  async loadLookupReferenceOptions() {
@@ -1727,15 +1718,12 @@ class SparkQueryListComponent {
1727
1718
  }
1728
1719
  }
1729
1720
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkQueryListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1730
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkQueryListComponent, isStandalone: true, selector: "spark-query-list", inputs: { extraActionsTemplate: { classPropertyName: "extraActionsTemplate", publicName: "extraActionsTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowClicked: "rowClicked", createClicked: "createClicked" }, queries: [{ propertyName: "columnTemplates", predicate: SparkColumnTemplateDirective }], ngImport: i0, template: "<bs-container>\n<div class=\"container-fluid\">\n <div class=\"d-flex justify-content-between align-items-center mb-4\">\n <h2>{{ (query()?.description | resolveTranslation) || query()?.name || ('loading' | t) }}</h2>\n <div class=\"d-flex gap-2\">\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n }\n @if (canCreate()) {\n <button class=\"btn btn-primary\" (click)=\"onCreate()\">\n <spark-icon name=\"plus-lg\" /> {{ 'new' | t }}\n </button>\n }\n </div>\n </div>\n\n @if (entityType()) {\n <!-- Search box -->\n <bs-form>\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"4\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'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]=\"8\" class=\"text-end\">\n @if (searchTerm && paginationData()) {\n <span class=\"text-muted\">\n {{ paginationData()!.totalRecords }} {{ paginationData()!.totalRecords === 1 ? ('resultFound' | t) : ('resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n </bs-form>\n\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n\n <bs-datatable [(settings)]=\"settings\" (settingsChange)=\"onSettingsChange()\">\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 <tr *bsRowTemplate=\"let item of paginationData()\" (click)=\"onRowClick(item)\" style=\"cursor: pointer;\">\n @for (attr of visibleAttributes(); track attr.id) {\n <td>\n @if (getColumnTemplate(attr); as customTpl) {\n <ng-container *ngTemplateOutlet=\"customTpl; context: getColumnTemplateContext(item, attr)\"></ng-container>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n }\n } @else {\n {{ (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) }}\n }\n </td>\n }\n </tr>\n </bs-datatable>\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n", styles: ["tr:hover{background-color:#0000000d}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: RouterModule }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsContainerComponent, selector: "bs-container" }, { kind: "component", type: BsDatatableComponent, selector: "bs-datatable", inputs: ["settings", "data"], outputs: ["settingsChange", "dataChange"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable"] }, { kind: "directive", type: BsRowTemplateDirective, selector: "[bsRowTemplate]", inputs: ["bsRowTemplateOf"] }, { 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: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: AttributeValuePipe, name: "attributeValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1721
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SparkQueryListComponent, isStandalone: true, selector: "spark-query-list", inputs: { extraActionsTemplate: { classPropertyName: "extraActionsTemplate", publicName: "extraActionsTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowClicked: "rowClicked", createClicked: "createClicked" }, ngImport: i0, template: "<bs-container>\n<div class=\"container-fluid\">\n <div class=\"d-flex justify-content-between align-items-center mb-4\">\n <h2>{{ (query()?.description | resolveTranslation) || query()?.name || ('loading' | t) }}</h2>\n <div class=\"d-flex gap-2\">\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n }\n @if (canCreate()) {\n <button class=\"btn btn-primary\" (click)=\"onCreate()\">\n <spark-icon name=\"plus-lg\" /> {{ 'new' | t }}\n </button>\n }\n </div>\n </div>\n\n @if (entityType()) {\n <!-- Search box -->\n <bs-form>\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"4\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'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]=\"8\" class=\"text-end\">\n @if (searchTerm && paginationData()) {\n <span class=\"text-muted\">\n {{ paginationData()!.totalRecords }} {{ paginationData()!.totalRecords === 1 ? ('resultFound' | t) : ('resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n </bs-form>\n\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n\n <bs-datatable [(settings)]=\"settings\" (settingsChange)=\"onSettingsChange()\">\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 <tr *bsRowTemplate=\"let item of paginationData()\" (click)=\"onRowClick(item)\" style=\"cursor: pointer;\">\n @for (attr of visibleAttributes(); track attr.id) {\n <td>\n @if (getColumnRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getColumnRendererInputs(item, attr)\"></ng-container>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n }\n } @else {\n {{ (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) }}\n }\n </td>\n }\n </tr>\n </bs-datatable>\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n", styles: ["tr:hover{background-color:#0000000d}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}\n"], dependencies: [{ 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.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: RouterModule }, { kind: "component", type: BsAlertComponent, selector: "bs-alert", inputs: ["type", "isVisible"], outputs: ["isVisibleChange", "afterOpenedOrClosed"] }, { kind: "component", type: BsContainerComponent, selector: "bs-container" }, { kind: "component", type: BsDatatableComponent, selector: "bs-datatable", inputs: ["settings", "data"], outputs: ["settingsChange", "dataChange"] }, { kind: "directive", type: BsDatatableColumnDirective, selector: "[bsDatatableColumn]", inputs: ["bsDatatableColumn", "bsDatatableColumnSortable"] }, { kind: "directive", type: BsRowTemplateDirective, selector: "[bsRowTemplate]", inputs: ["bsRowTemplateOf"] }, { 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: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: AttributeValuePipe, name: "attributeValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1731
1722
  }
1732
1723
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SparkQueryListComponent, decorators: [{
1733
1724
  type: Component,
1734
- args: [{ selector: 'spark-query-list', imports: [CommonModule, NgTemplateOutlet, FormsModule, RouterModule, BsAlertComponent, BsContainerComponent, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsInputGroupComponent, SparkIconComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-container>\n<div class=\"container-fluid\">\n <div class=\"d-flex justify-content-between align-items-center mb-4\">\n <h2>{{ (query()?.description | resolveTranslation) || query()?.name || ('loading' | t) }}</h2>\n <div class=\"d-flex gap-2\">\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n }\n @if (canCreate()) {\n <button class=\"btn btn-primary\" (click)=\"onCreate()\">\n <spark-icon name=\"plus-lg\" /> {{ 'new' | t }}\n </button>\n }\n </div>\n </div>\n\n @if (entityType()) {\n <!-- Search box -->\n <bs-form>\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"4\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'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]=\"8\" class=\"text-end\">\n @if (searchTerm && paginationData()) {\n <span class=\"text-muted\">\n {{ paginationData()!.totalRecords }} {{ paginationData()!.totalRecords === 1 ? ('resultFound' | t) : ('resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n </bs-form>\n\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n\n <bs-datatable [(settings)]=\"settings\" (settingsChange)=\"onSettingsChange()\">\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 <tr *bsRowTemplate=\"let item of paginationData()\" (click)=\"onRowClick(item)\" style=\"cursor: pointer;\">\n @for (attr of visibleAttributes(); track attr.id) {\n <td>\n @if (getColumnTemplate(attr); as customTpl) {\n <ng-container *ngTemplateOutlet=\"customTpl; context: getColumnTemplateContext(item, attr)\"></ng-container>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n }\n } @else {\n {{ (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) }}\n }\n </td>\n }\n </tr>\n </bs-datatable>\n } @else {\n <div class=\"text-center p-5\">\n <div class=\"spinner-border\" role=\"status\">\n <span class=\"visually-hidden\">{{ 'loading' | t }}</span>\n </div>\n </div>\n }\n</div>\n</bs-container>\n", styles: ["tr:hover{background-color:#0000000d}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}\n"] }]
1735
- }], ctorParameters: () => [], propDecorators: { columnTemplates: [{
1736
- type: ContentChildren,
1737
- args: [SparkColumnTemplateDirective]
1738
- }], extraActionsTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraActionsTemplate", required: false }] }], rowClicked: [{ type: i0.Output, args: ["rowClicked"] }], createClicked: [{ type: i0.Output, args: ["createClicked"] }] } });
1725
+ args: [{ selector: 'spark-query-list', imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, FormsModule, RouterModule, BsAlertComponent, BsContainerComponent, BsDatatableComponent, BsDatatableColumnDirective, BsRowTemplateDirective, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsInputGroupComponent, BsSpinnerComponent, SparkIconComponent, ResolveTranslationPipe, TranslateKeyPipe, AttributeValuePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-container>\n<div class=\"container-fluid\">\n <div class=\"d-flex justify-content-between align-items-center mb-4\">\n <h2>{{ (query()?.description | resolveTranslation) || query()?.name || ('loading' | t) }}</h2>\n <div class=\"d-flex gap-2\">\n @if (extraActionsTemplate(); as extraActionsTpl) {\n <ng-container *ngTemplateOutlet=\"extraActionsTpl\"></ng-container>\n }\n @if (canCreate()) {\n <button class=\"btn btn-primary\" (click)=\"onCreate()\">\n <spark-icon name=\"plus-lg\" /> {{ 'new' | t }}\n </button>\n }\n </div>\n </div>\n\n @if (entityType()) {\n <!-- Search box -->\n <bs-form>\n <bs-grid>\n <div bsRow class=\"mb-3\">\n <div [md]=\"4\">\n <bs-input-group>\n <span class=\"input-group-text\">\n <spark-icon name=\"search\" />\n </span>\n <input\n type=\"text\"\n [placeholder]=\"'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]=\"8\" class=\"text-end\">\n @if (searchTerm && paginationData()) {\n <span class=\"text-muted\">\n {{ paginationData()!.totalRecords }} {{ paginationData()!.totalRecords === 1 ? ('resultFound' | t) : ('resultsFound' | t) }}\n </span>\n }\n </div>\n </div>\n </bs-grid>\n </bs-form>\n\n @if (errorMessage(); as err) {\n <bs-alert [type]=\"colors.danger\" class=\"mb-3\">\n {{ err }}\n </bs-alert>\n }\n\n <bs-datatable [(settings)]=\"settings\" (settingsChange)=\"onSettingsChange()\">\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 <tr *bsRowTemplate=\"let item of paginationData()\" (click)=\"onRowClick(item)\" style=\"cursor: pointer;\">\n @for (attr of visibleAttributes(); track attr.id) {\n <td>\n @if (getColumnRendererComponent(attr); as rendererType) {\n <ng-container *ngComponentOutlet=\"rendererType; inputs: getColumnRendererInputs(item, attr)\"></ng-container>\n } @else if (attr.dataType === 'boolean') {\n <input type=\"checkbox\"\n [checked]=\"(attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) === true\"\n [indeterminate]=\"(attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) == null\"\n disabled\n onclick=\"return false;\">\n } @else if (attr.dataType === 'color') {\n @let colorVal = (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes());\n @if (colorVal) {\n <span class=\"d-inline-block align-middle border rounded\" [style.background-color]=\"colorVal\" style=\"width: 1.5em; height: 1.5em;\"></span>\n }\n } @else {\n {{ (attr.name | attributeValue:item:entityType():lookupReferenceOptions():allEntityTypes()) }}\n }\n </td>\n }\n </tr>\n </bs-datatable>\n } @else {\n <div class=\"text-center p-5\">\n <bs-spinner />\n </div>\n }\n</div>\n</bs-container>\n", styles: ["tr:hover{background-color:#0000000d}td input[type=checkbox]:disabled{opacity:1;pointer-events:none}\n"] }]
1726
+ }], ctorParameters: () => [], propDecorators: { extraActionsTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraActionsTemplate", required: false }] }], rowClicked: [{ type: i0.Output, args: ["rowClicked"] }], createClicked: [{ type: i0.Output, args: ["createClicked"] }] } });
1739
1727
 
1740
1728
  var sparkQueryList_component = /*#__PURE__*/Object.freeze({
1741
1729
  __proto__: null,
@@ -1885,5 +1873,5 @@ function provideSpark(config) {
1885
1873
  * Generated bundle index. Do not edit.
1886
1874
  */
1887
1875
 
1888
- export { ArrayValuePipe, AsDetailCellValuePipe, AsDetailColumnsPipe, AsDetailDisplayValuePipe, AsDetailTypePipe, AttributeValuePipe, CanCreateDetailRowPipe, CanDeleteDetailRowPipe, ELookupDisplayType, ErrorForAttributePipe, IconNamePipe, InlineRefOptionsPipe, InputTypePipe, LookupDisplayTypePipe, LookupDisplayValuePipe, LookupOptionsPipe, RawAttributeValuePipe, ReferenceAttrValuePipe, ReferenceDisplayValuePipe, ReferenceLinkRoutePipe, ResolveTranslationPipe, RetryActionService, RouterLinkPipe, SPARK_CONFIG, ShowedOn, SparkColumnTemplateDirective, SparkDetailFieldTemplateDirective, SparkFieldTemplateDirective, SparkIconComponent, SparkIconRegistry, SparkLanguageService, SparkPoCreateComponent, SparkPoDetailComponent, SparkPoEditComponent, SparkPoFormComponent, SparkQueryListComponent, SparkRetryActionModalComponent, SparkService, TranslateKeyPipe, defaultSparkConfig, hasShowedOnFlag, provideSpark, resolveTranslation, sparkRoutes };
1876
+ export { ArrayValuePipe, AsDetailCellValuePipe, AsDetailColumnsPipe, AsDetailDisplayValuePipe, AsDetailTypePipe, AttributeValuePipe, CanCreateDetailRowPipe, CanDeleteDetailRowPipe, ELookupDisplayType, ErrorForAttributePipe, IconNamePipe, InlineRefOptionsPipe, InputTypePipe, LookupDisplayTypePipe, LookupDisplayValuePipe, LookupOptionsPipe, RawAttributeValuePipe, ReferenceAttrValuePipe, ReferenceDisplayValuePipe, ReferenceLinkRoutePipe, ResolveTranslationPipe, RetryActionService, RouterLinkPipe, SPARK_ATTRIBUTE_RENDERERS, SPARK_CONFIG, ShowedOn, SparkIconComponent, SparkIconRegistry, SparkLanguageService, SparkPoCreateComponent, SparkPoDetailComponent, SparkPoEditComponent, SparkPoFormComponent, SparkQueryListComponent, SparkRetryActionModalComponent, SparkService, TranslateKeyPipe, defaultSparkConfig, hasShowedOnFlag, provideSpark, provideSparkAttributeRenderers, resolveTranslation, sparkRoutes };
1889
1877
  //# sourceMappingURL=mintplayer-ng-spark.mjs.map