@mintplayer/ng-spark 22.6.0 → 22.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/fesm2022/mintplayer-ng-spark-grid.mjs +133 -52
  2. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  3. package/fesm2022/mintplayer-ng-spark-models.mjs +77 -7
  4. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  5. package/fesm2022/mintplayer-ng-spark-pipes.mjs +85 -4
  6. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  7. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +3 -3
  8. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  9. package/fesm2022/mintplayer-ng-spark-po-form.mjs +12 -14
  10. package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
  11. package/fesm2022/mintplayer-ng-spark-query-list.mjs +21 -7
  12. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  13. package/fesm2022/mintplayer-ng-spark-renderers.mjs +14 -1
  14. package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -1
  15. package/fesm2022/mintplayer-ng-spark-services.mjs +92 -5
  16. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  17. package/fesm2022/mintplayer-ng-spark-shell.mjs +41 -4
  18. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  19. package/package.json +1 -1
  20. package/types/mintplayer-ng-spark-grid.d.ts +75 -33
  21. package/types/mintplayer-ng-spark-models.d.ts +132 -7
  22. package/types/mintplayer-ng-spark-pipes.d.ts +33 -5
  23. package/types/mintplayer-ng-spark-po-detail.d.ts +2 -2
  24. package/types/mintplayer-ng-spark-po-form.d.ts +7 -7
  25. package/types/mintplayer-ng-spark-query-list.d.ts +11 -4
  26. package/types/mintplayer-ng-spark-renderers.d.ts +29 -11
  27. package/types/mintplayer-ng-spark-services.d.ts +71 -2
  28. package/types/mintplayer-ng-spark-shell.d.ts +32 -2
@@ -77,7 +77,7 @@ class SparkReferencePickerComponent {
77
77
  .sort((a, b) => a.order - b.order) || [];
78
78
  }, /* @ts-ignore */
79
79
  ...(ngDevMode ? [{ debugName: "visibleAttributes" }] : /* istanbul ignore next */ []));
80
- // Typed rows so the datatable generic infers PersistentObject.
80
+ // Typed rows so the datatable generic infers the row type.
81
81
  rows = computed(() => this.pagination()?.data ?? [], /* @ts-ignore */
82
82
  ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
83
83
  displayValue = computed(() => {
@@ -85,7 +85,7 @@ class SparkReferencePickerComponent {
85
85
  if (!id)
86
86
  return this.lang.t('notSelected');
87
87
  const selected = this.options().find(o => o.id === id);
88
- return selected?.breadcrumb || selected?.name || id;
88
+ return selected?.breadcrumb || id;
89
89
  }, /* @ts-ignore */
90
90
  ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
91
91
  async open() {
@@ -113,12 +113,10 @@ class SparkReferencePickerComponent {
113
113
  if (this.searchTerm.trim()) {
114
114
  const term = this.searchTerm.toLowerCase().trim();
115
115
  filteredItems = this.options().filter(item => {
116
- if (item.name?.toLowerCase().includes(term))
117
- return true;
118
116
  if (item.breadcrumb?.toLowerCase().includes(term))
119
117
  return true;
120
- return item.attributes.some(attr => {
121
- const value = attr.breadcrumb || attr.value;
118
+ return item.values.some(v => {
119
+ const value = v.breadcrumb ?? v.value;
122
120
  if (value == null)
123
121
  return false;
124
122
  return String(value).toLowerCase().includes(term);
@@ -488,7 +486,7 @@ class SparkPoFormComponent {
488
486
  parentId: this.parentId(),
489
487
  parentType: this.parentType(),
490
488
  });
491
- return [attr.name, result.data];
489
+ return [attr.name, result.items];
492
490
  }));
493
491
  const optionsByAttr = this.toRecord(entries);
494
492
  this.referenceOptions.set(optionsByAttr);
@@ -503,7 +501,7 @@ class SparkPoFormComponent {
503
501
  const pos = optionsByAttr[attr.name] || [];
504
502
  const nodes = pos
505
503
  .filter(po => !!po.id)
506
- .map(po => ({ id: po.id, label: po.breadcrumb || po.name || po.id }));
504
+ .map(po => ({ id: po.id, label: po.breadcrumb || po.id }));
507
505
  providers[attr.name] = new InMemoryTreeSelectProvider(nodes);
508
506
  nodesByAttr[attr.name] = this.toRecord(nodes.map(n => [n.id, n]));
509
507
  }
@@ -559,7 +557,7 @@ class SparkPoFormComponent {
559
557
  parentId: this.parentId(),
560
558
  parentType: this.parentType(),
561
559
  });
562
- return [col.name, result.data];
560
+ return [col.name, result.items];
563
561
  }));
564
562
  this.asDetailReferenceOptions.update(prev => ({ ...prev, [attr.name]: this.toRecord(refEntries) }));
565
563
  }
@@ -848,12 +846,12 @@ class SparkPoFormComponent {
848
846
  const attr = byName.get(name);
849
847
  if (attr?.dataType !== 'Reference')
850
848
  continue;
849
+ // A refresh hook replaced this attribute's options, so synthesise the same row shape the
850
+ // query path produces: an id and a display string, which is all an option list ever needed.
851
851
  next[name] = (options ?? []).map(o => ({
852
852
  id: o.key,
853
- name: attr.referenceType ?? '',
854
- objectTypeId: '',
855
853
  breadcrumb: o.label ? resolveTranslation(o.label) : o.key,
856
- attributes: [],
854
+ values: [],
857
855
  }));
858
856
  }
859
857
  return next;
@@ -952,11 +950,11 @@ class SparkPoFormComponent {
952
950
  this.formData.set(data);
953
951
  }
954
952
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkPoFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
955
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SparkPoFormComponent, isStandalone: true, selector: "spark-po-form", inputs: { entityType: { classPropertyName: "entityType", publicName: "entityType", isSignal: true, isRequired: false, transformFunction: null }, formData: { classPropertyName: "formData", publicName: "formData", isSignal: true, isRequired: false, transformFunction: null }, validationErrors: { classPropertyName: "validationErrors", publicName: "validationErrors", isSignal: true, isRequired: false, transformFunction: null }, showButtons: { classPropertyName: "showButtons", publicName: "showButtons", isSignal: true, isRequired: false, transformFunction: null }, isSaving: { classPropertyName: "isSaving", publicName: "isSaving", isSignal: true, isRequired: false, transformFunction: null }, parentId: { classPropertyName: "parentId", publicName: "parentId", isSignal: true, isRequired: false, transformFunction: null }, parentType: { classPropertyName: "parentType", publicName: "parentType", isSignal: true, isRequired: false, transformFunction: null }, objectTypeId: { classPropertyName: "objectTypeId", publicName: "objectTypeId", isSignal: true, isRequired: false, transformFunction: null }, objectId: { classPropertyName: "objectId", publicName: "objectId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formData: "formDataChange", save: "save", cancel: "cancel" }, ngImport: i0, template: "<bs-form>\n @if (entityType()) {\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"tabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #tabContent let-tab>\n @if (tab.id === '__default__') {\n @if (ungroupedAttributes().length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n @for (attr of ungroupedAttributes(); track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #attrField let-attr>\n <div bsRow class=\"mb-3\">\n <label [md]=\"4\" bsColFormLabel [for]=\"attr.name\">\n {{ attr.label | resolveTranslation:attr.name }}\n @if (attr.isRequired) {\n <span class=\"text-danger\">*</span>\n }\n </label>\n <div [md]=\"8\">\n @if (attr.dataType === 'boolean') {\n <bs-checkbox\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange(attr)\">\n </bs-checkbox>\n } @else if (attr.lookupReferenceType) {\n @if ((attr | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <spark-lookup-picker\n [value]=\"formData()[attr.name]\"\n [options]=\"getLookupOptions(attr)\"\n [inputId]=\"attr.name\"\n [title]=\"attr.label | resolveTranslation:attr.name\"\n [isInvalid]=\"hasError(attr.name)\"\n (valueChange)=\"onLookupValueChange(attr, $event)\">\n </spark-lookup-picker>\n } @else {\n <bs-select\n class=\"w-100\"\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange(attr)\"\n [id]=\"attr.name\"\n [class.is-invalid]=\"hasError(attr.name)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | lookupOptions:lookupReferenceOptions()); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n <bs-tree-select\n mode=\"multiple\"\n variant=\"textbox\"\n [showClear]=\"true\"\n [placeholder]=\"'common.search' | t\"\n [provider]=\"getReferenceProvider(attr)\"\n [ngModel]=\"referenceTreeValues()[attr.name] || []\"\n (ngModelChange)=\"onReferenceTreeChange(attr, $event)\"\n [ngModelOptions]=\"{ standalone: true }\">\n </bs-tree-select>\n } @else if (attr.dataType === 'Reference') {\n <spark-reference-picker\n [value]=\"formData()[attr.name]\"\n [options]=\"referenceOptions()[attr.name] || []\"\n [referenceType]=\"attr.referenceType\"\n [inputId]=\"attr.name\"\n [title]=\"attr.label | resolveTranslation:attr.name\"\n [isInvalid]=\"hasError(attr.name)\"\n (valueChange)=\"onReferenceValueChange(attr, $event)\">\n </spark-reference-picker>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray && attr.editMode === 'inline') {\n <!-- One source of truth for an inline cell, reused by the row and by the drag\n preview. The preview is a live Angular view (not a cloneNode), so its\n <bs-select> binds to row[col.name] and shows the value while dragging. -->\n <ng-template #inlineDetailCell let-row=\"row\" let-col=\"col\" let-attr=\"attr\" let-rowIndex=\"rowIndex\">\n <td>\n @if (col.isReadOnly) {\n <span class=\"text-muted\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n } @else if (getAsDetailCellEditRenderer(col); as editRenderer) {\n <ng-container *ngComponentOutlet=\"editRenderer; inputs: getAsDetailCellEditRendererInputs(editRenderer, row, col)\"></ng-container>\n } @else if (col.dataType === 'AsDetail') {\n <!-- Nested AsDetail can't flatten into a row: escalate to the row modal,\n where the recursive spark-po-form renders the nested structure. -->\n <div class=\"d-flex align-items-center gap-1\">\n <span class=\"text-truncate flex-grow-1\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\" (click)=\"editArrayItem(attr, rowIndex)\">\n <spark-icon name=\"pencil\" />\n </button>\n </div>\n } @else if (col.dataType === 'boolean') {\n <bs-checkbox\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onInlineCellChange(attr, rowIndex, col)\">\n </bs-checkbox>\n } @else if (col.dataType === 'Reference' && col.query) {\n @if (col.referenceDisplayType === EReferenceDisplayType.Modal) {\n <spark-reference-picker\n [value]=\"row[col.name]\"\n [options]=\"(attr | inlineRefOptions:col:asDetailReferenceOptions())\"\n [referenceType]=\"col.referenceType\"\n [title]=\"col.label | resolveTranslation:col.name\"\n [isInvalid]=\"hasInlineError(attr, rowIndex, col)\"\n (valueChange)=\"row[col.name] = $event; onInlineCellChange(attr, rowIndex, col)\">\n </spark-reference-picker>\n } @else {\n <bs-select\n class=\"w-100\"\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onInlineCellChange(attr, rowIndex, col)\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | inlineRefOptions:col:asDetailReferenceOptions()); track option.id) {\n <option [ngValue]=\"option.id\">\n {{ option.breadcrumb || option.name || option.id }}\n </option>\n }\n </bs-select>\n }\n } @else if (col.lookupReferenceType) {\n @if ((col | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <spark-lookup-picker\n [value]=\"row[col.name]\"\n [options]=\"getLookupOptions(col)\"\n [title]=\"col.label | resolveTranslation:col.name\"\n [isInvalid]=\"hasInlineError(attr, rowIndex, col)\"\n (valueChange)=\"row[col.name] = $event; onInlineCellChange(attr, rowIndex, col)\">\n </spark-lookup-picker>\n } @else {\n <bs-select\n class=\"w-100\"\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onInlineCellChange(attr, rowIndex, col)\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of getLookupOptions(col); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else if (col.dataType === 'MultiLineString') {\n <textarea\n [(ngModel)]=\"row[col.name]\"\n [required]=\"col.isRequired\"\n rows=\"3\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\"\n (ngModelChange)=\"onFieldChange()\"\n (blur)=\"onInlineCellBlur(attr, rowIndex, col)\"></textarea>\n } @else {\n <input\n [type]=\"col.dataType | inputType\"\n [(ngModel)]=\"row[col.name]\"\n [required]=\"col.isRequired\"\n [step]=\"col.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\"\n (ngModelChange)=\"onFieldChange()\"\n (blur)=\"onInlineCellBlur(attr, rowIndex, col)\">\n }\n @if (inlineErrorMessage(attr, rowIndex, col); as errorMsg) {\n <div class=\"invalid-feedback d-block\">{{ errorMsg }}</div>\n }\n </td>\n </ng-template>\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 50px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index; let rowIndex = $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n <td class=\"text-nowrap\">\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"removeArrayItem(attr, rowIndex)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n\n <!-- Floating preview = a LIVE view of the row's cells (not CDK's cloneNode),\n so <bs-select> shows its value instead of blanking. The in-grid\n placeholder still uses CDK's default clone (exact row height) and is\n hidden via .cdk-drag-placeholder { visibility:hidden } in the styles,\n so the drop gap is blank without collapsing/shifting the other rows. -->\n <ng-template cdkDragPreview>\n <table class=\"table mb-0 bg-body shadow-sm spark-drag-preview\">\n <tbody class=\"align-middle\">\n <tr>\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"width: 32px\"><spark-icon name=\"grip-vertical\" /></td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n </tr>\n </tbody>\n </table>\n </ng-template>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addInlineRow(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 80px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (getAsDetailCellRendererComponent(col); as cellRenderer) {\n <ng-container *ngComponentOutlet=\"cellRenderer; inputs: getAsDetailCellRendererInputs(cellRenderer, row, col)\"></ng-container>\n } @else {\n {{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}\n }\n </td>\n }\n <td class=\"text-nowrap\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary me-1\" (click)=\"editArrayItem(attr, $index)\">\n <spark-icon name=\"pencil\" />\n </button>\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"removeArrayItem(attr, $index)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addArrayItem(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail') {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | asDetailDisplayValue:formData():asDetailTypes()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openAsDetailEditor(attr)\">\n <spark-icon name=\"pencil\" />\n </button>\n </bs-input-group>\n } @else if (getEditRendererComponent(attr); as editComp) {\n <ng-container *ngComponentOutlet=\"editComp; inputs: getEditRendererInputs(editComp, attr)\"></ng-container>\n } @else if (attr.dataType === 'MultiLineString') {\n <textarea\n [id]=\"attr.name\"\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange(attr)\"\n (blur)=\"onFieldBlur(attr)\"\n [name]=\"attr.name\"\n [required]=\"attr.isRequired\"\n rows=\"6\"\n [class.is-invalid]=\"hasError(attr.name)\"></textarea>\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(attr)\"\n (blur)=\"onFieldBlur(attr)\"\n [name]=\"attr.name\"\n [required]=\"attr.isRequired\"\n [step]=\"attr.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasError(attr.name)\">\n }\n @if (attr.name | errorForAttribute:validationErrors(); as errorMsg) {\n <div class=\"invalid-feedback d-block\">\n {{ errorMsg }}\n </div>\n }\n </div>\n </div>\n </ng-template>\n\n @if (showButtons()) {\n <div bsRow class=\"mt-4\">\n <div [md]=\"4\"></div>\n <div [md]=\"8\" class=\"d-flex justify-content-end gap-2\">\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"onCancel()\" [disabled]=\"isSaving()\">{{ 'common.cancel' | t }}</button>\n <button type=\"submit\" [color]=\"colors.primary\" [disabled]=\"isSaving()\" (click)=\"onSave()\">\n @if (isSaving()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'common.save' | t }}\n </button>\n </div>\n </div>\n }\n </bs-grid>\n}\n\n<!-- Modal for editing AsDetail objects -->\n<bs-modal [isOpen]=\"showAsDetailModal()\" (isOpenChange)=\"!$event && closeAsDetailModal()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.edit' | t }} {{ editingAsDetailAttr()?.label | resolveTranslation:editingAsDetailAttr()?.name }}</h5>\n </div>\n\n @if (editingAsDetailAttr(); as attr) {\n <div bsModalBody>\n <spark-po-form\n [entityType]=\"attr | asDetailType:asDetailTypes()\"\n [(formData)]=\"asDetailFormData\"\n [parentId]=\"parentId()\"\n [parentType]=\"parentType()\">\n </spark-po-form>\n </div>\n }\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeAsDetailModal()\">{{ 'common.cancel' | t }}</button>\n <button type=\"button\" [color]=\"colors.primary\" (click)=\"saveAsDetailObject()\">{{ 'common.save' | t }}</button>\n </div>\n </div>\n</bs-modal>\n</bs-form>\n", styles: [":host ::ng-deep .cdk-drag-placeholder{visibility:hidden}\n"], dependencies: [{ kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving", "parentId", "parentType", "objectTypeId", "objectId"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["color", "outline"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["color", "navStyle"] }, { kind: "component", type: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsGridComponent, selector: "bs-grid", inputs: ["stopFullWidthAt"] }, { kind: "directive", type: BsGridRowDirective, selector: "[bsRow]" }, { kind: "directive", type: BsGridColumnDirective, selector: "[xxs],[xs],[sm],[md],[lg],[xl],[xxl]", inputs: ["xxs", "xs", "sm", "md", "lg", "xl", "xxl"] }, { kind: "directive", type: BsColFormLabelDirective, selector: "[bsColFormLabel]" }, { kind: "directive", type: BsButtonTypeDirective, selector: "button[color],input[type=\"button\"][color],input[type=\"submit\"][color],a[color]", inputs: ["color"] }, { kind: "component", type: BsInputGroupComponent, selector: "bs-input-group", inputs: ["size"] }, { 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: BsTreeSelectComponent, selector: "bs-tree-select", inputs: ["provider", "mode", "variant", "cascadeSelect", "placeholder", "showClear", "panelScrollHeight", "searchDebounceMs", "disabled", "value"], outputs: ["valueChange", "opened", "closed", "cleared"] }, { kind: "component", type: BsModalHostComponent, selector: "bs-modal", inputs: ["isOpen", "closeOnEscape", "scrollable"], outputs: ["isOpenChange"] }, { kind: "directive", type: BsModalDirective, selector: "[bsModal]" }, { kind: "directive", type: BsModalHeaderDirective, selector: "[bsModalHeader]" }, { kind: "directive", type: BsModalBodyDirective, selector: "[bsModalBody]" }, { kind: "directive", type: BsModalFooterDirective, selector: "[bsModalFooter]" }, { kind: "component", type: BsTableComponent, selector: "bs-table", inputs: ["isResponsive", "striped", "hover", "border", "ariaRowCount"] }, { kind: "component", type: BsCheckboxComponent, selector: "bs-checkbox", inputs: ["type", "isToggled", "indeterminate", "name", "value", "group"], outputs: ["isToggledChange", "indeterminateChange"] }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: BsTabControlComponent, selector: "bs-tab-control", inputs: ["border", "selectFirstTab", "tabsPosition"] }, { kind: "component", type: BsTabPageComponent, selector: "bs-tab-page", inputs: ["disabled"] }, { kind: "directive", type: BsTabPageHeaderDirective, selector: "[bsTabPageHeader]" }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "component", type: SparkReferencePickerComponent, selector: "spark-reference-picker", inputs: ["value", "options", "referenceType", "isInvalid", "inputId", "title"], outputs: ["valueChange"] }, { kind: "component", type: SparkLookupPickerComponent, selector: "spark-lookup-picker", inputs: ["value", "options", "isInvalid", "inputId", "title"], outputs: ["valueChange"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: InputTypePipe, name: "inputType" }, { kind: "pipe", type: LookupDisplayTypePipe, name: "lookupDisplayType" }, { kind: "pipe", type: LookupOptionsPipe, name: "lookupOptions" }, { kind: "pipe", type: AsDetailDisplayValuePipe, name: "asDetailDisplayValue" }, { kind: "pipe", type: AsDetailTypePipe, name: "asDetailType" }, { kind: "pipe", type: AsDetailColumnsPipe, name: "asDetailColumns" }, { kind: "pipe", type: AsDetailCellValuePipe, name: "asDetailCellValue" }, { kind: "pipe", type: CanCreateDetailRowPipe, name: "canCreateDetailRow" }, { kind: "pipe", type: CanDeleteDetailRowPipe, name: "canDeleteDetailRow" }, { kind: "pipe", type: InlineRefOptionsPipe, name: "inlineRefOptions" }, { kind: "pipe", type: ErrorForAttributePipe, name: "errorForAttribute" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
953
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: SparkPoFormComponent, isStandalone: true, selector: "spark-po-form", inputs: { entityType: { classPropertyName: "entityType", publicName: "entityType", isSignal: true, isRequired: false, transformFunction: null }, formData: { classPropertyName: "formData", publicName: "formData", isSignal: true, isRequired: false, transformFunction: null }, validationErrors: { classPropertyName: "validationErrors", publicName: "validationErrors", isSignal: true, isRequired: false, transformFunction: null }, showButtons: { classPropertyName: "showButtons", publicName: "showButtons", isSignal: true, isRequired: false, transformFunction: null }, isSaving: { classPropertyName: "isSaving", publicName: "isSaving", isSignal: true, isRequired: false, transformFunction: null }, parentId: { classPropertyName: "parentId", publicName: "parentId", isSignal: true, isRequired: false, transformFunction: null }, parentType: { classPropertyName: "parentType", publicName: "parentType", isSignal: true, isRequired: false, transformFunction: null }, objectTypeId: { classPropertyName: "objectTypeId", publicName: "objectTypeId", isSignal: true, isRequired: false, transformFunction: null }, objectId: { classPropertyName: "objectId", publicName: "objectId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formData: "formDataChange", save: "save", cancel: "cancel" }, ngImport: i0, template: "<bs-form>\n @if (entityType()) {\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"tabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #tabContent let-tab>\n @if (tab.id === '__default__') {\n @if (ungroupedAttributes().length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n @for (attr of ungroupedAttributes(); track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #attrField let-attr>\n <div bsRow class=\"mb-3\">\n <label [md]=\"4\" bsColFormLabel [for]=\"attr.name\">\n {{ attr.label | resolveTranslation:attr.name }}\n @if (attr.isRequired) {\n <span class=\"text-danger\">*</span>\n }\n </label>\n <div [md]=\"8\">\n @if (attr.dataType === 'boolean') {\n <bs-checkbox\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange(attr)\">\n </bs-checkbox>\n } @else if (attr.lookupReferenceType) {\n @if ((attr | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <spark-lookup-picker\n [value]=\"formData()[attr.name]\"\n [options]=\"getLookupOptions(attr)\"\n [inputId]=\"attr.name\"\n [title]=\"attr.label | resolveTranslation:attr.name\"\n [isInvalid]=\"hasError(attr.name)\"\n (valueChange)=\"onLookupValueChange(attr, $event)\">\n </spark-lookup-picker>\n } @else {\n <bs-select\n class=\"w-100\"\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange(attr)\"\n [id]=\"attr.name\"\n [class.is-invalid]=\"hasError(attr.name)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | lookupOptions:lookupReferenceOptions()); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n <bs-tree-select\n mode=\"multiple\"\n variant=\"textbox\"\n [showClear]=\"true\"\n [placeholder]=\"'common.search' | t\"\n [provider]=\"getReferenceProvider(attr)\"\n [ngModel]=\"referenceTreeValues()[attr.name] || []\"\n (ngModelChange)=\"onReferenceTreeChange(attr, $event)\"\n [ngModelOptions]=\"{ standalone: true }\">\n </bs-tree-select>\n } @else if (attr.dataType === 'Reference') {\n <spark-reference-picker\n [value]=\"formData()[attr.name]\"\n [options]=\"referenceOptions()[attr.name] || []\"\n [referenceType]=\"attr.referenceType\"\n [inputId]=\"attr.name\"\n [title]=\"attr.label | resolveTranslation:attr.name\"\n [isInvalid]=\"hasError(attr.name)\"\n (valueChange)=\"onReferenceValueChange(attr, $event)\">\n </spark-reference-picker>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray && attr.editMode === 'inline') {\n <!-- One source of truth for an inline cell, reused by the row and by the drag\n preview. The preview is a live Angular view (not a cloneNode), so its\n <bs-select> binds to row[col.name] and shows the value while dragging. -->\n <ng-template #inlineDetailCell let-row=\"row\" let-col=\"col\" let-attr=\"attr\" let-rowIndex=\"rowIndex\">\n <td>\n @if (col.isReadOnly) {\n <span class=\"text-muted\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n } @else if (getAsDetailCellEditRenderer(col); as editRenderer) {\n <ng-container *ngComponentOutlet=\"editRenderer; inputs: getAsDetailCellEditRendererInputs(editRenderer, row, col)\"></ng-container>\n } @else if (col.dataType === 'AsDetail') {\n <!-- Nested AsDetail can't flatten into a row: escalate to the row modal,\n where the recursive spark-po-form renders the nested structure. -->\n <div class=\"d-flex align-items-center gap-1\">\n <span class=\"text-truncate flex-grow-1\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\" (click)=\"editArrayItem(attr, rowIndex)\">\n <spark-icon name=\"pencil\" />\n </button>\n </div>\n } @else if (col.dataType === 'boolean') {\n <bs-checkbox\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onInlineCellChange(attr, rowIndex, col)\">\n </bs-checkbox>\n } @else if (col.dataType === 'Reference' && col.query) {\n @if (col.referenceDisplayType === EReferenceDisplayType.Modal) {\n <spark-reference-picker\n [value]=\"row[col.name]\"\n [options]=\"(attr | inlineRefOptions:col:asDetailReferenceOptions())\"\n [referenceType]=\"col.referenceType\"\n [title]=\"col.label | resolveTranslation:col.name\"\n [isInvalid]=\"hasInlineError(attr, rowIndex, col)\"\n (valueChange)=\"row[col.name] = $event; onInlineCellChange(attr, rowIndex, col)\">\n </spark-reference-picker>\n } @else {\n <bs-select\n class=\"w-100\"\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onInlineCellChange(attr, rowIndex, col)\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | inlineRefOptions:col:asDetailReferenceOptions()); track option.id) {\n <option [ngValue]=\"option.id\">\n {{ option.breadcrumb || option.id }}\n </option>\n }\n </bs-select>\n }\n } @else if (col.lookupReferenceType) {\n @if ((col | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <spark-lookup-picker\n [value]=\"row[col.name]\"\n [options]=\"getLookupOptions(col)\"\n [title]=\"col.label | resolveTranslation:col.name\"\n [isInvalid]=\"hasInlineError(attr, rowIndex, col)\"\n (valueChange)=\"row[col.name] = $event; onInlineCellChange(attr, rowIndex, col)\">\n </spark-lookup-picker>\n } @else {\n <bs-select\n class=\"w-100\"\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onInlineCellChange(attr, rowIndex, col)\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of getLookupOptions(col); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else if (col.dataType === 'MultiLineString') {\n <textarea\n [(ngModel)]=\"row[col.name]\"\n [required]=\"col.isRequired\"\n rows=\"3\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\"\n (ngModelChange)=\"onFieldChange()\"\n (blur)=\"onInlineCellBlur(attr, rowIndex, col)\"></textarea>\n } @else {\n <input\n [type]=\"col.dataType | inputType\"\n [(ngModel)]=\"row[col.name]\"\n [required]=\"col.isRequired\"\n [step]=\"col.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\"\n (ngModelChange)=\"onFieldChange()\"\n (blur)=\"onInlineCellBlur(attr, rowIndex, col)\">\n }\n @if (inlineErrorMessage(attr, rowIndex, col); as errorMsg) {\n <div class=\"invalid-feedback d-block\">{{ errorMsg }}</div>\n }\n </td>\n </ng-template>\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 50px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index; let rowIndex = $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n <td class=\"text-nowrap\">\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"removeArrayItem(attr, rowIndex)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n\n <!-- Floating preview = a LIVE view of the row's cells (not CDK's cloneNode),\n so <bs-select> shows its value instead of blanking. The in-grid\n placeholder still uses CDK's default clone (exact row height) and is\n hidden via .cdk-drag-placeholder { visibility:hidden } in the styles,\n so the drop gap is blank without collapsing/shifting the other rows. -->\n <ng-template cdkDragPreview>\n <table class=\"table mb-0 bg-body shadow-sm spark-drag-preview\">\n <tbody class=\"align-middle\">\n <tr>\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"width: 32px\"><spark-icon name=\"grip-vertical\" /></td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n </tr>\n </tbody>\n </table>\n </ng-template>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addInlineRow(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 80px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (getAsDetailCellRendererComponent(col); as cellRenderer) {\n <ng-container *ngComponentOutlet=\"cellRenderer; inputs: getAsDetailCellRendererInputs(cellRenderer, row, col)\"></ng-container>\n } @else {\n {{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}\n }\n </td>\n }\n <td class=\"text-nowrap\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary me-1\" (click)=\"editArrayItem(attr, $index)\">\n <spark-icon name=\"pencil\" />\n </button>\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"removeArrayItem(attr, $index)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addArrayItem(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail') {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | asDetailDisplayValue:formData():asDetailTypes()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openAsDetailEditor(attr)\">\n <spark-icon name=\"pencil\" />\n </button>\n </bs-input-group>\n } @else if (getEditRendererComponent(attr); as editComp) {\n <ng-container *ngComponentOutlet=\"editComp; inputs: getEditRendererInputs(editComp, attr)\"></ng-container>\n } @else if (attr.dataType === 'MultiLineString') {\n <textarea\n [id]=\"attr.name\"\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange(attr)\"\n (blur)=\"onFieldBlur(attr)\"\n [name]=\"attr.name\"\n [required]=\"attr.isRequired\"\n rows=\"6\"\n [class.is-invalid]=\"hasError(attr.name)\"></textarea>\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(attr)\"\n (blur)=\"onFieldBlur(attr)\"\n [name]=\"attr.name\"\n [required]=\"attr.isRequired\"\n [step]=\"attr.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasError(attr.name)\">\n }\n @if (attr.name | errorForAttribute:validationErrors(); as errorMsg) {\n <div class=\"invalid-feedback d-block\">\n {{ errorMsg }}\n </div>\n }\n </div>\n </div>\n </ng-template>\n\n @if (showButtons()) {\n <div bsRow class=\"mt-4\">\n <div [md]=\"4\"></div>\n <div [md]=\"8\" class=\"d-flex justify-content-end gap-2\">\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"onCancel()\" [disabled]=\"isSaving()\">{{ 'common.cancel' | t }}</button>\n <button type=\"submit\" [color]=\"colors.primary\" [disabled]=\"isSaving()\" (click)=\"onSave()\">\n @if (isSaving()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'common.save' | t }}\n </button>\n </div>\n </div>\n }\n </bs-grid>\n}\n\n<!-- Modal for editing AsDetail objects -->\n<bs-modal [isOpen]=\"showAsDetailModal()\" (isOpenChange)=\"!$event && closeAsDetailModal()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.edit' | t }} {{ editingAsDetailAttr()?.label | resolveTranslation:editingAsDetailAttr()?.name }}</h5>\n </div>\n\n @if (editingAsDetailAttr(); as attr) {\n <div bsModalBody>\n <spark-po-form\n [entityType]=\"attr | asDetailType:asDetailTypes()\"\n [(formData)]=\"asDetailFormData\"\n [parentId]=\"parentId()\"\n [parentType]=\"parentType()\">\n </spark-po-form>\n </div>\n }\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeAsDetailModal()\">{{ 'common.cancel' | t }}</button>\n <button type=\"button\" [color]=\"colors.primary\" (click)=\"saveAsDetailObject()\">{{ 'common.save' | t }}</button>\n </div>\n </div>\n</bs-modal>\n</bs-form>\n", styles: [":host ::ng-deep .cdk-drag-placeholder{visibility:hidden}\n"], dependencies: [{ kind: "component", type: SparkPoFormComponent, selector: "spark-po-form", inputs: ["entityType", "formData", "validationErrors", "showButtons", "isSaving", "parentId", "parentType", "objectTypeId", "objectId"], outputs: ["formDataChange", "save", "cancel"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "component", type: BsCardComponent, selector: "bs-card", inputs: ["color", "outline"] }, { kind: "component", type: BsCardHeaderComponent, selector: "bs-card-header", inputs: ["color", "navStyle"] }, { kind: "component", type: BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "component", type: BsGridComponent, selector: "bs-grid", inputs: ["stopFullWidthAt"] }, { kind: "directive", type: BsGridRowDirective, selector: "[bsRow]" }, { kind: "directive", type: BsGridColumnDirective, selector: "[xxs],[xs],[sm],[md],[lg],[xl],[xxl]", inputs: ["xxs", "xs", "sm", "md", "lg", "xl", "xxl"] }, { kind: "directive", type: BsColFormLabelDirective, selector: "[bsColFormLabel]" }, { kind: "directive", type: BsButtonTypeDirective, selector: "button[color],input[type=\"button\"][color],input[type=\"submit\"][color],a[color]", inputs: ["color"] }, { kind: "component", type: BsInputGroupComponent, selector: "bs-input-group", inputs: ["size"] }, { 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: BsTreeSelectComponent, selector: "bs-tree-select", inputs: ["provider", "mode", "variant", "cascadeSelect", "placeholder", "showClear", "panelScrollHeight", "searchDebounceMs", "disabled", "value"], outputs: ["valueChange", "opened", "closed", "cleared"] }, { kind: "component", type: BsModalHostComponent, selector: "bs-modal", inputs: ["isOpen", "closeOnEscape", "scrollable"], outputs: ["isOpenChange"] }, { kind: "directive", type: BsModalDirective, selector: "[bsModal]" }, { kind: "directive", type: BsModalHeaderDirective, selector: "[bsModalHeader]" }, { kind: "directive", type: BsModalBodyDirective, selector: "[bsModalBody]" }, { kind: "directive", type: BsModalFooterDirective, selector: "[bsModalFooter]" }, { kind: "component", type: BsTableComponent, selector: "bs-table", inputs: ["isResponsive", "striped", "hover", "border", "ariaRowCount"] }, { kind: "component", type: BsCheckboxComponent, selector: "bs-checkbox", inputs: ["type", "isToggled", "indeterminate", "name", "value", "group"], outputs: ["isToggledChange", "indeterminateChange"] }, { kind: "component", type: BsSpinnerComponent, selector: "bs-spinner", inputs: ["type", "color"] }, { kind: "component", type: BsTabControlComponent, selector: "bs-tab-control", inputs: ["border", "selectFirstTab", "tabsPosition"] }, { kind: "component", type: BsTabPageComponent, selector: "bs-tab-page", inputs: ["disabled"] }, { kind: "directive", type: BsTabPageHeaderDirective, selector: "[bsTabPageHeader]" }, { kind: "component", type: SparkIconComponent, selector: "spark-icon", inputs: ["name"] }, { kind: "component", type: SparkReferencePickerComponent, selector: "spark-reference-picker", inputs: ["value", "options", "referenceType", "isInvalid", "inputId", "title"], outputs: ["valueChange"] }, { kind: "component", type: SparkLookupPickerComponent, selector: "spark-lookup-picker", inputs: ["value", "options", "isInvalid", "inputId", "title"], outputs: ["valueChange"] }, { kind: "pipe", type: TranslateKeyPipe, name: "t" }, { kind: "pipe", type: ResolveTranslationPipe, name: "resolveTranslation" }, { kind: "pipe", type: InputTypePipe, name: "inputType" }, { kind: "pipe", type: LookupDisplayTypePipe, name: "lookupDisplayType" }, { kind: "pipe", type: LookupOptionsPipe, name: "lookupOptions" }, { kind: "pipe", type: AsDetailDisplayValuePipe, name: "asDetailDisplayValue" }, { kind: "pipe", type: AsDetailTypePipe, name: "asDetailType" }, { kind: "pipe", type: AsDetailColumnsPipe, name: "asDetailColumns" }, { kind: "pipe", type: AsDetailCellValuePipe, name: "asDetailCellValue" }, { kind: "pipe", type: CanCreateDetailRowPipe, name: "canCreateDetailRow" }, { kind: "pipe", type: CanDeleteDetailRowPipe, name: "canDeleteDetailRow" }, { kind: "pipe", type: InlineRefOptionsPipe, name: "inlineRefOptions" }, { kind: "pipe", type: ErrorForAttributePipe, name: "errorForAttribute" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
956
954
  }
957
955
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SparkPoFormComponent, decorators: [{
958
956
  type: Component,
959
- args: [{ selector: 'spark-po-form', imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, FormsModule, CdkDropList, CdkDrag, CdkDragHandle, CdkDragPreview, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsGridColDirective, BsColFormLabelDirective, BsButtonTypeDirective, BsInputGroupComponent, BsSelectComponent, BsSelectOption, BsTreeSelectComponent, BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective, BsTableComponent, BsCheckboxComponent, BsSpinnerComponent, BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective, SparkIconComponent, SparkPoFormComponent, SparkReferencePickerComponent, SparkLookupPickerComponent, TranslateKeyPipe, ResolveTranslationPipe, InputTypePipe, LookupDisplayTypePipe, LookupOptionsPipe, AsDetailDisplayValuePipe, AsDetailTypePipe, AsDetailColumnsPipe, AsDetailCellValuePipe, CanCreateDetailRowPipe, CanDeleteDetailRowPipe, InlineRefOptionsPipe, ErrorForAttributePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-form>\n @if (entityType()) {\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"tabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #tabContent let-tab>\n @if (tab.id === '__default__') {\n @if (ungroupedAttributes().length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n @for (attr of ungroupedAttributes(); track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #attrField let-attr>\n <div bsRow class=\"mb-3\">\n <label [md]=\"4\" bsColFormLabel [for]=\"attr.name\">\n {{ attr.label | resolveTranslation:attr.name }}\n @if (attr.isRequired) {\n <span class=\"text-danger\">*</span>\n }\n </label>\n <div [md]=\"8\">\n @if (attr.dataType === 'boolean') {\n <bs-checkbox\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange(attr)\">\n </bs-checkbox>\n } @else if (attr.lookupReferenceType) {\n @if ((attr | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <spark-lookup-picker\n [value]=\"formData()[attr.name]\"\n [options]=\"getLookupOptions(attr)\"\n [inputId]=\"attr.name\"\n [title]=\"attr.label | resolveTranslation:attr.name\"\n [isInvalid]=\"hasError(attr.name)\"\n (valueChange)=\"onLookupValueChange(attr, $event)\">\n </spark-lookup-picker>\n } @else {\n <bs-select\n class=\"w-100\"\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange(attr)\"\n [id]=\"attr.name\"\n [class.is-invalid]=\"hasError(attr.name)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | lookupOptions:lookupReferenceOptions()); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n <bs-tree-select\n mode=\"multiple\"\n variant=\"textbox\"\n [showClear]=\"true\"\n [placeholder]=\"'common.search' | t\"\n [provider]=\"getReferenceProvider(attr)\"\n [ngModel]=\"referenceTreeValues()[attr.name] || []\"\n (ngModelChange)=\"onReferenceTreeChange(attr, $event)\"\n [ngModelOptions]=\"{ standalone: true }\">\n </bs-tree-select>\n } @else if (attr.dataType === 'Reference') {\n <spark-reference-picker\n [value]=\"formData()[attr.name]\"\n [options]=\"referenceOptions()[attr.name] || []\"\n [referenceType]=\"attr.referenceType\"\n [inputId]=\"attr.name\"\n [title]=\"attr.label | resolveTranslation:attr.name\"\n [isInvalid]=\"hasError(attr.name)\"\n (valueChange)=\"onReferenceValueChange(attr, $event)\">\n </spark-reference-picker>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray && attr.editMode === 'inline') {\n <!-- One source of truth for an inline cell, reused by the row and by the drag\n preview. The preview is a live Angular view (not a cloneNode), so its\n <bs-select> binds to row[col.name] and shows the value while dragging. -->\n <ng-template #inlineDetailCell let-row=\"row\" let-col=\"col\" let-attr=\"attr\" let-rowIndex=\"rowIndex\">\n <td>\n @if (col.isReadOnly) {\n <span class=\"text-muted\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n } @else if (getAsDetailCellEditRenderer(col); as editRenderer) {\n <ng-container *ngComponentOutlet=\"editRenderer; inputs: getAsDetailCellEditRendererInputs(editRenderer, row, col)\"></ng-container>\n } @else if (col.dataType === 'AsDetail') {\n <!-- Nested AsDetail can't flatten into a row: escalate to the row modal,\n where the recursive spark-po-form renders the nested structure. -->\n <div class=\"d-flex align-items-center gap-1\">\n <span class=\"text-truncate flex-grow-1\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\" (click)=\"editArrayItem(attr, rowIndex)\">\n <spark-icon name=\"pencil\" />\n </button>\n </div>\n } @else if (col.dataType === 'boolean') {\n <bs-checkbox\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onInlineCellChange(attr, rowIndex, col)\">\n </bs-checkbox>\n } @else if (col.dataType === 'Reference' && col.query) {\n @if (col.referenceDisplayType === EReferenceDisplayType.Modal) {\n <spark-reference-picker\n [value]=\"row[col.name]\"\n [options]=\"(attr | inlineRefOptions:col:asDetailReferenceOptions())\"\n [referenceType]=\"col.referenceType\"\n [title]=\"col.label | resolveTranslation:col.name\"\n [isInvalid]=\"hasInlineError(attr, rowIndex, col)\"\n (valueChange)=\"row[col.name] = $event; onInlineCellChange(attr, rowIndex, col)\">\n </spark-reference-picker>\n } @else {\n <bs-select\n class=\"w-100\"\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onInlineCellChange(attr, rowIndex, col)\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | inlineRefOptions:col:asDetailReferenceOptions()); track option.id) {\n <option [ngValue]=\"option.id\">\n {{ option.breadcrumb || option.name || option.id }}\n </option>\n }\n </bs-select>\n }\n } @else if (col.lookupReferenceType) {\n @if ((col | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <spark-lookup-picker\n [value]=\"row[col.name]\"\n [options]=\"getLookupOptions(col)\"\n [title]=\"col.label | resolveTranslation:col.name\"\n [isInvalid]=\"hasInlineError(attr, rowIndex, col)\"\n (valueChange)=\"row[col.name] = $event; onInlineCellChange(attr, rowIndex, col)\">\n </spark-lookup-picker>\n } @else {\n <bs-select\n class=\"w-100\"\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onInlineCellChange(attr, rowIndex, col)\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of getLookupOptions(col); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else if (col.dataType === 'MultiLineString') {\n <textarea\n [(ngModel)]=\"row[col.name]\"\n [required]=\"col.isRequired\"\n rows=\"3\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\"\n (ngModelChange)=\"onFieldChange()\"\n (blur)=\"onInlineCellBlur(attr, rowIndex, col)\"></textarea>\n } @else {\n <input\n [type]=\"col.dataType | inputType\"\n [(ngModel)]=\"row[col.name]\"\n [required]=\"col.isRequired\"\n [step]=\"col.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\"\n (ngModelChange)=\"onFieldChange()\"\n (blur)=\"onInlineCellBlur(attr, rowIndex, col)\">\n }\n @if (inlineErrorMessage(attr, rowIndex, col); as errorMsg) {\n <div class=\"invalid-feedback d-block\">{{ errorMsg }}</div>\n }\n </td>\n </ng-template>\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 50px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index; let rowIndex = $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n <td class=\"text-nowrap\">\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"removeArrayItem(attr, rowIndex)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n\n <!-- Floating preview = a LIVE view of the row's cells (not CDK's cloneNode),\n so <bs-select> shows its value instead of blanking. The in-grid\n placeholder still uses CDK's default clone (exact row height) and is\n hidden via .cdk-drag-placeholder { visibility:hidden } in the styles,\n so the drop gap is blank without collapsing/shifting the other rows. -->\n <ng-template cdkDragPreview>\n <table class=\"table mb-0 bg-body shadow-sm spark-drag-preview\">\n <tbody class=\"align-middle\">\n <tr>\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"width: 32px\"><spark-icon name=\"grip-vertical\" /></td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n </tr>\n </tbody>\n </table>\n </ng-template>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addInlineRow(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 80px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (getAsDetailCellRendererComponent(col); as cellRenderer) {\n <ng-container *ngComponentOutlet=\"cellRenderer; inputs: getAsDetailCellRendererInputs(cellRenderer, row, col)\"></ng-container>\n } @else {\n {{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}\n }\n </td>\n }\n <td class=\"text-nowrap\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary me-1\" (click)=\"editArrayItem(attr, $index)\">\n <spark-icon name=\"pencil\" />\n </button>\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"removeArrayItem(attr, $index)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addArrayItem(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail') {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | asDetailDisplayValue:formData():asDetailTypes()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openAsDetailEditor(attr)\">\n <spark-icon name=\"pencil\" />\n </button>\n </bs-input-group>\n } @else if (getEditRendererComponent(attr); as editComp) {\n <ng-container *ngComponentOutlet=\"editComp; inputs: getEditRendererInputs(editComp, attr)\"></ng-container>\n } @else if (attr.dataType === 'MultiLineString') {\n <textarea\n [id]=\"attr.name\"\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange(attr)\"\n (blur)=\"onFieldBlur(attr)\"\n [name]=\"attr.name\"\n [required]=\"attr.isRequired\"\n rows=\"6\"\n [class.is-invalid]=\"hasError(attr.name)\"></textarea>\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(attr)\"\n (blur)=\"onFieldBlur(attr)\"\n [name]=\"attr.name\"\n [required]=\"attr.isRequired\"\n [step]=\"attr.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasError(attr.name)\">\n }\n @if (attr.name | errorForAttribute:validationErrors(); as errorMsg) {\n <div class=\"invalid-feedback d-block\">\n {{ errorMsg }}\n </div>\n }\n </div>\n </div>\n </ng-template>\n\n @if (showButtons()) {\n <div bsRow class=\"mt-4\">\n <div [md]=\"4\"></div>\n <div [md]=\"8\" class=\"d-flex justify-content-end gap-2\">\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"onCancel()\" [disabled]=\"isSaving()\">{{ 'common.cancel' | t }}</button>\n <button type=\"submit\" [color]=\"colors.primary\" [disabled]=\"isSaving()\" (click)=\"onSave()\">\n @if (isSaving()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'common.save' | t }}\n </button>\n </div>\n </div>\n }\n </bs-grid>\n}\n\n<!-- Modal for editing AsDetail objects -->\n<bs-modal [isOpen]=\"showAsDetailModal()\" (isOpenChange)=\"!$event && closeAsDetailModal()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.edit' | t }} {{ editingAsDetailAttr()?.label | resolveTranslation:editingAsDetailAttr()?.name }}</h5>\n </div>\n\n @if (editingAsDetailAttr(); as attr) {\n <div bsModalBody>\n <spark-po-form\n [entityType]=\"attr | asDetailType:asDetailTypes()\"\n [(formData)]=\"asDetailFormData\"\n [parentId]=\"parentId()\"\n [parentType]=\"parentType()\">\n </spark-po-form>\n </div>\n }\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeAsDetailModal()\">{{ 'common.cancel' | t }}</button>\n <button type=\"button\" [color]=\"colors.primary\" (click)=\"saveAsDetailObject()\">{{ 'common.save' | t }}</button>\n </div>\n </div>\n</bs-modal>\n</bs-form>\n", styles: [":host ::ng-deep .cdk-drag-placeholder{visibility:hidden}\n"] }]
957
+ args: [{ selector: 'spark-po-form', imports: [CommonModule, NgTemplateOutlet, NgComponentOutlet, FormsModule, CdkDropList, CdkDrag, CdkDragHandle, CdkDragPreview, BsCardComponent, BsCardHeaderComponent, BsFormComponent, BsFormControlDirective, BsGridComponent, BsGridRowDirective, BsGridColumnDirective, BsGridColDirective, BsColFormLabelDirective, BsButtonTypeDirective, BsInputGroupComponent, BsSelectComponent, BsSelectOption, BsTreeSelectComponent, BsModalHostComponent, BsModalDirective, BsModalHeaderDirective, BsModalBodyDirective, BsModalFooterDirective, BsTableComponent, BsCheckboxComponent, BsSpinnerComponent, BsTabControlComponent, BsTabPageComponent, BsTabPageHeaderDirective, SparkIconComponent, SparkPoFormComponent, SparkReferencePickerComponent, SparkLookupPickerComponent, TranslateKeyPipe, ResolveTranslationPipe, InputTypePipe, LookupDisplayTypePipe, LookupOptionsPipe, AsDetailDisplayValuePipe, AsDetailTypePipe, AsDetailColumnsPipe, AsDetailCellValuePipe, CanCreateDetailRowPipe, CanDeleteDetailRowPipe, InlineRefOptionsPipe, ErrorForAttributePipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<bs-form>\n @if (entityType()) {\n <bs-grid>\n <bs-tab-control>\n @for (tab of resolvedTabs(); track tab.id) {\n <bs-tab-page>\n <ng-template bsTabPageHeader>{{ tab.label | resolveTranslation:tab.name }}</ng-template>\n <ng-container *ngTemplateOutlet=\"tabContent; context: { $implicit: tab }\"></ng-container>\n </bs-tab-page>\n }\n </bs-tab-control>\n\n <ng-template #tabContent let-tab>\n @if (tab.id === '__default__') {\n @if (ungroupedAttributes().length > 0) {\n <bs-card class=\"d-block m-3\">\n <div class=\"p-3\">\n @for (attr of ungroupedAttributes(); track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n @for (group of groupsForTab(tab); track group.id) {\n @if (attrsForGroup(group); as groupAttrs) {\n @if (groupAttrs.length > 0) {\n <bs-card class=\"d-block m-3\">\n @if (group.label) {\n <bs-card-header>{{ group.label | resolveTranslation:group.name }}</bs-card-header>\n }\n <div class=\"p-3\">\n @for (attr of groupAttrs; track attr.id) {\n <ng-container *ngTemplateOutlet=\"attrField; context: { $implicit: attr }\"></ng-container>\n }\n </div>\n </bs-card>\n }\n }\n }\n </ng-template>\n\n <ng-template #attrField let-attr>\n <div bsRow class=\"mb-3\">\n <label [md]=\"4\" bsColFormLabel [for]=\"attr.name\">\n {{ attr.label | resolveTranslation:attr.name }}\n @if (attr.isRequired) {\n <span class=\"text-danger\">*</span>\n }\n </label>\n <div [md]=\"8\">\n @if (attr.dataType === 'boolean') {\n <bs-checkbox\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange(attr)\">\n </bs-checkbox>\n } @else if (attr.lookupReferenceType) {\n @if ((attr | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <spark-lookup-picker\n [value]=\"formData()[attr.name]\"\n [options]=\"getLookupOptions(attr)\"\n [inputId]=\"attr.name\"\n [title]=\"attr.label | resolveTranslation:attr.name\"\n [isInvalid]=\"hasError(attr.name)\"\n (valueChange)=\"onLookupValueChange(attr, $event)\">\n </spark-lookup-picker>\n } @else {\n <bs-select\n class=\"w-100\"\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange(attr)\"\n [id]=\"attr.name\"\n [class.is-invalid]=\"hasError(attr.name)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | lookupOptions:lookupReferenceOptions()); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else if (attr.dataType === 'Reference' && attr.isArray) {\n <bs-tree-select\n mode=\"multiple\"\n variant=\"textbox\"\n [showClear]=\"true\"\n [placeholder]=\"'common.search' | t\"\n [provider]=\"getReferenceProvider(attr)\"\n [ngModel]=\"referenceTreeValues()[attr.name] || []\"\n (ngModelChange)=\"onReferenceTreeChange(attr, $event)\"\n [ngModelOptions]=\"{ standalone: true }\">\n </bs-tree-select>\n } @else if (attr.dataType === 'Reference') {\n <spark-reference-picker\n [value]=\"formData()[attr.name]\"\n [options]=\"referenceOptions()[attr.name] || []\"\n [referenceType]=\"attr.referenceType\"\n [inputId]=\"attr.name\"\n [title]=\"attr.label | resolveTranslation:attr.name\"\n [isInvalid]=\"hasError(attr.name)\"\n (valueChange)=\"onReferenceValueChange(attr, $event)\">\n </spark-reference-picker>\n } @else if (attr.dataType === 'AsDetail' && attr.isArray && attr.editMode === 'inline') {\n <!-- One source of truth for an inline cell, reused by the row and by the drag\n preview. The preview is a live Angular view (not a cloneNode), so its\n <bs-select> binds to row[col.name] and shows the value while dragging. -->\n <ng-template #inlineDetailCell let-row=\"row\" let-col=\"col\" let-attr=\"attr\" let-rowIndex=\"rowIndex\">\n <td>\n @if (col.isReadOnly) {\n <span class=\"text-muted\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n } @else if (getAsDetailCellEditRenderer(col); as editRenderer) {\n <ng-container *ngComponentOutlet=\"editRenderer; inputs: getAsDetailCellEditRendererInputs(editRenderer, row, col)\"></ng-container>\n } @else if (col.dataType === 'AsDetail') {\n <!-- Nested AsDetail can't flatten into a row: escalate to the row modal,\n where the recursive spark-po-form renders the nested structure. -->\n <div class=\"d-flex align-items-center gap-1\">\n <span class=\"text-truncate flex-grow-1\">{{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}</span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary\" (click)=\"editArrayItem(attr, rowIndex)\">\n <spark-icon name=\"pencil\" />\n </button>\n </div>\n } @else if (col.dataType === 'boolean') {\n <bs-checkbox\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onInlineCellChange(attr, rowIndex, col)\">\n </bs-checkbox>\n } @else if (col.dataType === 'Reference' && col.query) {\n @if (col.referenceDisplayType === EReferenceDisplayType.Modal) {\n <spark-reference-picker\n [value]=\"row[col.name]\"\n [options]=\"(attr | inlineRefOptions:col:asDetailReferenceOptions())\"\n [referenceType]=\"col.referenceType\"\n [title]=\"col.label | resolveTranslation:col.name\"\n [isInvalid]=\"hasInlineError(attr, rowIndex, col)\"\n (valueChange)=\"row[col.name] = $event; onInlineCellChange(attr, rowIndex, col)\">\n </spark-reference-picker>\n } @else {\n <bs-select\n class=\"w-100\"\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onInlineCellChange(attr, rowIndex, col)\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of (attr | inlineRefOptions:col:asDetailReferenceOptions()); track option.id) {\n <option [ngValue]=\"option.id\">\n {{ option.breadcrumb || option.id }}\n </option>\n }\n </bs-select>\n }\n } @else if (col.lookupReferenceType) {\n @if ((col | lookupDisplayType:lookupReferenceOptions()) === ELookupDisplayType.Modal) {\n <spark-lookup-picker\n [value]=\"row[col.name]\"\n [options]=\"getLookupOptions(col)\"\n [title]=\"col.label | resolveTranslation:col.name\"\n [isInvalid]=\"hasInlineError(attr, rowIndex, col)\"\n (valueChange)=\"row[col.name] = $event; onInlineCellChange(attr, rowIndex, col)\">\n </spark-lookup-picker>\n } @else {\n <bs-select\n class=\"w-100\"\n [(ngModel)]=\"row[col.name]\"\n (ngModelChange)=\"onInlineCellChange(attr, rowIndex, col)\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\">\n <option [ngValue]=\"null\">{{ 'common.selectPlaceholder' | t }}</option>\n @for (option of getLookupOptions(col); track option.key) {\n <option [ngValue]=\"option.key\">\n {{ option.values | resolveTranslation:option.key }}\n </option>\n }\n </bs-select>\n }\n } @else if (col.dataType === 'MultiLineString') {\n <textarea\n [(ngModel)]=\"row[col.name]\"\n [required]=\"col.isRequired\"\n rows=\"3\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\"\n (ngModelChange)=\"onFieldChange()\"\n (blur)=\"onInlineCellBlur(attr, rowIndex, col)\"></textarea>\n } @else {\n <input\n [type]=\"col.dataType | inputType\"\n [(ngModel)]=\"row[col.name]\"\n [required]=\"col.isRequired\"\n [step]=\"col.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasInlineError(attr, rowIndex, col)\"\n (ngModelChange)=\"onFieldChange()\"\n (blur)=\"onInlineCellBlur(attr, rowIndex, col)\">\n }\n @if (inlineErrorMessage(attr, rowIndex, col); as errorMsg) {\n <div class=\"invalid-feedback d-block\">{{ errorMsg }}</div>\n }\n </td>\n </ng-template>\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 50px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index; let rowIndex = $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n <td class=\"text-nowrap\">\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"removeArrayItem(attr, rowIndex)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n\n <!-- Floating preview = a LIVE view of the row's cells (not CDK's cloneNode),\n so <bs-select> shows its value instead of blanking. The in-grid\n placeholder still uses CDK's default clone (exact row height) and is\n hidden via .cdk-drag-placeholder { visibility:hidden } in the styles,\n so the drop gap is blank without collapsing/shifting the other rows. -->\n <ng-template cdkDragPreview>\n <table class=\"table mb-0 bg-body shadow-sm spark-drag-preview\">\n <tbody class=\"align-middle\">\n <tr>\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"width: 32px\"><spark-icon name=\"grip-vertical\" /></td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <ng-container *ngTemplateOutlet=\"inlineDetailCell; context: { row: row, col: col, attr: attr, rowIndex: rowIndex }\"></ng-container>\n }\n </tr>\n </tbody>\n </table>\n </ng-template>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addInlineRow(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail' && attr.isArray) {\n <bs-table [isResponsive]=\"true\" class=\"mb-1\">\n <thead>\n <tr>\n @if (attr.isSortable) {\n <th style=\"width: 32px\"></th>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <th>{{ col.label | resolveTranslation:col.name }}</th>\n }\n <th style=\"width: 80px\"></th>\n </tr>\n </thead>\n <tbody class=\"align-middle\" cdkDropList [cdkDropListDisabled]=\"!attr.isSortable\" (cdkDropListDropped)=\"onAsDetailReorder(attr, $event)\">\n @for (row of formData()[attr.name] || []; track $index) {\n <tr cdkDrag [cdkDragDisabled]=\"!attr.isSortable\">\n @if (attr.isSortable) {\n <td class=\"text-center text-muted\" style=\"cursor: move; touch-action: none\" cdkDragHandle>\n <spark-icon name=\"grip-vertical\" />\n </td>\n }\n @for (col of (attr | asDetailColumns:asDetailTypes()); track col.name) {\n <td>\n @if (getAsDetailCellRendererComponent(col); as cellRenderer) {\n <ng-container *ngComponentOutlet=\"cellRenderer; inputs: getAsDetailCellRendererInputs(cellRenderer, row, col)\"></ng-container>\n } @else {\n {{ row | asDetailCellValue:attr:col:asDetailReferenceOptions() }}\n }\n </td>\n }\n <td class=\"text-nowrap\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-secondary me-1\" (click)=\"editArrayItem(attr, $index)\">\n <spark-icon name=\"pencil\" />\n </button>\n @if (attr | canDeleteDetailRow:asDetailPermissions()) {\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"removeArrayItem(attr, $index)\">\n <spark-icon name=\"trash\" />\n </button>\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"(attr | asDetailColumns:asDetailTypes()).length + (attr.isSortable ? 2 : 1)\" class=\"text-center text-muted\">\n {{ 'common.noItemsFound' | t }}\n </td>\n </tr>\n }\n </tbody>\n </bs-table>\n @if (attr | canCreateDetailRow:asDetailPermissions()) {\n <button type=\"button\" [color]=\"colors.primary\" class=\"w-100 rounded-0\" (click)=\"addArrayItem(attr)\">\n <spark-icon name=\"plus\" /> {{ 'common.add' | t }}\n </button>\n }\n } @else if (attr.dataType === 'AsDetail') {\n <bs-input-group>\n <input\n type=\"text\"\n [id]=\"attr.name\"\n [value]=\"attr | asDetailDisplayValue:formData():asDetailTypes()\"\n readonly\n [class.is-invalid]=\"hasError(attr.name)\">\n <button\n type=\"button\"\n bsInputGroupBtn\n [color]=\"colors.secondary\"\n (click)=\"openAsDetailEditor(attr)\">\n <spark-icon name=\"pencil\" />\n </button>\n </bs-input-group>\n } @else if (getEditRendererComponent(attr); as editComp) {\n <ng-container *ngComponentOutlet=\"editComp; inputs: getEditRendererInputs(editComp, attr)\"></ng-container>\n } @else if (attr.dataType === 'MultiLineString') {\n <textarea\n [id]=\"attr.name\"\n [ngModel]=\"formData()[attr.name]\"\n (ngModelChange)=\"formData()[attr.name] = $event; onFieldChange(attr)\"\n (blur)=\"onFieldBlur(attr)\"\n [name]=\"attr.name\"\n [required]=\"attr.isRequired\"\n rows=\"6\"\n [class.is-invalid]=\"hasError(attr.name)\"></textarea>\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(attr)\"\n (blur)=\"onFieldBlur(attr)\"\n [name]=\"attr.name\"\n [required]=\"attr.isRequired\"\n [step]=\"attr.dataType === 'decimal' ? '0.01' : '1'\"\n [class.is-invalid]=\"hasError(attr.name)\">\n }\n @if (attr.name | errorForAttribute:validationErrors(); as errorMsg) {\n <div class=\"invalid-feedback d-block\">\n {{ errorMsg }}\n </div>\n }\n </div>\n </div>\n </ng-template>\n\n @if (showButtons()) {\n <div bsRow class=\"mt-4\">\n <div [md]=\"4\"></div>\n <div [md]=\"8\" class=\"d-flex justify-content-end gap-2\">\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"onCancel()\" [disabled]=\"isSaving()\">{{ 'common.cancel' | t }}</button>\n <button type=\"submit\" [color]=\"colors.primary\" [disabled]=\"isSaving()\" (click)=\"onSave()\">\n @if (isSaving()) {\n <bs-spinner class=\"me-1\" />\n }\n {{ 'common.save' | t }}\n </button>\n </div>\n </div>\n }\n </bs-grid>\n}\n\n<!-- Modal for editing AsDetail objects -->\n<bs-modal [isOpen]=\"showAsDetailModal()\" (isOpenChange)=\"!$event && closeAsDetailModal()\">\n <div *bsModal>\n <div bsModalHeader>\n <h5 class=\"modal-title\">{{ 'common.edit' | t }} {{ editingAsDetailAttr()?.label | resolveTranslation:editingAsDetailAttr()?.name }}</h5>\n </div>\n\n @if (editingAsDetailAttr(); as attr) {\n <div bsModalBody>\n <spark-po-form\n [entityType]=\"attr | asDetailType:asDetailTypes()\"\n [(formData)]=\"asDetailFormData\"\n [parentId]=\"parentId()\"\n [parentType]=\"parentType()\">\n </spark-po-form>\n </div>\n }\n\n <div bsModalFooter>\n <button type=\"button\" [color]=\"colors.secondary\" (click)=\"closeAsDetailModal()\">{{ 'common.cancel' | t }}</button>\n <button type=\"button\" [color]=\"colors.primary\" (click)=\"saveAsDetailObject()\">{{ 'common.save' | t }}</button>\n </div>\n </div>\n</bs-modal>\n</bs-form>\n", styles: [":host ::ng-deep .cdk-drag-placeholder{visibility:hidden}\n"] }]
960
958
  }], ctorParameters: () => [], propDecorators: { entityType: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityType", required: false }] }], formData: [{ type: i0.Input, args: [{ isSignal: true, alias: "formData", required: false }] }, { type: i0.Output, args: ["formDataChange"] }], validationErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationErrors", required: false }] }], showButtons: [{ type: i0.Input, args: [{ isSignal: true, alias: "showButtons", required: false }] }], isSaving: [{ type: i0.Input, args: [{ isSignal: true, alias: "isSaving", required: false }] }], parentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentId", required: false }] }], parentType: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentType", required: false }] }], save: [{ type: i0.Output, args: ["save"] }], cancel: [{ type: i0.Output, args: ["cancel"] }], objectTypeId: [{ type: i0.Input, args: [{ isSignal: true, alias: "objectTypeId", required: false }] }], objectId: [{ type: i0.Input, args: [{ isSignal: true, alias: "objectId", required: false }] }] } });
961
959
 
962
960
  /**