ngx-dynamic-entity 2.2.0 → 2.3.1

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.
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { InjectionToken, makeEnvironmentProviders, inject, Injectable, signal, Input, ChangeDetectionStrategy, Component, ChangeDetectorRef, DestroyRef, Injector, isDevMode, ViewContainerRef, ViewChild, forwardRef, Optional, Inject, EventEmitter, ElementRef, computed, afterNextRender, HostListener, ViewChildren, Output } from '@angular/core';
3
3
  import * as i1 from '@angular/forms';
4
4
  import { ReactiveFormsModule, Validators, FormBuilder, FormGroup, FormArray, FormsModule } from '@angular/forms';
5
- import { resolveLabel, formatDisplayValue, normalizeLookupValues, lookupValuesToOptions, valuesMatch, resolveOptionLabel, resolveOptionValue, buildReferenceCacheKey, normalizeReferenceOptions, entityKeyFromCacheKey, applyCascadeFilter, resolveEffectiveMask, shouldMaskField, findTab, isValidUrl, isValidPhone, fileRefName, sliderBounds, ratingScale, normalizeHexColor, normalizeTags, evaluateFormRules, filterRulesForTab, evaluateFieldVisibility, getTabPath, fieldRefFor, toRefToken, refOf, parseFieldRef, setTabData, setValueByPath, normalizeArrayStructures, getTabData, getValueByPath, normalizeConfigOptions, migrateRecord, ambiguousFieldIds, collectFieldScopes, applyAutoPatch, isUnsafePath, fieldsUnderTab, valuesEqual, parseCsv, deriveImportColumns, suggestMapping, applyMapping, toCsv, stripIndices, CORE_VERSION, coerceCell, buildTemplateSpec, collectLeafTargets, formatConfigProblems } from '@dynamic-entity/core';
5
+ import { resolveLabel, formatDisplayValue, normalizeLookupValues, lookupValuesToOptions, valuesMatch, resolveOptionLabel, resolveOptionValue, buildReferenceCacheKey, normalizeReferenceOptions, entityKeyFromCacheKey, applyCascadeFilter, resolveEffectiveMask, shouldMaskField, findTab, isValidUrl, isValidPhone, fileRefName, sliderBounds, ratingScale, normalizeHexColor, normalizeTags, evaluateFormRules, filterRulesForTab, evaluateFieldVisibility, fieldsUnderTab, getTabPath, fieldRefFor, flattenFieldValues, parseFieldRef, placeTabFields, normalizeArrayStructures, getTabData, getValueByPath, normalizeConfigOptions, migrateRecord, ambiguousFieldIds, toRefToken, collectFieldScopes, applyAutoPatch, refOf, isUnsafePath, valuesEqual, parseCsv, arrayBoundFor, deriveImportColumns, suggestMapping, cellText, applyMapping, decimalMarkFor, toCsv, stripIndices, CORE_VERSION, DEFAULT_ARRAY_ROWS, upgradeLegacyRefs, coerceCell, buildTemplateSpec, collectLeafTargets, formatConfigProblems } from '@dynamic-entity/core';
6
6
  export { formatDisplayValue, normalizeConfig, normalizeOption, resolveLabel, resolveOptionLabel, resolveOptionValue, valuesMatch } from '@dynamic-entity/core';
7
7
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
8
8
  import { Subject, isObservable, firstValueFrom } from 'rxjs';
@@ -406,10 +406,10 @@ class ValidationMessagesService {
406
406
  return '';
407
407
  return typeof message === 'function' ? message(language, error) : message;
408
408
  }
409
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ValidationMessagesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
410
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ValidationMessagesService, providedIn: 'root' });
409
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ValidationMessagesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
410
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ValidationMessagesService, providedIn: 'root' });
411
411
  }
412
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ValidationMessagesService, decorators: [{
412
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ValidationMessagesService, decorators: [{
413
413
  type: Injectable,
414
414
  args: [{ providedIn: 'root' }]
415
415
  }] });
@@ -522,8 +522,8 @@ class TextFieldComponent {
522
522
  return '';
523
523
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
524
524
  }
525
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: TextFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
526
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: TextFieldComponent, isStandalone: true, selector: "ngx-text-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
525
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: TextFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
526
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: TextFieldComponent, isStandalone: true, selector: "ngx-text-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
527
527
  <div
528
528
  class="ngx-field ngx-field--text"
529
529
  [attr.data-testid]="'field-' + field.id"
@@ -583,7 +583,7 @@ class TextFieldComponent {
583
583
  </div>
584
584
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
585
585
  }
586
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: TextFieldComponent, decorators: [{
586
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: TextFieldComponent, decorators: [{
587
587
  type: Component,
588
588
  args: [{
589
589
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -698,8 +698,8 @@ class TextareaFieldComponent {
698
698
  return '';
699
699
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
700
700
  }
701
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: TextareaFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
702
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: TextareaFieldComponent, isStandalone: true, selector: "ngx-textarea-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
701
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: TextareaFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
702
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: TextareaFieldComponent, isStandalone: true, selector: "ngx-textarea-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
703
703
  <div
704
704
  class="ngx-field ngx-field--textarea"
705
705
  [attr.data-testid]="'field-' + field.id"
@@ -748,7 +748,7 @@ class TextareaFieldComponent {
748
748
  </div>
749
749
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
750
750
  }
751
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: TextareaFieldComponent, decorators: [{
751
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: TextareaFieldComponent, decorators: [{
752
752
  type: Component,
753
753
  args: [{
754
754
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -920,8 +920,16 @@ const DEFAULT_UI_TEXT = {
920
920
  importGuessed: 'Guessed',
921
921
  importMappedCount: '{mapped} of {total} field(s) matched.',
922
922
  importRequiredUnmapped: '{count} required field(s) still need a column.',
923
+ importConstant: 'Fixed value: {value}',
924
+ importAddSlot: 'Add a {label} row',
925
+ importStaleHeading: 'Mapped to fields this form no longer has',
926
+ importStaleExplain: 'The import will refuse this mapping until these are removed.',
927
+ importRemoveEntry: 'Remove',
923
928
  // Import wizard — review and result
924
929
  importPreviewHeading: 'First {count} row(s), as they will be saved',
930
+ importDelimiterComma: 'Columns are separated by commas.',
931
+ importDelimiterSemicolon: 'Columns are separated by semicolons, and numbers use a decimal comma.',
932
+ importDelimiterTab: 'Columns are separated by tabs.',
925
933
  importRowRejected: 'This row will not be imported: {reason}',
926
934
  importRunImport: 'Import {count} row(s)',
927
935
  importImporting: 'Importing…',
@@ -1022,10 +1030,10 @@ class UiTextService {
1022
1030
  text(key, language = 'en', params) {
1023
1031
  return resolveUiText(this.overrides, DEFAULT_UI_TEXT, key, language, params);
1024
1032
  }
1025
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: UiTextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1026
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: UiTextService, providedIn: 'root' });
1033
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: UiTextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1034
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: UiTextService, providedIn: 'root' });
1027
1035
  }
1028
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: UiTextService, decorators: [{
1036
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: UiTextService, decorators: [{
1029
1037
  type: Injectable,
1030
1038
  args: [{ providedIn: 'root' }]
1031
1039
  }] });
@@ -1152,8 +1160,8 @@ class MarkdownFieldComponent {
1152
1160
  return '';
1153
1161
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
1154
1162
  }
1155
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: MarkdownFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1156
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: MarkdownFieldComponent, isStandalone: true, selector: "ngx-markdown-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, usesOnChanges: true, ngImport: i0, template: `
1163
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MarkdownFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1164
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: MarkdownFieldComponent, isStandalone: true, selector: "ngx-markdown-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, usesOnChanges: true, ngImport: i0, template: `
1157
1165
  <div
1158
1166
  class="ngx-field ngx-field--markdown"
1159
1167
  [attr.data-testid]="'field-' + field.id"
@@ -1253,7 +1261,7 @@ class MarkdownFieldComponent {
1253
1261
  </div>
1254
1262
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1255
1263
  }
1256
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: MarkdownFieldComponent, decorators: [{
1264
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MarkdownFieldComponent, decorators: [{
1257
1265
  type: Component,
1258
1266
  args: [{
1259
1267
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -1408,8 +1416,8 @@ class NumberFieldComponent {
1408
1416
  return '';
1409
1417
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
1410
1418
  }
1411
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: NumberFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1412
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: NumberFieldComponent, isStandalone: true, selector: "ngx-number-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1419
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: NumberFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1420
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: NumberFieldComponent, isStandalone: true, selector: "ngx-number-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1413
1421
  <div
1414
1422
  class="ngx-field ngx-field--number"
1415
1423
  [attr.data-testid]="'field-' + field.id"
@@ -1469,7 +1477,7 @@ class NumberFieldComponent {
1469
1477
  </div>
1470
1478
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1471
1479
  }
1472
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: NumberFieldComponent, decorators: [{
1480
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: NumberFieldComponent, decorators: [{
1473
1481
  type: Component,
1474
1482
  args: [{
1475
1483
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -1597,8 +1605,8 @@ class CurrencyFieldComponent {
1597
1605
  return '';
1598
1606
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
1599
1607
  }
1600
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: CurrencyFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1601
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: CurrencyFieldComponent, isStandalone: true, selector: "ngx-currency-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1608
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: CurrencyFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1609
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: CurrencyFieldComponent, isStandalone: true, selector: "ngx-currency-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1602
1610
  <div
1603
1611
  class="ngx-field ngx-field--currency"
1604
1612
  [attr.data-testid]="'field-' + field.id"
@@ -1653,7 +1661,7 @@ class CurrencyFieldComponent {
1653
1661
  </div>
1654
1662
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1655
1663
  }
1656
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: CurrencyFieldComponent, decorators: [{
1664
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: CurrencyFieldComponent, decorators: [{
1657
1665
  type: Component,
1658
1666
  args: [{
1659
1667
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -1764,8 +1772,8 @@ class EmailFieldComponent {
1764
1772
  return '';
1765
1773
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
1766
1774
  }
1767
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EmailFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1768
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: EmailFieldComponent, isStandalone: true, selector: "ngx-email-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1775
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EmailFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1776
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: EmailFieldComponent, isStandalone: true, selector: "ngx-email-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1769
1777
  <div
1770
1778
  class="ngx-field ngx-field--email"
1771
1779
  [attr.data-testid]="'field-' + field.id"
@@ -1822,7 +1830,7 @@ class EmailFieldComponent {
1822
1830
  </div>
1823
1831
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1824
1832
  }
1825
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EmailFieldComponent, decorators: [{
1833
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EmailFieldComponent, decorators: [{
1826
1834
  type: Component,
1827
1835
  args: [{
1828
1836
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -1941,8 +1949,8 @@ class PasswordFieldComponent {
1941
1949
  return '';
1942
1950
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
1943
1951
  }
1944
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: PasswordFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1945
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: PasswordFieldComponent, isStandalone: true, selector: "ngx-password-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1952
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: PasswordFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1953
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: PasswordFieldComponent, isStandalone: true, selector: "ngx-password-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1946
1954
  <div
1947
1955
  class="ngx-field ngx-field--password"
1948
1956
  [attr.data-testid]="'field-' + field.id"
@@ -2002,7 +2010,7 @@ class PasswordFieldComponent {
2002
2010
  </div>
2003
2011
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2004
2012
  }
2005
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: PasswordFieldComponent, decorators: [{
2013
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: PasswordFieldComponent, decorators: [{
2006
2014
  type: Component,
2007
2015
  args: [{
2008
2016
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -2116,8 +2124,8 @@ class CheckboxFieldComponent {
2116
2124
  return '';
2117
2125
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
2118
2126
  }
2119
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: CheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2120
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: CheckboxFieldComponent, isStandalone: true, selector: "ngx-checkbox-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
2127
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: CheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2128
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: CheckboxFieldComponent, isStandalone: true, selector: "ngx-checkbox-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
2121
2129
  <div
2122
2130
  class="ngx-field ngx-field--checkbox"
2123
2131
  [attr.data-testid]="'field-' + field.id"
@@ -2173,7 +2181,7 @@ class CheckboxFieldComponent {
2173
2181
  </div>
2174
2182
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2175
2183
  }
2176
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: CheckboxFieldComponent, decorators: [{
2184
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: CheckboxFieldComponent, decorators: [{
2177
2185
  type: Component,
2178
2186
  args: [{
2179
2187
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -2288,8 +2296,8 @@ class BooleanFieldComponent {
2288
2296
  return '';
2289
2297
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
2290
2298
  }
2291
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: BooleanFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2292
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: BooleanFieldComponent, isStandalone: true, selector: "ngx-boolean-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
2299
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: BooleanFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2300
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: BooleanFieldComponent, isStandalone: true, selector: "ngx-boolean-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
2293
2301
  <div
2294
2302
  class="ngx-field ngx-field--boolean"
2295
2303
  [attr.data-testid]="'field-' + field.id"
@@ -2353,7 +2361,7 @@ class BooleanFieldComponent {
2353
2361
  </div>
2354
2362
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2355
2363
  }
2356
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: BooleanFieldComponent, decorators: [{
2364
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: BooleanFieldComponent, decorators: [{
2357
2365
  type: Component,
2358
2366
  args: [{
2359
2367
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -2490,8 +2498,8 @@ class DateFieldComponent {
2490
2498
  return '';
2491
2499
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
2492
2500
  }
2493
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DateFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2494
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: DateFieldComponent, isStandalone: true, selector: "ngx-date-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
2501
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: DateFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2502
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: DateFieldComponent, isStandalone: true, selector: "ngx-date-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
2495
2503
  <div
2496
2504
  class="ngx-field ngx-field--date"
2497
2505
  [attr.data-testid]="'field-' + field.id"
@@ -2541,7 +2549,7 @@ class DateFieldComponent {
2541
2549
  </div>
2542
2550
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2543
2551
  }
2544
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DateFieldComponent, decorators: [{
2552
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: DateFieldComponent, decorators: [{
2545
2553
  type: Component,
2546
2554
  args: [{
2547
2555
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -2710,8 +2718,8 @@ class DateTimeFieldComponent {
2710
2718
  return '';
2711
2719
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
2712
2720
  }
2713
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DateTimeFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2714
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: DateTimeFieldComponent, isStandalone: true, selector: "ngx-date-time-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
2721
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: DateTimeFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2722
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: DateTimeFieldComponent, isStandalone: true, selector: "ngx-date-time-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
2715
2723
  <div
2716
2724
  class="ngx-field ngx-field--datetime"
2717
2725
  [attr.data-testid]="'field-' + field.id"
@@ -2762,7 +2770,7 @@ class DateTimeFieldComponent {
2762
2770
  </div>
2763
2771
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2764
2772
  }
2765
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DateTimeFieldComponent, decorators: [{
2773
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: DateTimeFieldComponent, decorators: [{
2766
2774
  type: Component,
2767
2775
  args: [{
2768
2776
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -2881,8 +2889,8 @@ class TimeFieldComponent {
2881
2889
  return '';
2882
2890
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
2883
2891
  }
2884
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: TimeFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2885
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: TimeFieldComponent, isStandalone: true, selector: "ngx-time-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
2892
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: TimeFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2893
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: TimeFieldComponent, isStandalone: true, selector: "ngx-time-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
2886
2894
  <div
2887
2895
  class="ngx-field ngx-field--time"
2888
2896
  [attr.data-testid]="'field-' + field.id"
@@ -2932,7 +2940,7 @@ class TimeFieldComponent {
2932
2940
  </div>
2933
2941
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2934
2942
  }
2935
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: TimeFieldComponent, decorators: [{
2943
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: TimeFieldComponent, decorators: [{
2936
2944
  type: Component,
2937
2945
  args: [{
2938
2946
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -3084,8 +3092,8 @@ class MonthYearFieldComponent {
3084
3092
  return '';
3085
3093
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
3086
3094
  }
3087
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: MonthYearFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3088
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: MonthYearFieldComponent, isStandalone: true, selector: "ngx-month-year-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
3095
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MonthYearFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3096
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: MonthYearFieldComponent, isStandalone: true, selector: "ngx-month-year-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
3089
3097
  <div
3090
3098
  class="ngx-field ngx-field--month-year"
3091
3099
  [attr.data-testid]="'field-' + field.id"
@@ -3164,7 +3172,7 @@ class MonthYearFieldComponent {
3164
3172
  </div>
3165
3173
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3166
3174
  }
3167
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: MonthYearFieldComponent, decorators: [{
3175
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MonthYearFieldComponent, decorators: [{
3168
3176
  type: Component,
3169
3177
  args: [{
3170
3178
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -3465,10 +3473,10 @@ class LookupRegistryService {
3465
3473
  value !== null &&
3466
3474
  typeof value.subscribe === 'function');
3467
3475
  }
3468
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LookupRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3469
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LookupRegistryService, providedIn: 'root' });
3476
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: LookupRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3477
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: LookupRegistryService, providedIn: 'root' });
3470
3478
  }
3471
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LookupRegistryService, decorators: [{
3479
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: LookupRegistryService, decorators: [{
3472
3480
  type: Injectable,
3473
3481
  args: [{ providedIn: 'root' }]
3474
3482
  }] });
@@ -3594,8 +3602,8 @@ class DropdownFieldComponent {
3594
3602
  // dropdown than "This field is required", and both stay independently overridable.
3595
3603
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
3596
3604
  }
3597
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DropdownFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3598
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: DropdownFieldComponent, isStandalone: true, selector: "ngx-dropdown-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
3605
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: DropdownFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3606
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: DropdownFieldComponent, isStandalone: true, selector: "ngx-dropdown-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
3599
3607
  <div
3600
3608
  class="ngx-field ngx-field--dropdown"
3601
3609
  [attr.data-testid]="'field-' + field.id"
@@ -3665,7 +3673,7 @@ class DropdownFieldComponent {
3665
3673
  </div>
3666
3674
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3667
3675
  }
3668
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DropdownFieldComponent, decorators: [{
3676
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: DropdownFieldComponent, decorators: [{
3669
3677
  type: Component,
3670
3678
  args: [{
3671
3679
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -3880,8 +3888,8 @@ class RadioFieldComponent {
3880
3888
  return '';
3881
3889
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
3882
3890
  }
3883
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RadioFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3884
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: RadioFieldComponent, isStandalone: true, selector: "ngx-radio-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
3891
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: RadioFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3892
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: RadioFieldComponent, isStandalone: true, selector: "ngx-radio-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
3885
3893
  <div
3886
3894
  class="ngx-field ngx-field--radio"
3887
3895
  [attr.data-testid]="'field-' + field.id"
@@ -3945,7 +3953,7 @@ class RadioFieldComponent {
3945
3953
  </div>
3946
3954
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3947
3955
  }
3948
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RadioFieldComponent, decorators: [{
3956
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: RadioFieldComponent, decorators: [{
3949
3957
  type: Component,
3950
3958
  args: [{
3951
3959
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -4111,8 +4119,8 @@ class MultiSelectFieldComponent {
4111
4119
  return '';
4112
4120
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
4113
4121
  }
4114
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: MultiSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4115
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: MultiSelectFieldComponent, isStandalone: true, selector: "ngx-multi-select-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
4122
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MultiSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4123
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: MultiSelectFieldComponent, isStandalone: true, selector: "ngx-multi-select-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
4116
4124
  <div
4117
4125
  class="ngx-field ngx-field--multiSelect"
4118
4126
  [attr.data-testid]="'field-' + field.id"
@@ -4172,7 +4180,7 @@ class MultiSelectFieldComponent {
4172
4180
  </div>
4173
4181
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectMultipleControlValueAccessor, selector: "select[multiple][formControlName],select[multiple][formControl],select[multiple][ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4174
4182
  }
4175
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: MultiSelectFieldComponent, decorators: [{
4183
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: MultiSelectFieldComponent, decorators: [{
4176
4184
  type: Component,
4177
4185
  args: [{
4178
4186
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -4271,10 +4279,10 @@ class EntityRefRegistryService {
4271
4279
  has(entityKey) {
4272
4280
  return this.registry.has(entityKey);
4273
4281
  }
4274
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4275
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefRegistryService, providedIn: 'root' });
4282
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityRefRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4283
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityRefRegistryService, providedIn: 'root' });
4276
4284
  }
4277
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefRegistryService, decorators: [{
4285
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityRefRegistryService, decorators: [{
4278
4286
  type: Injectable,
4279
4287
  args: [{ providedIn: 'root' }]
4280
4288
  }] });
@@ -4434,10 +4442,10 @@ class EntityReferenceService {
4434
4442
  matches(a, b, lang = 'en') {
4435
4443
  return valuesMatch(a, b, lang);
4436
4444
  }
4437
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityReferenceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4438
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityReferenceService, providedIn: 'root' });
4445
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityReferenceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4446
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityReferenceService, providedIn: 'root' });
4439
4447
  }
4440
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityReferenceService, decorators: [{
4448
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityReferenceService, decorators: [{
4441
4449
  type: Injectable,
4442
4450
  args: [{ providedIn: 'root' }]
4443
4451
  }] });
@@ -4558,10 +4566,10 @@ class CascadeDataService {
4558
4566
  walkTabs(config?.tabs);
4559
4567
  return found;
4560
4568
  }
4561
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: CascadeDataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4562
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: CascadeDataService, providedIn: 'root' });
4569
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: CascadeDataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4570
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: CascadeDataService, providedIn: 'root' });
4563
4571
  }
4564
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: CascadeDataService, decorators: [{
4572
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: CascadeDataService, decorators: [{
4565
4573
  type: Injectable,
4566
4574
  args: [{ providedIn: 'root' }]
4567
4575
  }] });
@@ -4582,10 +4590,10 @@ class EntityRefSelectionService {
4582
4590
  emit(fieldId, option) {
4583
4591
  this.subject.next({ fieldId, option });
4584
4592
  }
4585
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefSelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4586
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefSelectionService, providedIn: 'root' });
4593
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityRefSelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4594
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityRefSelectionService, providedIn: 'root' });
4587
4595
  }
4588
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefSelectionService, decorators: [{
4596
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityRefSelectionService, decorators: [{
4589
4597
  type: Injectable,
4590
4598
  args: [{ providedIn: 'root' }]
4591
4599
  }] });
@@ -4713,8 +4721,8 @@ class EntityRefFieldComponent {
4713
4721
  return '';
4714
4722
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
4715
4723
  }
4716
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4717
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: EntityRefFieldComponent, isStandalone: true, selector: "ngx-entity-ref-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
4724
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityRefFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4725
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: EntityRefFieldComponent, isStandalone: true, selector: "ngx-entity-ref-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
4718
4726
  <div
4719
4727
  class="ngx-field ngx-field--entity-ref"
4720
4728
  [attr.data-testid]="'field-' + field.id"
@@ -4779,7 +4787,7 @@ class EntityRefFieldComponent {
4779
4787
  </div>
4780
4788
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4781
4789
  }
4782
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefFieldComponent, decorators: [{
4790
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityRefFieldComponent, decorators: [{
4783
4791
  type: Component,
4784
4792
  args: [{
4785
4793
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -4907,10 +4915,10 @@ class FieldRegistryService {
4907
4915
  registeredTypes() {
4908
4916
  return [...new Set([...this.dynamicRegistry.keys(), ...this.consumerRegistry.keys()])].sort();
4909
4917
  }
4910
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FieldRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4911
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FieldRegistryService, providedIn: 'root' });
4918
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: FieldRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4919
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: FieldRegistryService, providedIn: 'root' });
4912
4920
  }
4913
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FieldRegistryService, decorators: [{
4921
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: FieldRegistryService, decorators: [{
4914
4922
  type: Injectable,
4915
4923
  args: [{ providedIn: 'root' }]
4916
4924
  }], ctorParameters: () => [] });
@@ -4949,10 +4957,10 @@ class RbacService {
4949
4957
  canDelete: this.hasPermission(userRoles, config.permissions?.delete),
4950
4958
  };
4951
4959
  }
4952
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RbacService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4953
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RbacService, providedIn: 'root' });
4960
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: RbacService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4961
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: RbacService, providedIn: 'root' });
4954
4962
  }
4955
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RbacService, decorators: [{
4963
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: RbacService, decorators: [{
4956
4964
  type: Injectable,
4957
4965
  args: [{ providedIn: 'root' }]
4958
4966
  }] });
@@ -5074,10 +5082,10 @@ class DynamicFieldComponent {
5074
5082
  ref.setInput('readonly', this.readonly || !!this.field.readonly);
5075
5083
  ref.setInput('masked', masked);
5076
5084
  }
5077
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DynamicFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5078
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.24", type: DynamicFieldComponent, isStandalone: true, selector: "ngx-dynamic-field", inputs: { field: "field", control: "control", config: "config", currentTabId: "currentTabId", language: "language", readonly: "readonly", userRoles: "userRoles" }, viewQueries: [{ propertyName: "fieldHost", first: true, predicate: ["fieldHost"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: `<ng-container #fieldHost></ng-container>`, isInline: true, styles: [":host{display:block;width:100%;min-width:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5085
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: DynamicFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5086
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.25", type: DynamicFieldComponent, isStandalone: true, selector: "ngx-dynamic-field", inputs: { field: "field", control: "control", config: "config", currentTabId: "currentTabId", language: "language", readonly: "readonly", userRoles: "userRoles" }, viewQueries: [{ propertyName: "fieldHost", first: true, predicate: ["fieldHost"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: `<ng-container #fieldHost></ng-container>`, isInline: true, styles: [":host{display:block;width:100%;min-width:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5079
5087
  }
5080
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DynamicFieldComponent, decorators: [{
5088
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: DynamicFieldComponent, decorators: [{
5081
5089
  type: Component,
5082
5090
  args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-dynamic-field', standalone: true, template: `<ng-container #fieldHost></ng-container>`, styles: [":host{display:block;width:100%;min-width:0}\n"] }]
5083
5091
  }], propDecorators: { field: [{
@@ -5121,8 +5129,8 @@ class GroupFieldComponent {
5121
5129
  const group = this.control;
5122
5130
  return group?.get(childId);
5123
5131
  }
5124
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: GroupFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5125
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: GroupFieldComponent, isStandalone: true, selector: "ngx-group-field", inputs: { field: "field", control: "control", config: "config", language: "language", readonly: "readonly", masked: "masked", userRoles: "userRoles" }, ngImport: i0, template: `
5132
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: GroupFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5133
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: GroupFieldComponent, isStandalone: true, selector: "ngx-group-field", inputs: { field: "field", control: "control", config: "config", language: "language", readonly: "readonly", masked: "masked", userRoles: "userRoles" }, ngImport: i0, template: `
5126
5134
  <fieldset
5127
5135
  class="ngx-field ngx-field--group"
5128
5136
  [attr.data-testid]="'field-' + field.id"
@@ -5163,7 +5171,7 @@ class GroupFieldComponent {
5163
5171
  </fieldset>
5164
5172
  `, isInline: true, styles: [".ngx-field--group{border:1px solid #e2e8f0;border-radius:8px;padding:16px;margin-bottom:16px;background:#fafafa}.ngx-field__legend{font-weight:600;font-size:14px;padding:0 8px;color:#334155}.ngx-field__group-children{display:flex;flex-direction:column;gap:12px}\n"], dependencies: [{ kind: "ngmodule", type: i0.forwardRef(() => ReactiveFormsModule) }, { kind: "component", type: i0.forwardRef(() => DynamicFieldComponent), selector: "ngx-dynamic-field", inputs: ["field", "control", "config", "currentTabId", "language", "readonly", "userRoles"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5165
5173
  }
5166
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: GroupFieldComponent, decorators: [{
5174
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: GroupFieldComponent, decorators: [{
5167
5175
  type: Component,
5168
5176
  args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-group-field', standalone: true, imports: [ReactiveFormsModule, forwardRef(() => DynamicFieldComponent)], template: `
5169
5177
  <fieldset
@@ -5371,10 +5379,10 @@ class ValidatorRegistryService {
5371
5379
  this.warned.add(id);
5372
5380
  console.warn(`[ngx-dynamic-entity] ${message}`);
5373
5381
  }
5374
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ValidatorRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5375
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ValidatorRegistryService, providedIn: 'root' });
5382
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ValidatorRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5383
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ValidatorRegistryService, providedIn: 'root' });
5376
5384
  }
5377
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ValidatorRegistryService, decorators: [{
5385
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ValidatorRegistryService, decorators: [{
5378
5386
  type: Injectable,
5379
5387
  args: [{ providedIn: 'root' }]
5380
5388
  }] });
@@ -5427,8 +5435,8 @@ class ArrayFieldComponent {
5427
5435
  this.formArray.removeAt(index);
5428
5436
  this.cdr.markForCheck();
5429
5437
  }
5430
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5431
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: ArrayFieldComponent, isStandalone: true, selector: "ngx-array-field", inputs: { field: "field", control: "control", config: "config", language: "language", readonly: "readonly", masked: "masked", userRoles: "userRoles" }, ngImport: i0, template: `
5438
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5439
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: ArrayFieldComponent, isStandalone: true, selector: "ngx-array-field", inputs: { field: "field", control: "control", config: "config", language: "language", readonly: "readonly", masked: "masked", userRoles: "userRoles" }, ngImport: i0, template: `
5432
5440
  <div
5433
5441
  class="ngx-field ngx-field--array"
5434
5442
  [attr.data-testid]="'field-' + field.id"
@@ -5507,7 +5515,7 @@ class ArrayFieldComponent {
5507
5515
  </div>
5508
5516
  `, isInline: true, styles: [".ngx-field--array{border:1px solid #e2e8f0;border-radius:8px;padding:16px;margin-bottom:16px;background:#f8fafc}.ngx-field__array-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.ngx-field__array-add-btn{background:#2563eb;color:#fff;border:none;padding:6px 12px;border-radius:4px;font-weight:500;cursor:pointer}.ngx-field__array-add-btn:hover{background:#1d4ed8}.ngx-field__array-list{display:flex;flex-direction:column;gap:12px}.ngx-field__array-item{background:#fff;border:1px solid #cbd5e1;border-radius:6px;padding:12px}.ngx-field__array-item-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;border-bottom:1px solid #f1f5f9;padding-bottom:6px}.ngx-field__array-item-title{font-weight:600;font-size:13px;color:#475569}.ngx-field__array-remove-btn{background:#ef4444;color:#fff;border:none;padding:4px 8px;border-radius:4px;font-size:12px;cursor:pointer}.ngx-field__array-empty{color:#94a3b8;font-style:italic;font-size:13px}\n"], dependencies: [{ kind: "ngmodule", type: i0.forwardRef(() => ReactiveFormsModule) }, { kind: "component", type: i0.forwardRef(() => DynamicFieldComponent), selector: "ngx-dynamic-field", inputs: ["field", "control", "config", "currentTabId", "language", "readonly", "userRoles"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5509
5517
  }
5510
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ArrayFieldComponent, decorators: [{
5518
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ArrayFieldComponent, decorators: [{
5511
5519
  type: Component,
5512
5520
  args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-array-field', standalone: true, imports: [ReactiveFormsModule, forwardRef(() => DynamicFieldComponent)], template: `
5513
5521
  <div
@@ -5681,10 +5689,10 @@ class FileUploadService {
5681
5689
  value !== null &&
5682
5690
  typeof value.subscribe === 'function');
5683
5691
  }
5684
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FileUploadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5685
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FileUploadService, providedIn: 'root' });
5692
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: FileUploadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
5693
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: FileUploadService, providedIn: 'root' });
5686
5694
  }
5687
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FileUploadService, decorators: [{
5695
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: FileUploadService, decorators: [{
5688
5696
  type: Injectable,
5689
5697
  args: [{ providedIn: 'root' }]
5690
5698
  }] });
@@ -5769,8 +5777,8 @@ class ImageFieldComponent {
5769
5777
  return '';
5770
5778
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
5771
5779
  }
5772
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImageFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5773
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: ImageFieldComponent, isStandalone: true, selector: "ngx-image-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
5780
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ImageFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5781
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: ImageFieldComponent, isStandalone: true, selector: "ngx-image-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
5774
5782
  <div
5775
5783
  class="ngx-field ngx-field--image"
5776
5784
  [attr.data-testid]="'field-' + field.id"
@@ -5849,7 +5857,7 @@ class ImageFieldComponent {
5849
5857
  </div>
5850
5858
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
5851
5859
  }
5852
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImageFieldComponent, decorators: [{
5860
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ImageFieldComponent, decorators: [{
5853
5861
  type: Component,
5854
5862
  args: [{
5855
5863
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -6019,8 +6027,8 @@ class FileFieldComponent {
6019
6027
  return '';
6020
6028
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
6021
6029
  }
6022
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FileFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6023
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: FileFieldComponent, isStandalone: true, selector: "ngx-file-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
6030
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: FileFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6031
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: FileFieldComponent, isStandalone: true, selector: "ngx-file-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
6024
6032
  <div
6025
6033
  class="ngx-field ngx-field--file"
6026
6034
  [attr.data-testid]="'field-' + field.id"
@@ -6102,7 +6110,7 @@ class FileFieldComponent {
6102
6110
  </div>
6103
6111
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
6104
6112
  }
6105
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FileFieldComponent, decorators: [{
6113
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: FileFieldComponent, decorators: [{
6106
6114
  type: Component,
6107
6115
  args: [{
6108
6116
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -6252,8 +6260,8 @@ class UrlFieldComponent {
6252
6260
  return '';
6253
6261
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
6254
6262
  }
6255
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: UrlFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6256
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: UrlFieldComponent, isStandalone: true, selector: "ngx-url-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
6263
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: UrlFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6264
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: UrlFieldComponent, isStandalone: true, selector: "ngx-url-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
6257
6265
  <div
6258
6266
  class="ngx-field ngx-field--url"
6259
6267
  [attr.data-testid]="'field-' + field.id"
@@ -6323,7 +6331,7 @@ class UrlFieldComponent {
6323
6331
  </div>
6324
6332
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6325
6333
  }
6326
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: UrlFieldComponent, decorators: [{
6334
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: UrlFieldComponent, decorators: [{
6327
6335
  type: Component,
6328
6336
  args: [{
6329
6337
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -6462,8 +6470,8 @@ class PhoneFieldComponent {
6462
6470
  return '';
6463
6471
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
6464
6472
  }
6465
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: PhoneFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6466
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: PhoneFieldComponent, isStandalone: true, selector: "ngx-phone-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
6473
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: PhoneFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6474
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: PhoneFieldComponent, isStandalone: true, selector: "ngx-phone-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
6467
6475
  <div
6468
6476
  class="ngx-field ngx-field--phone"
6469
6477
  [attr.data-testid]="'field-' + field.id"
@@ -6531,7 +6539,7 @@ class PhoneFieldComponent {
6531
6539
  </div>
6532
6540
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6533
6541
  }
6534
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: PhoneFieldComponent, decorators: [{
6542
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: PhoneFieldComponent, decorators: [{
6535
6543
  type: Component,
6536
6544
  args: [{
6537
6545
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -6670,8 +6678,8 @@ class SliderFieldComponent {
6670
6678
  return '';
6671
6679
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
6672
6680
  }
6673
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SliderFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6674
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: SliderFieldComponent, isStandalone: true, selector: "ngx-slider-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
6681
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: SliderFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6682
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: SliderFieldComponent, isStandalone: true, selector: "ngx-slider-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
6675
6683
  <div
6676
6684
  class="ngx-field ngx-field--slider"
6677
6685
  [attr.data-testid]="'field-' + field.id"
@@ -6744,7 +6752,7 @@ class SliderFieldComponent {
6744
6752
  </div>
6745
6753
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6746
6754
  }
6747
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: SliderFieldComponent, decorators: [{
6755
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: SliderFieldComponent, decorators: [{
6748
6756
  type: Component,
6749
6757
  args: [{
6750
6758
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -6921,8 +6929,8 @@ class RatingFieldComponent {
6921
6929
  return '';
6922
6930
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
6923
6931
  }
6924
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RatingFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6925
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: RatingFieldComponent, isStandalone: true, selector: "ngx-rating-field", inputs: { field: "field", language: "language", readonly: "readonly", masked: "masked", control: "control" }, ngImport: i0, template: `
6932
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: RatingFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6933
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: RatingFieldComponent, isStandalone: true, selector: "ngx-rating-field", inputs: { field: "field", language: "language", readonly: "readonly", masked: "masked", control: "control" }, ngImport: i0, template: `
6926
6934
  <div
6927
6935
  class="ngx-field ngx-field--rating"
6928
6936
  [attr.data-testid]="'field-' + field.id"
@@ -7019,7 +7027,7 @@ class RatingFieldComponent {
7019
7027
  </div>
7020
7028
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7021
7029
  }
7022
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RatingFieldComponent, decorators: [{
7030
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: RatingFieldComponent, decorators: [{
7023
7031
  type: Component,
7024
7032
  args: [{
7025
7033
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -7188,8 +7196,8 @@ class ColorFieldComponent {
7188
7196
  return '';
7189
7197
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
7190
7198
  }
7191
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ColorFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7192
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: ColorFieldComponent, isStandalone: true, selector: "ngx-color-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
7199
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ColorFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7200
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: ColorFieldComponent, isStandalone: true, selector: "ngx-color-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
7193
7201
  <div
7194
7202
  class="ngx-field ngx-field--color"
7195
7203
  [attr.data-testid]="'field-' + field.id"
@@ -7269,7 +7277,7 @@ class ColorFieldComponent {
7269
7277
  </div>
7270
7278
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7271
7279
  }
7272
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ColorFieldComponent, decorators: [{
7280
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ColorFieldComponent, decorators: [{
7273
7281
  type: Component,
7274
7282
  args: [{
7275
7283
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -7484,8 +7492,8 @@ class TagsFieldComponent {
7484
7492
  return '';
7485
7493
  return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
7486
7494
  }
7487
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: TagsFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7488
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: TagsFieldComponent, isStandalone: true, selector: "ngx-tags-field", inputs: { field: "field", language: "language", readonly: "readonly", masked: "masked", control: "control" }, ngImport: i0, template: `
7495
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: TagsFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7496
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: TagsFieldComponent, isStandalone: true, selector: "ngx-tags-field", inputs: { field: "field", language: "language", readonly: "readonly", masked: "masked", control: "control" }, ngImport: i0, template: `
7489
7497
  <div
7490
7498
  class="ngx-field ngx-field--tags"
7491
7499
  [attr.data-testid]="'field-' + field.id"
@@ -7575,7 +7583,7 @@ class TagsFieldComponent {
7575
7583
  </div>
7576
7584
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
7577
7585
  }
7578
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: TagsFieldComponent, decorators: [{
7586
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: TagsFieldComponent, decorators: [{
7579
7587
  type: Component,
7580
7588
  args: [{
7581
7589
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -7758,10 +7766,10 @@ class HookRegistryService {
7758
7766
  return data;
7759
7767
  return hook(data, context);
7760
7768
  }
7761
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HookRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7762
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HookRegistryService, providedIn: 'root' });
7769
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: HookRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7770
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: HookRegistryService, providedIn: 'root' });
7763
7771
  }
7764
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HookRegistryService, decorators: [{
7772
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: HookRegistryService, decorators: [{
7765
7773
  type: Injectable,
7766
7774
  args: [{ providedIn: 'root' }]
7767
7775
  }] });
@@ -7825,6 +7833,12 @@ class RulesEvaluationService {
7825
7833
  * The predicate is `isFieldVisible`, the same one the render filter uses, so what is on
7826
7834
  * screen and what counts toward validity cannot drift apart.
7827
7835
  *
7836
+ * A tab that is not rendered — hidden by a rule, or `visibility: false` with no rule showing
7837
+ * it — hides everything it owns, sub-tabs and container children included. Only fields were
7838
+ * checked here, so a rule hiding a tab with a required field on it left the form invalid
7839
+ * with the field out of sight and out of the error summary: Save did nothing, silently.
7840
+ * Import already relaxed a hidden tab's fields, so the form refused rows import accepted.
7841
+ *
7828
7842
  * Entries must arrive parent-first, which is what `collectFieldScopes` yields: a container's
7829
7843
  * children are skipped once the container itself is hidden, because Angular's `disable()`
7830
7844
  * cascades to descendants and a child's `enable()` walks back up recalculating ancestors.
@@ -7832,6 +7846,7 @@ class RulesEvaluationService {
7832
7846
  */
7833
7847
  syncHiddenFieldState(entries, ctx) {
7834
7848
  const hiddenContainers = [];
7849
+ const onHiddenTab = this.fieldsOnHiddenTabs(ctx.result, ctx.config);
7835
7850
  for (const entry of entries) {
7836
7851
  const field = entry.field;
7837
7852
  if (!field?.id)
@@ -7839,7 +7854,7 @@ class RulesEvaluationService {
7839
7854
  const address = ctx.addressOf(field);
7840
7855
  if (hiddenContainers.some(prefix => address.startsWith(`${prefix}.`)))
7841
7856
  continue;
7842
- const hidden = !this.isFieldVisible(ctx.result, field, ctx.namesOf(field), ctx.values);
7857
+ const hidden = onHiddenTab.has(field) || !this.isFieldVisible(ctx.result, field, ctx.namesOf(field), ctx.values);
7843
7858
  if (hidden && (field.type === 'group' || field.type === 'array'))
7844
7859
  hiddenContainers.push(address);
7845
7860
  const ctrl = ctx.controlFor(entry);
@@ -7854,6 +7869,30 @@ class RulesEvaluationService {
7854
7869
  }
7855
7870
  }
7856
7871
  }
7872
+ /**
7873
+ * Every field owned by a tab that does not render, under `isTabVisible`'s precedence.
7874
+ *
7875
+ * Stops descending at the first hidden tab: `fieldsUnderTab` already covers its sub-tabs,
7876
+ * and a sub-tab of a hidden tab is not rendered whatever its own visibility says.
7877
+ */
7878
+ fieldsOnHiddenTabs(result, config) {
7879
+ const out = new Set();
7880
+ const visit = (tabs) => {
7881
+ for (const tab of Array.isArray(tabs) ? tabs : []) {
7882
+ if (!tab || typeof tab !== 'object' || !tab.id)
7883
+ continue;
7884
+ if (this.isTabVisible(result, tab)) {
7885
+ visit(tab.children);
7886
+ }
7887
+ else {
7888
+ for (const entry of fieldsUnderTab(config, tab.id))
7889
+ out.add(entry.field);
7890
+ }
7891
+ }
7892
+ };
7893
+ visit(config?.tabs);
7894
+ return out;
7895
+ }
7857
7896
  /**
7858
7897
  * The hidden and shown names as sets, memoised against the result they came from.
7859
7898
  *
@@ -7870,10 +7909,10 @@ class RulesEvaluationService {
7870
7909
  return sets;
7871
7910
  }
7872
7911
  nameSetCache = new WeakMap();
7873
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RulesEvaluationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7874
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RulesEvaluationService, providedIn: 'root' });
7912
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: RulesEvaluationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7913
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: RulesEvaluationService, providedIn: 'root' });
7875
7914
  }
7876
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RulesEvaluationService, decorators: [{
7915
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: RulesEvaluationService, decorators: [{
7877
7916
  type: Injectable,
7878
7917
  args: [{ providedIn: 'root' }]
7879
7918
  }] });
@@ -7928,10 +7967,10 @@ class ConfigSourceService {
7928
7967
  this.cache.clear();
7929
7968
  }
7930
7969
  }
7931
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ConfigSourceService, deps: [{ token: CONFIG_SOURCE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
7932
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ConfigSourceService, providedIn: 'root' });
7970
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ConfigSourceService, deps: [{ token: CONFIG_SOURCE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
7971
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ConfigSourceService, providedIn: 'root' });
7933
7972
  }
7934
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ConfigSourceService, decorators: [{
7973
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ConfigSourceService, decorators: [{
7935
7974
  type: Injectable,
7936
7975
  args: [{ providedIn: 'root' }]
7937
7976
  }], ctorParameters: () => [{ type: undefined, decorators: [{
@@ -8063,18 +8102,11 @@ class FormStructureService {
8063
8102
  * unknown>` and needs no knowledge of refs at all — the extra keys simply resolve.
8064
8103
  */
8065
8104
  flattenValues(form, entries) {
8066
- const out = {};
8067
- for (const entry of entries) {
8068
- const field = entry.field;
8069
- if (!field?.id)
8070
- continue;
8105
+ // The naming rule is core's, shared with the importer; only where values come from is ours.
8106
+ return flattenFieldValues(entries, entry => {
8071
8107
  const ctrl = this.controlAt(form, entry);
8072
- if (!ctrl)
8073
- continue;
8074
- out[field.id] = ctrl.value;
8075
- out[toRefToken(refOf(field, entry.scope))] = ctrl.value;
8076
- }
8077
- return out;
8108
+ return ctrl ? { value: ctrl.value } : undefined;
8109
+ });
8078
8110
  }
8079
8111
  /**
8080
8112
  * Find a control by reference: a bracketed path, or a bare field id.
@@ -8129,14 +8161,8 @@ class FormStructureService {
8129
8161
  if (ctrl)
8130
8162
  fieldValBag[field.id] = ctrl.value;
8131
8163
  }
8132
- setTabData(record, tab.id, fieldValBag, config);
8133
- for (const field of tab.fields || []) {
8134
- if (field.refererField) {
8135
- const ctrl = this.getControl(form, config, field.id, tab.id);
8136
- if (ctrl)
8137
- setValueByPath(record, field.refererField, ctrl.value);
8138
- }
8139
- }
8164
+ // At its position and at its `refererField` — the placement import uses too.
8165
+ placeTabFields(record, tab, fieldValBag, config);
8140
8166
  if (tab.children)
8141
8167
  walkTabs(tab.children);
8142
8168
  }
@@ -8219,10 +8245,10 @@ class FormStructureService {
8219
8245
  fieldsById.has(key) &&
8220
8246
  !patchedFieldIds.has(key));
8221
8247
  }
8222
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FormStructureService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
8223
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FormStructureService });
8248
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: FormStructureService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
8249
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: FormStructureService });
8224
8250
  }
8225
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FormStructureService, decorators: [{
8251
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: FormStructureService, decorators: [{
8226
8252
  type: Injectable
8227
8253
  }] });
8228
8254
 
@@ -9235,6 +9261,7 @@ class DynamicFormComponent {
9235
9261
  namesOf: field => this.namesOf(field),
9236
9262
  addressOf: field => this.addressOf(field),
9237
9263
  controlFor: entry => this.controlForEntry(entry),
9264
+ config: this.config,
9238
9265
  });
9239
9266
  }
9240
9267
  /** Every field in the config, including `group`/`array` children. */
@@ -9384,10 +9411,10 @@ class DynamicFormComponent {
9384
9411
  this.submitAttempted.set(false);
9385
9412
  this.formReset.emit();
9386
9413
  }
9387
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DynamicFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9388
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: DynamicFormComponent, isStandalone: true, selector: "ngx-dynamic-form", inputs: { config: "config", rules: "rules", initialData: "initialData", originalData: "originalData", userRoles: "userRoles", language: "language", readonly: "readonly", readOnlyFields: "readOnlyFields", showInfoBanners: "showInfoBanners", loading: "loading", error: "error", changeDebounceMs: "changeDebounceMs", preview: "preview", layout: "layout" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", activeTabChange: "activeTabChange", saveRejected: "saveRejected" }, host: { listeners: { "window:keydown": "handleKeyboardEvent($event)" } }, providers: [EntityRefSelectionService, FormStructureService], viewQueries: [{ propertyName: "formPanel", first: true, predicate: ["formPanel"], descendants: true }, { propertyName: "fieldHosts", predicate: DynamicFieldComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "@if (canView) {\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\" class=\"ngx-form\" [class.ngx-form--readonly]=\"readonly\" novalidate>\n <!-- Error message banner -->\n @if (error) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" data-testid=\"form-error\">{{ error }}</div>\n }\n\n <!--\n Why the last Save did nothing.\n Only after a refused submit \u2014 see `submitAttempted`. Each entry jumps to its field,\n switching tabs on the way, because the field is usually not the one on screen.\n -->\n @if (submitAttempted() && invalidFields().length > 0) {\n <div class=\"ngx-form__error-summary\" role=\"alert\" data-testid=\"error-summary\" tabindex=\"-1\">\n <!--\n `error-summary-heading`, not `-title`: the links below are `error-summary-<fieldId>`,\n and a field whose id happens to be `title` would then share a test id with this line.\n The `extensions` entity has exactly such a field.\n -->\n <p class=\"ngx-form__error-summary-title\" data-testid=\"error-summary-heading\">\n {{ ui.text('errorSummaryTitle', language, { count: invalidFields().length }) }}\n </p>\n <ul class=\"ngx-form__error-summary-list\">\n @for (entry of invalidFields(); track entry.field.id) {\n <li>\n <!--\n The field, and what is wrong with it \u2014 the same sentence the field renders\n under itself, so the summary is not a second opinion. Naming only the field\n left the user to visit each one to find out what it wanted.\n -->\n <button\n type=\"button\"\n class=\"ngx-form__error-summary-link\"\n [attr.data-testid]=\"'error-summary-' + entry.field.id\"\n (click)=\"jumpToField(entry.field.id)\"\n >\n <span class=\"ngx-form__error-summary-field\">{{ resolveFieldLabel(entry.field) }}</span>\n @if (entry.message) {\n <span class=\"ngx-form__error-summary-reason\">{{ entry.message }}</span>\n }\n </button>\n </li>\n }\n </ul>\n </div>\n }\n\n <!-- Rules Info Banners -->\n @for (infoKey of showInfoBanners ? Object.keys(ruleResult().infoBanners) : []; track infoKey) {\n <div class=\"ngx-form__info-banner\" role=\"status\" [attr.data-testid]=\"'info-banner-' + infoKey\">\n \u2139\uFE0F {{ ruleResult().infoBanners[infoKey] }}\n </div>\n }\n\n <!-- Rules Validation Errors & Warnings -->\n @for (errKey of Object.keys(ruleResult().validationErrors); track errKey) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" [attr.data-testid]=\"'rule-error-' + errKey\">\n \u26A0\uFE0F {{ ruleResult().validationErrors[errKey] }}\n </div>\n }\n @for (warnKey of Object.keys(ruleResult().validationWarnings); track warnKey) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" [attr.data-testid]=\"'rule-warning-' + warnKey\">\n \u26A1 {{ ruleResult().validationWarnings[warnKey] }}\n </div>\n }\n\n <!-- Deferred VALUE_CHANGED notice for criticalField edits (vs session baseline) -->\n @if (changedCriticalFields().length > 0) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" data-testid=\"critical-change-banner\">\n {{ ui.text('criticalFieldChanged', language, { fields: changedCriticalFieldLabels() }) }}\n </div>\n }\n\n <!-- Primary Tab bar \u2014 only if visible tabs defined -->\n @if (visibleTabs.length > 0) {\n <div class=\"ngx-form__tabs\" role=\"tablist\" data-testid=\"tab-strip\">\n @for (tab of visibleTabs; track tab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab\"\n [attr.data-testid]=\"'tab-' + tab.id\"\n [class.ngx-form__tab--active]=\"activeTab() === tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n >\n {{ resolveTabLabel(tab) }}\n <!--\n How many fields on this tab are stopping the save.\n The count is in the accessible name rather than the badge, because \"3\" read out\n after a tab name says nothing about what it counts.\n -->\n @if (tabErrorCount(tab); as errors) {\n <span\n class=\"ngx-form__tab-badge\"\n [attr.data-testid]=\"'tab-errors-' + tab.id\"\n [attr.aria-label]=\"ui.text('tabErrorCount', language, { count: errors })\"\n >{{ errors }}</span\n >\n }\n </button>\n }\n </div>\n }\n\n <!-- Sub-tab bar \u2014 only if active tab has sub-tabs -->\n @if (visibleSubTabs.length > 0) {\n <div\n class=\"ngx-form__tabs ngx-form__tabs--sub\"\n role=\"tablist\"\n data-testid=\"subtab-strip\"\n style=\"margin-top: -12px; margin-bottom: 12px; font-size: 13px\"\n >\n @for (subTab of visibleSubTabs; track subTab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab ngx-form__tab--sub\"\n [attr.data-testid]=\"'subtab-' + subTab.id\"\n [class.ngx-form__tab--active]=\"activeSubTabConfig?.id === subTab.id\"\n (click)=\"setActiveSubTab(subTab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"activeSubTabConfig?.id === subTab.id\"\n >\n {{ resolveTabLabel(subTab) }}\n @if (subTabErrorCount(subTab); as errors) {\n <span\n class=\"ngx-form__tab-badge\"\n [attr.data-testid]=\"'subtab-errors-' + subTab.id\"\n [attr.aria-label]=\"ui.text('tabErrorCount', language, { count: errors })\"\n >{{ errors }}</span\n >\n }\n </button>\n }\n </div>\n }\n\n <!--\n One tabpanel per tab, wrapping both branches. Focus moves here on a tab switch, and a\n module tab is still a tab \u2014 putting the panel semantics on only the fields branch left\n a module tab with nowhere for focus to land.\n -->\n <div role=\"tabpanel\" tabindex=\"-1\" #formPanel class=\"ngx-form__tabpanel\" [attr.aria-label]=\"activeTabLabel\">\n @if (activeTabModuleComponent) {\n <div class=\"ngx-form__module-panel\" data-testid=\"module-panel\">\n <ng-container\n *ngComponentOutlet=\"activeTabModuleComponent; inputs: (activeSubTabConfig || activeTabConfig)?.moduleInputs\"\n ></ng-container>\n </div>\n } @else {\n <!-- Fields for active tab (Responsive 12-column grid) -->\n <div class=\"ngx-form__panel\" data-testid=\"form-panel\">\n @for (field of fieldsForActiveTab; track field.id) {\n <div\n class=\"ngx-form__field-slot\"\n [style.gridColumn]=\"getFieldSpan(field)\"\n [attr.id]=\"'field-container-' + field.id\"\n tabindex=\"-1\"\n >\n <ngx-dynamic-field\n [field]=\"field\"\n [control]=\"getControl(field.id, (activeSubTabConfig || activeTabConfig)?.id)!\"\n [config]=\"config\"\n [currentTabId]=\"(activeSubTabConfig || activeTabConfig)?.id\"\n [language]=\"language\"\n [readonly]=\"isFieldReadonly(field)\"\n [userRoles]=\"userRoles\"\n />\n @if (field.hasDrift) {\n <!--\n Drift was previously visible only in the builder, so a referenced field whose\n source had changed looked completely normal to the person filling the form in.\n role=\"status\" rather than \"alert\": it is information, not an error.\n -->\n <span\n class=\"ngx-field__drift\"\n role=\"status\"\n [attr.data-testid]=\"'field-' + field.id + '-drift'\"\n [title]=\"driftHint(field)\"\n >\u26A0 {{ driftHint(field) }}</span\n >\n }\n @if (field.criticalField && !readonly) {\n <button\n type=\"button\"\n class=\"ngx-form__lock\"\n [attr.data-testid]=\"'lock-' + field.id\"\n [attr.aria-pressed]=\"!isFieldLocked(field)\"\n [attr.aria-label]=\"\n ui.text(isFieldLocked(field) ? 'unlockField' : 'lockField', language, {\n field: resolveFieldLabel(field),\n })\n \"\n [title]=\"ui.text(isFieldLocked(field) ? 'criticalFieldUnlockHint' : 'criticalFieldLockHint', language)\"\n (click)=\"toggleFieldLock(field)\"\n >\n {{ isFieldLocked(field) ? '\uD83D\uDD12' : '\uD83D\uDD13' }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Submit button \u2014 gated by RBAC and isSaving lock -->\n @if (!readonly && permissions.canEdit) {\n <div class=\"ngx-form__actions\" data-testid=\"form-actions\">\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"form-reset\" (click)=\"reset()\" [disabled]=\"isSaving()\">\n {{ ui.text('reset', language) }}\n </button>\n <!--\n Two different \"unavailable\" states, and they are not the same thing.\n\n `disabled` is for what a retry cannot fix \u2014 a save in flight, an async check still\n outstanding. `data-blocked` is for validation: the button stays fully operable,\n because pressing it is how the user gets the error summary, but it should not look\n ready either. The stylesheet mutes it without taking the click away.\n\n Deliberately not `aria-disabled`. That announces a control as present-but-inoperable,\n and this one is operable and useful \u2014 pressing it raises a `role=\"alert\"` naming\n every field at fault, which is more than a disabled button could ever say. Tooling\n agrees: Playwright refuses to click through `aria-disabled`, and it would be right\n to, because the attribute would be claiming something untrue.\n -->\n <button\n type=\"submit\"\n class=\"ngx-form__submit\"\n data-testid=\"form-submit\"\n [disabled]=\"submitDisabled\"\n [attr.data-blocked]=\"submitBlocked ? '' : null\"\n [title]=\"ui.text('saveShortcutHint', language)\"\n >\n @if (isSaving() || loading) {\n {{ ui.text('saving', language) }}\n } @else {\n {{ ui.text('save', language) }}\n }\n </button>\n </div>\n }\n </form>\n} @else {\n <!-- permissions.view denied: render nothing of the record.\n Presentational only \u2014 the server must not send data this user may not see. -->\n <div class=\"ngx-form__denied\" role=\"status\" data-testid=\"form-access-denied\">\n {{ ui.text('accessDenied', language) }}\n </div>\n}\n", styles: [".ngx-form__panel{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:var(--ngx-gap, 18px);align-items:start}.ngx-form__field-slot{display:flex;align-items:flex-start;gap:6px;min-width:0}.ngx-form__field-slot ngx-dynamic-field{flex:1;min-width:0}.ngx-form__lock{flex:0 0 auto;margin-top:22px;background:none;border:1px solid transparent;border-radius:var(--ngx-radius-sm, 6px);cursor:pointer;font-size:14px;line-height:1;padding:4px 6px}.ngx-form__lock:hover{border-color:var(--ngx-color-border, #d1d5db);background:var(--ngx-color-surface-alt, #f9fafb)}@media(max-width:640px){.ngx-form__panel{grid-template-columns:minmax(0,1fr)}.ngx-form__field-slot{grid-column:1 / -1!important}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicFieldComponent, selector: "ngx-dynamic-field", inputs: ["field", "control", "config", "currentTabId", "language", "readonly", "userRoles"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9414
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: DynamicFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9415
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: DynamicFormComponent, isStandalone: true, selector: "ngx-dynamic-form", inputs: { config: "config", rules: "rules", initialData: "initialData", originalData: "originalData", userRoles: "userRoles", language: "language", readonly: "readonly", readOnlyFields: "readOnlyFields", showInfoBanners: "showInfoBanners", loading: "loading", error: "error", changeDebounceMs: "changeDebounceMs", preview: "preview", layout: "layout" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", activeTabChange: "activeTabChange", saveRejected: "saveRejected" }, host: { listeners: { "window:keydown": "handleKeyboardEvent($event)" } }, providers: [EntityRefSelectionService, FormStructureService], viewQueries: [{ propertyName: "formPanel", first: true, predicate: ["formPanel"], descendants: true }, { propertyName: "fieldHosts", predicate: DynamicFieldComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "@if (canView) {\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\" class=\"ngx-form\" [class.ngx-form--readonly]=\"readonly\" novalidate>\n <!-- Error message banner -->\n @if (error) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" data-testid=\"form-error\">{{ error }}</div>\n }\n\n <!--\n Why the last Save did nothing.\n Only after a refused submit \u2014 see `submitAttempted`. Each entry jumps to its field,\n switching tabs on the way, because the field is usually not the one on screen.\n -->\n @if (submitAttempted() && invalidFields().length > 0) {\n <div class=\"ngx-form__error-summary\" role=\"alert\" data-testid=\"error-summary\" tabindex=\"-1\">\n <!--\n `error-summary-heading`, not `-title`: the links below are `error-summary-<fieldId>`,\n and a field whose id happens to be `title` would then share a test id with this line.\n The `extensions` entity has exactly such a field.\n -->\n <p class=\"ngx-form__error-summary-title\" data-testid=\"error-summary-heading\">\n {{ ui.text('errorSummaryTitle', language, { count: invalidFields().length }) }}\n </p>\n <ul class=\"ngx-form__error-summary-list\">\n @for (entry of invalidFields(); track entry.field.id) {\n <li>\n <!--\n The field, and what is wrong with it \u2014 the same sentence the field renders\n under itself, so the summary is not a second opinion. Naming only the field\n left the user to visit each one to find out what it wanted.\n -->\n <button\n type=\"button\"\n class=\"ngx-form__error-summary-link\"\n [attr.data-testid]=\"'error-summary-' + entry.field.id\"\n (click)=\"jumpToField(entry.field.id)\"\n >\n <span class=\"ngx-form__error-summary-field\">{{ resolveFieldLabel(entry.field) }}</span>\n @if (entry.message) {\n <span class=\"ngx-form__error-summary-reason\">{{ entry.message }}</span>\n }\n </button>\n </li>\n }\n </ul>\n </div>\n }\n\n <!-- Rules Info Banners -->\n @for (infoKey of showInfoBanners ? Object.keys(ruleResult().infoBanners) : []; track infoKey) {\n <div class=\"ngx-form__info-banner\" role=\"status\" [attr.data-testid]=\"'info-banner-' + infoKey\">\n \u2139\uFE0F {{ ruleResult().infoBanners[infoKey] }}\n </div>\n }\n\n <!-- Rules Validation Errors & Warnings -->\n @for (errKey of Object.keys(ruleResult().validationErrors); track errKey) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" [attr.data-testid]=\"'rule-error-' + errKey\">\n \u26A0\uFE0F {{ ruleResult().validationErrors[errKey] }}\n </div>\n }\n @for (warnKey of Object.keys(ruleResult().validationWarnings); track warnKey) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" [attr.data-testid]=\"'rule-warning-' + warnKey\">\n \u26A1 {{ ruleResult().validationWarnings[warnKey] }}\n </div>\n }\n\n <!-- Deferred VALUE_CHANGED notice for criticalField edits (vs session baseline) -->\n @if (changedCriticalFields().length > 0) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" data-testid=\"critical-change-banner\">\n {{ ui.text('criticalFieldChanged', language, { fields: changedCriticalFieldLabels() }) }}\n </div>\n }\n\n <!-- Primary Tab bar \u2014 only if visible tabs defined -->\n @if (visibleTabs.length > 0) {\n <div class=\"ngx-form__tabs\" role=\"tablist\" data-testid=\"tab-strip\">\n @for (tab of visibleTabs; track tab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab\"\n [attr.data-testid]=\"'tab-' + tab.id\"\n [class.ngx-form__tab--active]=\"activeTab() === tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n >\n {{ resolveTabLabel(tab) }}\n <!--\n How many fields on this tab are stopping the save.\n The count is in the accessible name rather than the badge, because \"3\" read out\n after a tab name says nothing about what it counts.\n -->\n @if (tabErrorCount(tab); as errors) {\n <span\n class=\"ngx-form__tab-badge\"\n [attr.data-testid]=\"'tab-errors-' + tab.id\"\n [attr.aria-label]=\"ui.text('tabErrorCount', language, { count: errors })\"\n >{{ errors }}</span\n >\n }\n </button>\n }\n </div>\n }\n\n <!-- Sub-tab bar \u2014 only if active tab has sub-tabs -->\n @if (visibleSubTabs.length > 0) {\n <div\n class=\"ngx-form__tabs ngx-form__tabs--sub\"\n role=\"tablist\"\n data-testid=\"subtab-strip\"\n style=\"margin-top: -12px; margin-bottom: 12px; font-size: 13px\"\n >\n @for (subTab of visibleSubTabs; track subTab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab ngx-form__tab--sub\"\n [attr.data-testid]=\"'subtab-' + subTab.id\"\n [class.ngx-form__tab--active]=\"activeSubTabConfig?.id === subTab.id\"\n (click)=\"setActiveSubTab(subTab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"activeSubTabConfig?.id === subTab.id\"\n >\n {{ resolveTabLabel(subTab) }}\n @if (subTabErrorCount(subTab); as errors) {\n <span\n class=\"ngx-form__tab-badge\"\n [attr.data-testid]=\"'subtab-errors-' + subTab.id\"\n [attr.aria-label]=\"ui.text('tabErrorCount', language, { count: errors })\"\n >{{ errors }}</span\n >\n }\n </button>\n }\n </div>\n }\n\n <!--\n One tabpanel per tab, wrapping both branches. Focus moves here on a tab switch, and a\n module tab is still a tab \u2014 putting the panel semantics on only the fields branch left\n a module tab with nowhere for focus to land.\n -->\n <div role=\"tabpanel\" tabindex=\"-1\" #formPanel class=\"ngx-form__tabpanel\" [attr.aria-label]=\"activeTabLabel\">\n @if (activeTabModuleComponent) {\n <div class=\"ngx-form__module-panel\" data-testid=\"module-panel\">\n <ng-container\n *ngComponentOutlet=\"activeTabModuleComponent; inputs: (activeSubTabConfig || activeTabConfig)?.moduleInputs\"\n ></ng-container>\n </div>\n } @else {\n <!-- Fields for active tab (Responsive 12-column grid) -->\n <div class=\"ngx-form__panel\" data-testid=\"form-panel\">\n @for (field of fieldsForActiveTab; track field.id) {\n <div\n class=\"ngx-form__field-slot\"\n [style.gridColumn]=\"getFieldSpan(field)\"\n [attr.id]=\"'field-container-' + field.id\"\n tabindex=\"-1\"\n >\n <ngx-dynamic-field\n [field]=\"field\"\n [control]=\"getControl(field.id, (activeSubTabConfig || activeTabConfig)?.id)!\"\n [config]=\"config\"\n [currentTabId]=\"(activeSubTabConfig || activeTabConfig)?.id\"\n [language]=\"language\"\n [readonly]=\"isFieldReadonly(field)\"\n [userRoles]=\"userRoles\"\n />\n @if (field.hasDrift) {\n <!--\n Drift was previously visible only in the builder, so a referenced field whose\n source had changed looked completely normal to the person filling the form in.\n role=\"status\" rather than \"alert\": it is information, not an error.\n -->\n <span\n class=\"ngx-field__drift\"\n role=\"status\"\n [attr.data-testid]=\"'field-' + field.id + '-drift'\"\n [title]=\"driftHint(field)\"\n >\u26A0 {{ driftHint(field) }}</span\n >\n }\n @if (field.criticalField && !readonly) {\n <button\n type=\"button\"\n class=\"ngx-form__lock\"\n [attr.data-testid]=\"'lock-' + field.id\"\n [attr.aria-pressed]=\"!isFieldLocked(field)\"\n [attr.aria-label]=\"\n ui.text(isFieldLocked(field) ? 'unlockField' : 'lockField', language, {\n field: resolveFieldLabel(field),\n })\n \"\n [title]=\"ui.text(isFieldLocked(field) ? 'criticalFieldUnlockHint' : 'criticalFieldLockHint', language)\"\n (click)=\"toggleFieldLock(field)\"\n >\n {{ isFieldLocked(field) ? '\uD83D\uDD12' : '\uD83D\uDD13' }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Submit button \u2014 gated by RBAC and isSaving lock -->\n @if (!readonly && permissions.canEdit) {\n <div class=\"ngx-form__actions\" data-testid=\"form-actions\">\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"form-reset\" (click)=\"reset()\" [disabled]=\"isSaving()\">\n {{ ui.text('reset', language) }}\n </button>\n <!--\n Two different \"unavailable\" states, and they are not the same thing.\n\n `disabled` is for what a retry cannot fix \u2014 a save in flight, an async check still\n outstanding. `data-blocked` is for validation: the button stays fully operable,\n because pressing it is how the user gets the error summary, but it should not look\n ready either. The stylesheet mutes it without taking the click away.\n\n Deliberately not `aria-disabled`. That announces a control as present-but-inoperable,\n and this one is operable and useful \u2014 pressing it raises a `role=\"alert\"` naming\n every field at fault, which is more than a disabled button could ever say. Tooling\n agrees: Playwright refuses to click through `aria-disabled`, and it would be right\n to, because the attribute would be claiming something untrue.\n -->\n <button\n type=\"submit\"\n class=\"ngx-form__submit\"\n data-testid=\"form-submit\"\n [disabled]=\"submitDisabled\"\n [attr.data-blocked]=\"submitBlocked ? '' : null\"\n [title]=\"ui.text('saveShortcutHint', language)\"\n >\n @if (isSaving() || loading) {\n {{ ui.text('saving', language) }}\n } @else {\n {{ ui.text('save', language) }}\n }\n </button>\n </div>\n }\n </form>\n} @else {\n <!-- permissions.view denied: render nothing of the record.\n Presentational only \u2014 the server must not send data this user may not see. -->\n <div class=\"ngx-form__denied\" role=\"status\" data-testid=\"form-access-denied\">\n {{ ui.text('accessDenied', language) }}\n </div>\n}\n", styles: [".ngx-form__panel{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:var(--ngx-gap, 18px);align-items:start}.ngx-form__field-slot{display:flex;align-items:flex-start;gap:6px;min-width:0}.ngx-form__field-slot ngx-dynamic-field{flex:1;min-width:0}.ngx-form__lock{flex:0 0 auto;margin-top:22px;background:none;border:1px solid transparent;border-radius:var(--ngx-radius-sm, 6px);cursor:pointer;font-size:14px;line-height:1;padding:4px 6px}.ngx-form__lock:hover{border-color:var(--ngx-color-border, #d1d5db);background:var(--ngx-color-surface-alt, #f9fafb)}@media(max-width:640px){.ngx-form__panel{grid-template-columns:minmax(0,1fr)}.ngx-form__field-slot{grid-column:1 / -1!important}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicFieldComponent, selector: "ngx-dynamic-field", inputs: ["field", "control", "config", "currentTabId", "language", "readonly", "userRoles"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9389
9416
  }
9390
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DynamicFormComponent, decorators: [{
9417
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: DynamicFormComponent, decorators: [{
9391
9418
  type: Component,
9392
9419
  args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-dynamic-form', standalone: true, imports: [ReactiveFormsModule, DynamicFieldComponent, NgComponentOutlet], providers: [EntityRefSelectionService, FormStructureService], template: "@if (canView) {\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\" class=\"ngx-form\" [class.ngx-form--readonly]=\"readonly\" novalidate>\n <!-- Error message banner -->\n @if (error) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" data-testid=\"form-error\">{{ error }}</div>\n }\n\n <!--\n Why the last Save did nothing.\n Only after a refused submit \u2014 see `submitAttempted`. Each entry jumps to its field,\n switching tabs on the way, because the field is usually not the one on screen.\n -->\n @if (submitAttempted() && invalidFields().length > 0) {\n <div class=\"ngx-form__error-summary\" role=\"alert\" data-testid=\"error-summary\" tabindex=\"-1\">\n <!--\n `error-summary-heading`, not `-title`: the links below are `error-summary-<fieldId>`,\n and a field whose id happens to be `title` would then share a test id with this line.\n The `extensions` entity has exactly such a field.\n -->\n <p class=\"ngx-form__error-summary-title\" data-testid=\"error-summary-heading\">\n {{ ui.text('errorSummaryTitle', language, { count: invalidFields().length }) }}\n </p>\n <ul class=\"ngx-form__error-summary-list\">\n @for (entry of invalidFields(); track entry.field.id) {\n <li>\n <!--\n The field, and what is wrong with it \u2014 the same sentence the field renders\n under itself, so the summary is not a second opinion. Naming only the field\n left the user to visit each one to find out what it wanted.\n -->\n <button\n type=\"button\"\n class=\"ngx-form__error-summary-link\"\n [attr.data-testid]=\"'error-summary-' + entry.field.id\"\n (click)=\"jumpToField(entry.field.id)\"\n >\n <span class=\"ngx-form__error-summary-field\">{{ resolveFieldLabel(entry.field) }}</span>\n @if (entry.message) {\n <span class=\"ngx-form__error-summary-reason\">{{ entry.message }}</span>\n }\n </button>\n </li>\n }\n </ul>\n </div>\n }\n\n <!-- Rules Info Banners -->\n @for (infoKey of showInfoBanners ? Object.keys(ruleResult().infoBanners) : []; track infoKey) {\n <div class=\"ngx-form__info-banner\" role=\"status\" [attr.data-testid]=\"'info-banner-' + infoKey\">\n \u2139\uFE0F {{ ruleResult().infoBanners[infoKey] }}\n </div>\n }\n\n <!-- Rules Validation Errors & Warnings -->\n @for (errKey of Object.keys(ruleResult().validationErrors); track errKey) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" [attr.data-testid]=\"'rule-error-' + errKey\">\n \u26A0\uFE0F {{ ruleResult().validationErrors[errKey] }}\n </div>\n }\n @for (warnKey of Object.keys(ruleResult().validationWarnings); track warnKey) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" [attr.data-testid]=\"'rule-warning-' + warnKey\">\n \u26A1 {{ ruleResult().validationWarnings[warnKey] }}\n </div>\n }\n\n <!-- Deferred VALUE_CHANGED notice for criticalField edits (vs session baseline) -->\n @if (changedCriticalFields().length > 0) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" data-testid=\"critical-change-banner\">\n {{ ui.text('criticalFieldChanged', language, { fields: changedCriticalFieldLabels() }) }}\n </div>\n }\n\n <!-- Primary Tab bar \u2014 only if visible tabs defined -->\n @if (visibleTabs.length > 0) {\n <div class=\"ngx-form__tabs\" role=\"tablist\" data-testid=\"tab-strip\">\n @for (tab of visibleTabs; track tab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab\"\n [attr.data-testid]=\"'tab-' + tab.id\"\n [class.ngx-form__tab--active]=\"activeTab() === tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n >\n {{ resolveTabLabel(tab) }}\n <!--\n How many fields on this tab are stopping the save.\n The count is in the accessible name rather than the badge, because \"3\" read out\n after a tab name says nothing about what it counts.\n -->\n @if (tabErrorCount(tab); as errors) {\n <span\n class=\"ngx-form__tab-badge\"\n [attr.data-testid]=\"'tab-errors-' + tab.id\"\n [attr.aria-label]=\"ui.text('tabErrorCount', language, { count: errors })\"\n >{{ errors }}</span\n >\n }\n </button>\n }\n </div>\n }\n\n <!-- Sub-tab bar \u2014 only if active tab has sub-tabs -->\n @if (visibleSubTabs.length > 0) {\n <div\n class=\"ngx-form__tabs ngx-form__tabs--sub\"\n role=\"tablist\"\n data-testid=\"subtab-strip\"\n style=\"margin-top: -12px; margin-bottom: 12px; font-size: 13px\"\n >\n @for (subTab of visibleSubTabs; track subTab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab ngx-form__tab--sub\"\n [attr.data-testid]=\"'subtab-' + subTab.id\"\n [class.ngx-form__tab--active]=\"activeSubTabConfig?.id === subTab.id\"\n (click)=\"setActiveSubTab(subTab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"activeSubTabConfig?.id === subTab.id\"\n >\n {{ resolveTabLabel(subTab) }}\n @if (subTabErrorCount(subTab); as errors) {\n <span\n class=\"ngx-form__tab-badge\"\n [attr.data-testid]=\"'subtab-errors-' + subTab.id\"\n [attr.aria-label]=\"ui.text('tabErrorCount', language, { count: errors })\"\n >{{ errors }}</span\n >\n }\n </button>\n }\n </div>\n }\n\n <!--\n One tabpanel per tab, wrapping both branches. Focus moves here on a tab switch, and a\n module tab is still a tab \u2014 putting the panel semantics on only the fields branch left\n a module tab with nowhere for focus to land.\n -->\n <div role=\"tabpanel\" tabindex=\"-1\" #formPanel class=\"ngx-form__tabpanel\" [attr.aria-label]=\"activeTabLabel\">\n @if (activeTabModuleComponent) {\n <div class=\"ngx-form__module-panel\" data-testid=\"module-panel\">\n <ng-container\n *ngComponentOutlet=\"activeTabModuleComponent; inputs: (activeSubTabConfig || activeTabConfig)?.moduleInputs\"\n ></ng-container>\n </div>\n } @else {\n <!-- Fields for active tab (Responsive 12-column grid) -->\n <div class=\"ngx-form__panel\" data-testid=\"form-panel\">\n @for (field of fieldsForActiveTab; track field.id) {\n <div\n class=\"ngx-form__field-slot\"\n [style.gridColumn]=\"getFieldSpan(field)\"\n [attr.id]=\"'field-container-' + field.id\"\n tabindex=\"-1\"\n >\n <ngx-dynamic-field\n [field]=\"field\"\n [control]=\"getControl(field.id, (activeSubTabConfig || activeTabConfig)?.id)!\"\n [config]=\"config\"\n [currentTabId]=\"(activeSubTabConfig || activeTabConfig)?.id\"\n [language]=\"language\"\n [readonly]=\"isFieldReadonly(field)\"\n [userRoles]=\"userRoles\"\n />\n @if (field.hasDrift) {\n <!--\n Drift was previously visible only in the builder, so a referenced field whose\n source had changed looked completely normal to the person filling the form in.\n role=\"status\" rather than \"alert\": it is information, not an error.\n -->\n <span\n class=\"ngx-field__drift\"\n role=\"status\"\n [attr.data-testid]=\"'field-' + field.id + '-drift'\"\n [title]=\"driftHint(field)\"\n >\u26A0 {{ driftHint(field) }}</span\n >\n }\n @if (field.criticalField && !readonly) {\n <button\n type=\"button\"\n class=\"ngx-form__lock\"\n [attr.data-testid]=\"'lock-' + field.id\"\n [attr.aria-pressed]=\"!isFieldLocked(field)\"\n [attr.aria-label]=\"\n ui.text(isFieldLocked(field) ? 'unlockField' : 'lockField', language, {\n field: resolveFieldLabel(field),\n })\n \"\n [title]=\"ui.text(isFieldLocked(field) ? 'criticalFieldUnlockHint' : 'criticalFieldLockHint', language)\"\n (click)=\"toggleFieldLock(field)\"\n >\n {{ isFieldLocked(field) ? '\uD83D\uDD12' : '\uD83D\uDD13' }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Submit button \u2014 gated by RBAC and isSaving lock -->\n @if (!readonly && permissions.canEdit) {\n <div class=\"ngx-form__actions\" data-testid=\"form-actions\">\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"form-reset\" (click)=\"reset()\" [disabled]=\"isSaving()\">\n {{ ui.text('reset', language) }}\n </button>\n <!--\n Two different \"unavailable\" states, and they are not the same thing.\n\n `disabled` is for what a retry cannot fix \u2014 a save in flight, an async check still\n outstanding. `data-blocked` is for validation: the button stays fully operable,\n because pressing it is how the user gets the error summary, but it should not look\n ready either. The stylesheet mutes it without taking the click away.\n\n Deliberately not `aria-disabled`. That announces a control as present-but-inoperable,\n and this one is operable and useful \u2014 pressing it raises a `role=\"alert\"` naming\n every field at fault, which is more than a disabled button could ever say. Tooling\n agrees: Playwright refuses to click through `aria-disabled`, and it would be right\n to, because the attribute would be claiming something untrue.\n -->\n <button\n type=\"submit\"\n class=\"ngx-form__submit\"\n data-testid=\"form-submit\"\n [disabled]=\"submitDisabled\"\n [attr.data-blocked]=\"submitBlocked ? '' : null\"\n [title]=\"ui.text('saveShortcutHint', language)\"\n >\n @if (isSaving() || loading) {\n {{ ui.text('saving', language) }}\n } @else {\n {{ ui.text('save', language) }}\n }\n </button>\n </div>\n }\n </form>\n} @else {\n <!-- permissions.view denied: render nothing of the record.\n Presentational only \u2014 the server must not send data this user may not see. -->\n <div class=\"ngx-form__denied\" role=\"status\" data-testid=\"form-access-denied\">\n {{ ui.text('accessDenied', language) }}\n </div>\n}\n", styles: [".ngx-form__panel{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:var(--ngx-gap, 18px);align-items:start}.ngx-form__field-slot{display:flex;align-items:flex-start;gap:6px;min-width:0}.ngx-form__field-slot ngx-dynamic-field{flex:1;min-width:0}.ngx-form__lock{flex:0 0 auto;margin-top:22px;background:none;border:1px solid transparent;border-radius:var(--ngx-radius-sm, 6px);cursor:pointer;font-size:14px;line-height:1;padding:4px 6px}.ngx-form__lock:hover{border-color:var(--ngx-color-border, #d1d5db);background:var(--ngx-color-surface-alt, #f9fafb)}@media(max-width:640px){.ngx-form__panel{grid-template-columns:minmax(0,1fr)}.ngx-form__field-slot{grid-column:1 / -1!important}}\n"] }]
9393
9420
  }], propDecorators: { config: [{
@@ -10000,10 +10027,10 @@ class DynamicRecordFormComponent {
10000
10027
  jumpToField(fieldId) {
10001
10028
  this.dynamicFormComp?.jumpToField(fieldId);
10002
10029
  }
10003
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DynamicRecordFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10004
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: DynamicRecordFormComponent, isStandalone: true, selector: "ngx-dynamic-record-form", inputs: { config: "config", rules: "rules", initialData: "initialData", userRoles: "userRoles", language: "language", readonly: "readonly", loading: "loading", error: "error", isReadOnly: "isReadOnly", readOnlyFields: "readOnlyFields", layout: "layout", viewMode: "viewMode" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", sectionSave: "sectionSave", saveRejected: "saveRejected" }, viewQueries: [{ propertyName: "dynamicFormComp", first: true, predicate: DynamicFormComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"ngx-record-editor\">\n <!-- Record Header -->\n <div class=\"ngx-record-editor__header\">\n <div class=\"ngx-record-editor__title-group\">\n @if (profileImageUrl) {\n <img\n class=\"ngx-record-editor__avatar\"\n data-testid=\"header-profile-image\"\n [src]=\"profileImageUrl\"\n [alt]=\"recordHeading\"\n />\n } @else {\n <div class=\"ngx-record-editor__avatar\" data-testid=\"header-avatar-initial\">\n {{ avatarLetter }}\n </div>\n }\n <div>\n <!--\n The record's own name, with the entity underneath. These were the other way round \u2014\n both showing the entity \u2014 so the header never said which record was open.\n -->\n <h2 class=\"ngx-record-editor__title\" data-testid=\"record-title\">\n {{ recordHeading }}\n </h2>\n <span class=\"ngx-record-editor__subtitle\">{{ ui.text('entityLabel', language, { entity: recordTitle }) }}</span>\n </div>\n </div>\n\n <!-- Record-level status switch, outside section editing -->\n @if (headerToggleField; as toggleField) {\n <label class=\"ngx-record-editor__header-toggle\">\n <span>{{ formatFieldLabel(toggleField) }}</span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n data-testid=\"header-toggle\"\n [checked]=\"headerToggleValue\"\n [disabled]=\"recordReadOnly\"\n [attr.aria-checked]=\"headerToggleValue\"\n (change)=\"toggleHeaderStatus()\"\n />\n <span class=\"ngx-record-editor__header-toggle-text\">{{ ui.text(headerToggleValue ? 'yes' : 'no', language) }}</span>\n </label>\n }\n </div>\n\n <!-- Dismissible info banners (rules `info` action) -->\n @for (banner of infoBanners; track banner.fieldId) {\n <div\n class=\"ngx-record-editor__banner ngx-record-editor__banner--info\"\n role=\"status\"\n [attr.data-testid]=\"'info-banner-' + banner.fieldId\"\n >\n <span>\u2139\uFE0F {{ banner.message }}</span>\n <button\n type=\"button\"\n class=\"ngx-record-editor__banner-dismiss\"\n [attr.data-testid]=\"'dismiss-' + banner.fieldId\"\n [attr.aria-label]=\"ui.text('dismissBanner', language, { message: banner.message })\"\n (click)=\"dismissInfoBanner(banner.fieldId)\"\n >\n \u2715\n </button>\n </div>\n }\n\n <!-- Modification baseline banner -->\n @if (isModified()) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--warning\">\n {{ ui.text('recordModified', language) }}\n </div>\n }\n\n <!-- Summary Panel (showOnMinimize fields with interactive jump) -->\n @if (summaryFields().length > 0) {\n <div class=\"ngx-record-editor__summary-panel\" data-testid=\"summary-panel\">\n @for (sf of summaryFields(); track sf.id) {\n <div\n class=\"ngx-record-editor__summary-item\"\n [attr.data-testid]=\"'summary-' + sf.id\"\n (click)=\"jumpToField(sf.id)\"\n [title]=\"ui.text('jumpToField', language, { field: formatFieldLabel(sf) })\"\n >\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(sf) }}</span>\n <span class=\"ngx-record-editor__summary-val\">{{ formatFieldValue(sf) }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Section edit bar: one tab is edited at a time, and validated on its own -->\n @if (viewMode && !recordReadOnly) {\n <div class=\"ngx-record-editor__section-bar\">\n @if (isEditingActiveTab) {\n <span class=\"ngx-record-editor__section-hint\">{{ ui.text('editingSection', language) }}</span>\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-section\" (click)=\"cancelSection()\">\n {{ ui.text('cancel', language) }}\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-section\" (click)=\"saveSection()\">\n {{ ui.text('saveSection', language) }}\n </button>\n } @else {\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"edit-section\" (click)=\"editSection()\">\n {{ ui.text('editSection', language) }}\n </button>\n }\n </div>\n }\n\n @if (Object.keys(sectionErrors()).length > 0) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--error\" role=\"alert\" data-testid=\"section-errors\">\n @for (key of Object.keys(sectionErrors()); track key) {\n <div>\u26A0\uFE0F {{ sectionErrors()[key] }}</div>\n }\n </div>\n }\n\n <!-- Array rows: listed here, edited in the drawer below -->\n @for (arrayField of arrayFieldsForActiveTab; track arrayField.id) {\n <div class=\"ngx-record-editor__rows\" [attr.data-testid]=\"'rows-' + arrayField.id\">\n <div class=\"ngx-record-editor__rows-head\">\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(arrayField) }}</span>\n @if (!sectionReadOnly) {\n <button\n type=\"button\"\n class=\"ngx-form__reset\"\n [attr.data-testid]=\"'add-row-' + arrayField.id\"\n (click)=\"openAddRow(arrayField)\"\n >\n {{ ui.text('addRow', language) }}\n </button>\n }\n </div>\n\n @for (row of rowsOf(arrayField); track $index) {\n <div class=\"ngx-record-editor__row\" [attr.data-testid]=\"'row-' + arrayField.id + '-' + $index\">\n <span class=\"ngx-record-editor__row-text\">{{ rowSummary(arrayField, row) }}</span>\n @if (!sectionReadOnly) {\n <button\n type=\"button\"\n class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'edit-row-' + arrayField.id + '-' + $index\"\n (click)=\"openEditRow(arrayField, $index)\"\n >\n {{ ui.text('edit', language) }}\n </button>\n <button\n type=\"button\"\n class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'delete-row-' + arrayField.id + '-' + $index\"\n (click)=\"deleteRow(arrayField, $index)\"\n >\n {{ ui.text('delete', language) }}\n </button>\n }\n </div>\n } @empty {\n <p class=\"ngx-record-editor__row-empty\">{{ ui.text('noRows', language) }}</p>\n }\n </div>\n }\n\n <!--\n Row drawer. Deliberately outside the tab panel: building a row's FormGroup inside the\n tab view races the tab's own initialisation (the reference hit this with FormArray).\n -->\n @if (inlineRowField(); as rowField) {\n <div class=\"ngx-record-editor__drawer\" data-testid=\"row-drawer\" [formGroup]=\"inlineRowForm!\">\n <div class=\"ngx-record-editor__rows-head\">\n <strong>{{ rowDrawerTitle(rowField) }}</strong>\n </div>\n\n <div class=\"ngx-form__panel\">\n @for (child of rowField.children ?? []; track child.id) {\n <ngx-dynamic-field\n [field]=\"child\"\n [control]=\"inlineRowForm!.get(child.id)!\"\n [config]=\"config\"\n [language]=\"language\"\n [readonly]=\"false\"\n [userRoles]=\"userRoles\"\n [style.gridColumn]=\"'span 12'\"\n />\n }\n </div>\n\n <div class=\"ngx-record-editor__section-bar\">\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-row\" (click)=\"cancelRow()\">\n {{ ui.text('cancel', language) }}\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-row\" (click)=\"saveRow()\">\n {{ ui.text('saveRow', language) }}\n </button>\n </div>\n </div>\n }\n\n <!-- Main Dynamic Form -->\n <ngx-dynamic-form\n [config]=\"config\"\n [rules]=\"rules\"\n [initialData]=\"initialData\"\n [originalData]=\"originalBaseline()\"\n [userRoles]=\"userRoles\"\n [language]=\"language\"\n [readonly]=\"sectionReadOnly\"\n [readOnlyFields]=\"readOnlyFields\"\n [layout]=\"layout\"\n [showInfoBanners]=\"false\"\n [loading]=\"loading\"\n [error]=\"error\"\n (formChange)=\"onFormChange($event)\"\n (formSubmit)=\"onFormSubmit($event)\"\n (saveRejected)=\"saveRejected.emit($event)\"\n (formReset)=\"onFormReset()\"\n (activeTabChange)=\"onActiveTabChange($event)\"\n />\n</div>\n", styles: [".ngx-record-editor{display:flex;flex-direction:column;gap:var(--ngx-gap, 16px);background:var(--ngx-color-surface, #ffffff);border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius, 12px);padding:20px}.ngx-record-editor__header{display:flex;align-items:center;justify-content:space-between;gap:14px;padding-bottom:var(--ngx-gap-sm, 16px);border-bottom:1px solid var(--ngx-color-border, #f3f4f6)}.ngx-record-editor__title-group{display:flex;align-items:center;gap:12px;min-width:0}.ngx-record-editor__avatar{width:44px;height:44px;border-radius:50%;object-fit:cover;background:var(--ngx-color-accent-soft, #e0e7ff);color:var(--ngx-color-accent, #4f46e5);border:1px solid var(--ngx-color-border, transparent);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:17px;flex:none}.ngx-record-editor__title{font-size:19px;font-weight:700;color:var(--ngx-color-text, #111827);margin:0;letter-spacing:-.01em}.ngx-record-editor__subtitle{font-size:var(--ngx-hint-size, 13px);color:var(--ngx-color-muted, #6b7280)}.ngx-record-editor__header-toggle{display:flex;align-items:center;gap:8px;font-size:var(--ngx-label-size, 13px);font-weight:var(--ngx-label-weight, 600);color:var(--ngx-color-text, #374151)}.ngx-record-editor__header-toggle-text{font-weight:600}.ngx-record-editor__banner{padding:11px 14px;border-radius:var(--ngx-radius-sm, 8px);font-size:var(--ngx-font-size, 13px);border:1px solid transparent;border-left-width:3px}.ngx-record-editor__banner--info{background:var(--ngx-color-info-soft, #eff6ff);border-color:var(--ngx-color-border, #dbeafe);border-left-color:var(--ngx-color-info, #1d4ed8);color:var(--ngx-color-info, #1d4ed8);display:flex;align-items:center;justify-content:space-between;gap:12px}.ngx-record-editor__banner--error{background:var(--ngx-color-error-soft, #fef2f2);border-color:var(--ngx-color-border, #fecaca);border-left-color:var(--ngx-color-error, #b91c1c);color:var(--ngx-color-error, #b91c1c)}.ngx-record-editor__banner--warning{background:var(--ngx-color-warning-soft, #fffbeb);border-color:var(--ngx-color-border, #fef3c7);border-left-color:var(--ngx-color-warning, #b45309);color:var(--ngx-color-warning, #b45309)}.ngx-record-editor__banner-dismiss{background:none;border:none;cursor:pointer;color:inherit;font-size:14px;line-height:1;padding:2px 6px;border-radius:4px}.ngx-record-editor__banner-dismiss:hover{background:#0000000f}.ngx-record-editor__section-bar{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:8px}.ngx-record-editor__section-hint{margin-right:auto;font-size:var(--ngx-hint-size, 12px);color:var(--ngx-color-muted, #6b7280)}.ngx-record-editor__rows{border:1px solid var(--ngx-color-border, #f3f4f6);border-radius:var(--ngx-radius-sm, 8px);padding:12px 14px;display:flex;flex-direction:column;gap:6px}.ngx-record-editor__rows-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.ngx-record-editor__row{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface-alt, #f9fafb)}.ngx-record-editor__row-text{flex:1;font-size:var(--ngx-font-size, 13px);overflow-wrap:anywhere}.ngx-record-editor__row-btn{background:var(--ngx-color-surface, none);border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px);cursor:pointer;font:inherit;font-size:var(--ngx-hint-size, 12px);font-weight:var(--ngx-label-weight, 600);color:inherit;padding:4px 10px}.ngx-record-editor__row-btn:hover{border-color:var(--ngx-color-accent, #e5e7eb);color:var(--ngx-color-accent, inherit)}.ngx-record-editor__row-empty{font-size:var(--ngx-hint-size, 12px);color:var(--ngx-color-muted, #6b7280);margin:0}.ngx-record-editor__drawer{border:1px solid var(--ngx-color-border, #dbeafe);background:var(--ngx-color-surface-alt, #f8fafc);border-radius:var(--ngx-radius, 8px);padding:16px;display:flex;flex-direction:column;gap:12px}.ngx-record-editor__summary-panel{background:var(--ngx-color-surface-alt, #f9fafb);border:1px solid var(--ngx-color-border, #f3f4f6);border-radius:var(--ngx-radius, 8px);padding:12px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:4px var(--ngx-gap, 12px)}.ngx-record-editor__summary-item{display:flex;flex-direction:column;cursor:pointer;padding:6px 10px;border-radius:var(--ngx-radius-sm, 6px);min-width:0;transition:background .15s}.ngx-record-editor__summary-item:hover{background:var(--ngx-color-accent-soft, #eff6ff)}.ngx-record-editor__summary-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--ngx-color-muted, #6b7280);font-weight:600}.ngx-record-editor__summary-val{font-size:var(--ngx-font-size, 14px);font-weight:600;color:var(--ngx-color-text, #1f2937);overflow-wrap:anywhere}@media(prefers-reduced-motion:reduce){.ngx-record-editor__summary-item{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicFormComponent, selector: "ngx-dynamic-form", inputs: ["config", "rules", "initialData", "originalData", "userRoles", "language", "readonly", "readOnlyFields", "showInfoBanners", "loading", "error", "changeDebounceMs", "preview", "layout"], outputs: ["formSubmit", "formChange", "formReset", "activeTabChange", "saveRejected"] }, { kind: "component", type: DynamicFieldComponent, selector: "ngx-dynamic-field", inputs: ["field", "control", "config", "currentTabId", "language", "readonly", "userRoles"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10030
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: DynamicRecordFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10031
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: DynamicRecordFormComponent, isStandalone: true, selector: "ngx-dynamic-record-form", inputs: { config: "config", rules: "rules", initialData: "initialData", userRoles: "userRoles", language: "language", readonly: "readonly", loading: "loading", error: "error", isReadOnly: "isReadOnly", readOnlyFields: "readOnlyFields", layout: "layout", viewMode: "viewMode" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", sectionSave: "sectionSave", saveRejected: "saveRejected" }, viewQueries: [{ propertyName: "dynamicFormComp", first: true, predicate: DynamicFormComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"ngx-record-editor\">\n <!-- Record Header -->\n <div class=\"ngx-record-editor__header\">\n <div class=\"ngx-record-editor__title-group\">\n @if (profileImageUrl) {\n <img\n class=\"ngx-record-editor__avatar\"\n data-testid=\"header-profile-image\"\n [src]=\"profileImageUrl\"\n [alt]=\"recordHeading\"\n />\n } @else {\n <div class=\"ngx-record-editor__avatar\" data-testid=\"header-avatar-initial\">\n {{ avatarLetter }}\n </div>\n }\n <div>\n <!--\n The record's own name, with the entity underneath. These were the other way round \u2014\n both showing the entity \u2014 so the header never said which record was open.\n -->\n <h2 class=\"ngx-record-editor__title\" data-testid=\"record-title\">\n {{ recordHeading }}\n </h2>\n <span class=\"ngx-record-editor__subtitle\">{{ ui.text('entityLabel', language, { entity: recordTitle }) }}</span>\n </div>\n </div>\n\n <!-- Record-level status switch, outside section editing -->\n @if (headerToggleField; as toggleField) {\n <label class=\"ngx-record-editor__header-toggle\">\n <span>{{ formatFieldLabel(toggleField) }}</span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n data-testid=\"header-toggle\"\n [checked]=\"headerToggleValue\"\n [disabled]=\"recordReadOnly\"\n [attr.aria-checked]=\"headerToggleValue\"\n (change)=\"toggleHeaderStatus()\"\n />\n <span class=\"ngx-record-editor__header-toggle-text\">{{ ui.text(headerToggleValue ? 'yes' : 'no', language) }}</span>\n </label>\n }\n </div>\n\n <!-- Dismissible info banners (rules `info` action) -->\n @for (banner of infoBanners; track banner.fieldId) {\n <div\n class=\"ngx-record-editor__banner ngx-record-editor__banner--info\"\n role=\"status\"\n [attr.data-testid]=\"'info-banner-' + banner.fieldId\"\n >\n <span>\u2139\uFE0F {{ banner.message }}</span>\n <button\n type=\"button\"\n class=\"ngx-record-editor__banner-dismiss\"\n [attr.data-testid]=\"'dismiss-' + banner.fieldId\"\n [attr.aria-label]=\"ui.text('dismissBanner', language, { message: banner.message })\"\n (click)=\"dismissInfoBanner(banner.fieldId)\"\n >\n \u2715\n </button>\n </div>\n }\n\n <!-- Modification baseline banner -->\n @if (isModified()) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--warning\">\n {{ ui.text('recordModified', language) }}\n </div>\n }\n\n <!-- Summary Panel (showOnMinimize fields with interactive jump) -->\n @if (summaryFields().length > 0) {\n <div class=\"ngx-record-editor__summary-panel\" data-testid=\"summary-panel\">\n @for (sf of summaryFields(); track sf.id) {\n <div\n class=\"ngx-record-editor__summary-item\"\n [attr.data-testid]=\"'summary-' + sf.id\"\n (click)=\"jumpToField(sf.id)\"\n [title]=\"ui.text('jumpToField', language, { field: formatFieldLabel(sf) })\"\n >\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(sf) }}</span>\n <span class=\"ngx-record-editor__summary-val\">{{ formatFieldValue(sf) }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Section edit bar: one tab is edited at a time, and validated on its own -->\n @if (viewMode && !recordReadOnly) {\n <div class=\"ngx-record-editor__section-bar\">\n @if (isEditingActiveTab) {\n <span class=\"ngx-record-editor__section-hint\">{{ ui.text('editingSection', language) }}</span>\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-section\" (click)=\"cancelSection()\">\n {{ ui.text('cancel', language) }}\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-section\" (click)=\"saveSection()\">\n {{ ui.text('saveSection', language) }}\n </button>\n } @else {\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"edit-section\" (click)=\"editSection()\">\n {{ ui.text('editSection', language) }}\n </button>\n }\n </div>\n }\n\n @if (Object.keys(sectionErrors()).length > 0) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--error\" role=\"alert\" data-testid=\"section-errors\">\n @for (key of Object.keys(sectionErrors()); track key) {\n <div>\u26A0\uFE0F {{ sectionErrors()[key] }}</div>\n }\n </div>\n }\n\n <!-- Array rows: listed here, edited in the drawer below -->\n @for (arrayField of arrayFieldsForActiveTab; track arrayField.id) {\n <div class=\"ngx-record-editor__rows\" [attr.data-testid]=\"'rows-' + arrayField.id\">\n <div class=\"ngx-record-editor__rows-head\">\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(arrayField) }}</span>\n @if (!sectionReadOnly) {\n <button\n type=\"button\"\n class=\"ngx-form__reset\"\n [attr.data-testid]=\"'add-row-' + arrayField.id\"\n (click)=\"openAddRow(arrayField)\"\n >\n {{ ui.text('addRow', language) }}\n </button>\n }\n </div>\n\n @for (row of rowsOf(arrayField); track $index) {\n <div class=\"ngx-record-editor__row\" [attr.data-testid]=\"'row-' + arrayField.id + '-' + $index\">\n <span class=\"ngx-record-editor__row-text\">{{ rowSummary(arrayField, row) }}</span>\n @if (!sectionReadOnly) {\n <button\n type=\"button\"\n class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'edit-row-' + arrayField.id + '-' + $index\"\n (click)=\"openEditRow(arrayField, $index)\"\n >\n {{ ui.text('edit', language) }}\n </button>\n <button\n type=\"button\"\n class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'delete-row-' + arrayField.id + '-' + $index\"\n (click)=\"deleteRow(arrayField, $index)\"\n >\n {{ ui.text('delete', language) }}\n </button>\n }\n </div>\n } @empty {\n <p class=\"ngx-record-editor__row-empty\">{{ ui.text('noRows', language) }}</p>\n }\n </div>\n }\n\n <!--\n Row drawer. Deliberately outside the tab panel: building a row's FormGroup inside the\n tab view races the tab's own initialisation (the reference hit this with FormArray).\n -->\n @if (inlineRowField(); as rowField) {\n <div class=\"ngx-record-editor__drawer\" data-testid=\"row-drawer\" [formGroup]=\"inlineRowForm!\">\n <div class=\"ngx-record-editor__rows-head\">\n <strong>{{ rowDrawerTitle(rowField) }}</strong>\n </div>\n\n <div class=\"ngx-form__panel\">\n @for (child of rowField.children ?? []; track child.id) {\n <ngx-dynamic-field\n [field]=\"child\"\n [control]=\"inlineRowForm!.get(child.id)!\"\n [config]=\"config\"\n [language]=\"language\"\n [readonly]=\"false\"\n [userRoles]=\"userRoles\"\n [style.gridColumn]=\"'span 12'\"\n />\n }\n </div>\n\n <div class=\"ngx-record-editor__section-bar\">\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-row\" (click)=\"cancelRow()\">\n {{ ui.text('cancel', language) }}\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-row\" (click)=\"saveRow()\">\n {{ ui.text('saveRow', language) }}\n </button>\n </div>\n </div>\n }\n\n <!-- Main Dynamic Form -->\n <ngx-dynamic-form\n [config]=\"config\"\n [rules]=\"rules\"\n [initialData]=\"initialData\"\n [originalData]=\"originalBaseline()\"\n [userRoles]=\"userRoles\"\n [language]=\"language\"\n [readonly]=\"sectionReadOnly\"\n [readOnlyFields]=\"readOnlyFields\"\n [layout]=\"layout\"\n [showInfoBanners]=\"false\"\n [loading]=\"loading\"\n [error]=\"error\"\n (formChange)=\"onFormChange($event)\"\n (formSubmit)=\"onFormSubmit($event)\"\n (saveRejected)=\"saveRejected.emit($event)\"\n (formReset)=\"onFormReset()\"\n (activeTabChange)=\"onActiveTabChange($event)\"\n />\n</div>\n", styles: [".ngx-record-editor{display:flex;flex-direction:column;gap:var(--ngx-gap, 16px);background:var(--ngx-color-surface, #ffffff);border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius, 12px);padding:20px}.ngx-record-editor__header{display:flex;align-items:center;justify-content:space-between;gap:14px;padding-bottom:var(--ngx-gap-sm, 16px);border-bottom:1px solid var(--ngx-color-border, #f3f4f6)}.ngx-record-editor__title-group{display:flex;align-items:center;gap:12px;min-width:0}.ngx-record-editor__avatar{width:44px;height:44px;border-radius:50%;object-fit:cover;background:var(--ngx-color-accent-soft, #e0e7ff);color:var(--ngx-color-accent, #4f46e5);border:1px solid var(--ngx-color-border, transparent);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:17px;flex:none}.ngx-record-editor__title{font-size:19px;font-weight:700;color:var(--ngx-color-text, #111827);margin:0;letter-spacing:-.01em}.ngx-record-editor__subtitle{font-size:var(--ngx-hint-size, 13px);color:var(--ngx-color-muted, #6b7280)}.ngx-record-editor__header-toggle{display:flex;align-items:center;gap:8px;font-size:var(--ngx-label-size, 13px);font-weight:var(--ngx-label-weight, 600);color:var(--ngx-color-text, #374151)}.ngx-record-editor__header-toggle-text{font-weight:600}.ngx-record-editor__banner{padding:11px 14px;border-radius:var(--ngx-radius-sm, 8px);font-size:var(--ngx-font-size, 13px);border:1px solid transparent;border-left-width:3px}.ngx-record-editor__banner--info{background:var(--ngx-color-info-soft, #eff6ff);border-color:var(--ngx-color-border, #dbeafe);border-left-color:var(--ngx-color-info, #1d4ed8);color:var(--ngx-color-info, #1d4ed8);display:flex;align-items:center;justify-content:space-between;gap:12px}.ngx-record-editor__banner--error{background:var(--ngx-color-error-soft, #fef2f2);border-color:var(--ngx-color-border, #fecaca);border-left-color:var(--ngx-color-error, #b91c1c);color:var(--ngx-color-error, #b91c1c)}.ngx-record-editor__banner--warning{background:var(--ngx-color-warning-soft, #fffbeb);border-color:var(--ngx-color-border, #fef3c7);border-left-color:var(--ngx-color-warning, #b45309);color:var(--ngx-color-warning, #b45309)}.ngx-record-editor__banner-dismiss{background:none;border:none;cursor:pointer;color:inherit;font-size:14px;line-height:1;padding:2px 6px;border-radius:4px}.ngx-record-editor__banner-dismiss:hover{background:#0000000f}.ngx-record-editor__section-bar{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:8px}.ngx-record-editor__section-hint{margin-right:auto;font-size:var(--ngx-hint-size, 12px);color:var(--ngx-color-muted, #6b7280)}.ngx-record-editor__rows{border:1px solid var(--ngx-color-border, #f3f4f6);border-radius:var(--ngx-radius-sm, 8px);padding:12px 14px;display:flex;flex-direction:column;gap:6px}.ngx-record-editor__rows-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.ngx-record-editor__row{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface-alt, #f9fafb)}.ngx-record-editor__row-text{flex:1;font-size:var(--ngx-font-size, 13px);overflow-wrap:anywhere}.ngx-record-editor__row-btn{background:var(--ngx-color-surface, none);border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px);cursor:pointer;font:inherit;font-size:var(--ngx-hint-size, 12px);font-weight:var(--ngx-label-weight, 600);color:inherit;padding:4px 10px}.ngx-record-editor__row-btn:hover{border-color:var(--ngx-color-accent, #e5e7eb);color:var(--ngx-color-accent, inherit)}.ngx-record-editor__row-empty{font-size:var(--ngx-hint-size, 12px);color:var(--ngx-color-muted, #6b7280);margin:0}.ngx-record-editor__drawer{border:1px solid var(--ngx-color-border, #dbeafe);background:var(--ngx-color-surface-alt, #f8fafc);border-radius:var(--ngx-radius, 8px);padding:16px;display:flex;flex-direction:column;gap:12px}.ngx-record-editor__summary-panel{background:var(--ngx-color-surface-alt, #f9fafb);border:1px solid var(--ngx-color-border, #f3f4f6);border-radius:var(--ngx-radius, 8px);padding:12px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:4px var(--ngx-gap, 12px)}.ngx-record-editor__summary-item{display:flex;flex-direction:column;cursor:pointer;padding:6px 10px;border-radius:var(--ngx-radius-sm, 6px);min-width:0;transition:background .15s}.ngx-record-editor__summary-item:hover{background:var(--ngx-color-accent-soft, #eff6ff)}.ngx-record-editor__summary-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--ngx-color-muted, #6b7280);font-weight:600}.ngx-record-editor__summary-val{font-size:var(--ngx-font-size, 14px);font-weight:600;color:var(--ngx-color-text, #1f2937);overflow-wrap:anywhere}@media(prefers-reduced-motion:reduce){.ngx-record-editor__summary-item{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicFormComponent, selector: "ngx-dynamic-form", inputs: ["config", "rules", "initialData", "originalData", "userRoles", "language", "readonly", "readOnlyFields", "showInfoBanners", "loading", "error", "changeDebounceMs", "preview", "layout"], outputs: ["formSubmit", "formChange", "formReset", "activeTabChange", "saveRejected"] }, { kind: "component", type: DynamicFieldComponent, selector: "ngx-dynamic-field", inputs: ["field", "control", "config", "currentTabId", "language", "readonly", "userRoles"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10005
10032
  }
10006
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DynamicRecordFormComponent, decorators: [{
10033
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: DynamicRecordFormComponent, decorators: [{
10007
10034
  type: Component,
10008
10035
  args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-dynamic-record-form', standalone: true, imports: [CommonModule, ReactiveFormsModule, DynamicFormComponent, DynamicFieldComponent], template: "<div class=\"ngx-record-editor\">\n <!-- Record Header -->\n <div class=\"ngx-record-editor__header\">\n <div class=\"ngx-record-editor__title-group\">\n @if (profileImageUrl) {\n <img\n class=\"ngx-record-editor__avatar\"\n data-testid=\"header-profile-image\"\n [src]=\"profileImageUrl\"\n [alt]=\"recordHeading\"\n />\n } @else {\n <div class=\"ngx-record-editor__avatar\" data-testid=\"header-avatar-initial\">\n {{ avatarLetter }}\n </div>\n }\n <div>\n <!--\n The record's own name, with the entity underneath. These were the other way round \u2014\n both showing the entity \u2014 so the header never said which record was open.\n -->\n <h2 class=\"ngx-record-editor__title\" data-testid=\"record-title\">\n {{ recordHeading }}\n </h2>\n <span class=\"ngx-record-editor__subtitle\">{{ ui.text('entityLabel', language, { entity: recordTitle }) }}</span>\n </div>\n </div>\n\n <!-- Record-level status switch, outside section editing -->\n @if (headerToggleField; as toggleField) {\n <label class=\"ngx-record-editor__header-toggle\">\n <span>{{ formatFieldLabel(toggleField) }}</span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n data-testid=\"header-toggle\"\n [checked]=\"headerToggleValue\"\n [disabled]=\"recordReadOnly\"\n [attr.aria-checked]=\"headerToggleValue\"\n (change)=\"toggleHeaderStatus()\"\n />\n <span class=\"ngx-record-editor__header-toggle-text\">{{ ui.text(headerToggleValue ? 'yes' : 'no', language) }}</span>\n </label>\n }\n </div>\n\n <!-- Dismissible info banners (rules `info` action) -->\n @for (banner of infoBanners; track banner.fieldId) {\n <div\n class=\"ngx-record-editor__banner ngx-record-editor__banner--info\"\n role=\"status\"\n [attr.data-testid]=\"'info-banner-' + banner.fieldId\"\n >\n <span>\u2139\uFE0F {{ banner.message }}</span>\n <button\n type=\"button\"\n class=\"ngx-record-editor__banner-dismiss\"\n [attr.data-testid]=\"'dismiss-' + banner.fieldId\"\n [attr.aria-label]=\"ui.text('dismissBanner', language, { message: banner.message })\"\n (click)=\"dismissInfoBanner(banner.fieldId)\"\n >\n \u2715\n </button>\n </div>\n }\n\n <!-- Modification baseline banner -->\n @if (isModified()) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--warning\">\n {{ ui.text('recordModified', language) }}\n </div>\n }\n\n <!-- Summary Panel (showOnMinimize fields with interactive jump) -->\n @if (summaryFields().length > 0) {\n <div class=\"ngx-record-editor__summary-panel\" data-testid=\"summary-panel\">\n @for (sf of summaryFields(); track sf.id) {\n <div\n class=\"ngx-record-editor__summary-item\"\n [attr.data-testid]=\"'summary-' + sf.id\"\n (click)=\"jumpToField(sf.id)\"\n [title]=\"ui.text('jumpToField', language, { field: formatFieldLabel(sf) })\"\n >\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(sf) }}</span>\n <span class=\"ngx-record-editor__summary-val\">{{ formatFieldValue(sf) }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Section edit bar: one tab is edited at a time, and validated on its own -->\n @if (viewMode && !recordReadOnly) {\n <div class=\"ngx-record-editor__section-bar\">\n @if (isEditingActiveTab) {\n <span class=\"ngx-record-editor__section-hint\">{{ ui.text('editingSection', language) }}</span>\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-section\" (click)=\"cancelSection()\">\n {{ ui.text('cancel', language) }}\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-section\" (click)=\"saveSection()\">\n {{ ui.text('saveSection', language) }}\n </button>\n } @else {\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"edit-section\" (click)=\"editSection()\">\n {{ ui.text('editSection', language) }}\n </button>\n }\n </div>\n }\n\n @if (Object.keys(sectionErrors()).length > 0) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--error\" role=\"alert\" data-testid=\"section-errors\">\n @for (key of Object.keys(sectionErrors()); track key) {\n <div>\u26A0\uFE0F {{ sectionErrors()[key] }}</div>\n }\n </div>\n }\n\n <!-- Array rows: listed here, edited in the drawer below -->\n @for (arrayField of arrayFieldsForActiveTab; track arrayField.id) {\n <div class=\"ngx-record-editor__rows\" [attr.data-testid]=\"'rows-' + arrayField.id\">\n <div class=\"ngx-record-editor__rows-head\">\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(arrayField) }}</span>\n @if (!sectionReadOnly) {\n <button\n type=\"button\"\n class=\"ngx-form__reset\"\n [attr.data-testid]=\"'add-row-' + arrayField.id\"\n (click)=\"openAddRow(arrayField)\"\n >\n {{ ui.text('addRow', language) }}\n </button>\n }\n </div>\n\n @for (row of rowsOf(arrayField); track $index) {\n <div class=\"ngx-record-editor__row\" [attr.data-testid]=\"'row-' + arrayField.id + '-' + $index\">\n <span class=\"ngx-record-editor__row-text\">{{ rowSummary(arrayField, row) }}</span>\n @if (!sectionReadOnly) {\n <button\n type=\"button\"\n class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'edit-row-' + arrayField.id + '-' + $index\"\n (click)=\"openEditRow(arrayField, $index)\"\n >\n {{ ui.text('edit', language) }}\n </button>\n <button\n type=\"button\"\n class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'delete-row-' + arrayField.id + '-' + $index\"\n (click)=\"deleteRow(arrayField, $index)\"\n >\n {{ ui.text('delete', language) }}\n </button>\n }\n </div>\n } @empty {\n <p class=\"ngx-record-editor__row-empty\">{{ ui.text('noRows', language) }}</p>\n }\n </div>\n }\n\n <!--\n Row drawer. Deliberately outside the tab panel: building a row's FormGroup inside the\n tab view races the tab's own initialisation (the reference hit this with FormArray).\n -->\n @if (inlineRowField(); as rowField) {\n <div class=\"ngx-record-editor__drawer\" data-testid=\"row-drawer\" [formGroup]=\"inlineRowForm!\">\n <div class=\"ngx-record-editor__rows-head\">\n <strong>{{ rowDrawerTitle(rowField) }}</strong>\n </div>\n\n <div class=\"ngx-form__panel\">\n @for (child of rowField.children ?? []; track child.id) {\n <ngx-dynamic-field\n [field]=\"child\"\n [control]=\"inlineRowForm!.get(child.id)!\"\n [config]=\"config\"\n [language]=\"language\"\n [readonly]=\"false\"\n [userRoles]=\"userRoles\"\n [style.gridColumn]=\"'span 12'\"\n />\n }\n </div>\n\n <div class=\"ngx-record-editor__section-bar\">\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-row\" (click)=\"cancelRow()\">\n {{ ui.text('cancel', language) }}\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-row\" (click)=\"saveRow()\">\n {{ ui.text('saveRow', language) }}\n </button>\n </div>\n </div>\n }\n\n <!-- Main Dynamic Form -->\n <ngx-dynamic-form\n [config]=\"config\"\n [rules]=\"rules\"\n [initialData]=\"initialData\"\n [originalData]=\"originalBaseline()\"\n [userRoles]=\"userRoles\"\n [language]=\"language\"\n [readonly]=\"sectionReadOnly\"\n [readOnlyFields]=\"readOnlyFields\"\n [layout]=\"layout\"\n [showInfoBanners]=\"false\"\n [loading]=\"loading\"\n [error]=\"error\"\n (formChange)=\"onFormChange($event)\"\n (formSubmit)=\"onFormSubmit($event)\"\n (saveRejected)=\"saveRejected.emit($event)\"\n (formReset)=\"onFormReset()\"\n (activeTabChange)=\"onActiveTabChange($event)\"\n />\n</div>\n", styles: [".ngx-record-editor{display:flex;flex-direction:column;gap:var(--ngx-gap, 16px);background:var(--ngx-color-surface, #ffffff);border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius, 12px);padding:20px}.ngx-record-editor__header{display:flex;align-items:center;justify-content:space-between;gap:14px;padding-bottom:var(--ngx-gap-sm, 16px);border-bottom:1px solid var(--ngx-color-border, #f3f4f6)}.ngx-record-editor__title-group{display:flex;align-items:center;gap:12px;min-width:0}.ngx-record-editor__avatar{width:44px;height:44px;border-radius:50%;object-fit:cover;background:var(--ngx-color-accent-soft, #e0e7ff);color:var(--ngx-color-accent, #4f46e5);border:1px solid var(--ngx-color-border, transparent);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:17px;flex:none}.ngx-record-editor__title{font-size:19px;font-weight:700;color:var(--ngx-color-text, #111827);margin:0;letter-spacing:-.01em}.ngx-record-editor__subtitle{font-size:var(--ngx-hint-size, 13px);color:var(--ngx-color-muted, #6b7280)}.ngx-record-editor__header-toggle{display:flex;align-items:center;gap:8px;font-size:var(--ngx-label-size, 13px);font-weight:var(--ngx-label-weight, 600);color:var(--ngx-color-text, #374151)}.ngx-record-editor__header-toggle-text{font-weight:600}.ngx-record-editor__banner{padding:11px 14px;border-radius:var(--ngx-radius-sm, 8px);font-size:var(--ngx-font-size, 13px);border:1px solid transparent;border-left-width:3px}.ngx-record-editor__banner--info{background:var(--ngx-color-info-soft, #eff6ff);border-color:var(--ngx-color-border, #dbeafe);border-left-color:var(--ngx-color-info, #1d4ed8);color:var(--ngx-color-info, #1d4ed8);display:flex;align-items:center;justify-content:space-between;gap:12px}.ngx-record-editor__banner--error{background:var(--ngx-color-error-soft, #fef2f2);border-color:var(--ngx-color-border, #fecaca);border-left-color:var(--ngx-color-error, #b91c1c);color:var(--ngx-color-error, #b91c1c)}.ngx-record-editor__banner--warning{background:var(--ngx-color-warning-soft, #fffbeb);border-color:var(--ngx-color-border, #fef3c7);border-left-color:var(--ngx-color-warning, #b45309);color:var(--ngx-color-warning, #b45309)}.ngx-record-editor__banner-dismiss{background:none;border:none;cursor:pointer;color:inherit;font-size:14px;line-height:1;padding:2px 6px;border-radius:4px}.ngx-record-editor__banner-dismiss:hover{background:#0000000f}.ngx-record-editor__section-bar{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:8px}.ngx-record-editor__section-hint{margin-right:auto;font-size:var(--ngx-hint-size, 12px);color:var(--ngx-color-muted, #6b7280)}.ngx-record-editor__rows{border:1px solid var(--ngx-color-border, #f3f4f6);border-radius:var(--ngx-radius-sm, 8px);padding:12px 14px;display:flex;flex-direction:column;gap:6px}.ngx-record-editor__rows-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.ngx-record-editor__row{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface-alt, #f9fafb)}.ngx-record-editor__row-text{flex:1;font-size:var(--ngx-font-size, 13px);overflow-wrap:anywhere}.ngx-record-editor__row-btn{background:var(--ngx-color-surface, none);border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px);cursor:pointer;font:inherit;font-size:var(--ngx-hint-size, 12px);font-weight:var(--ngx-label-weight, 600);color:inherit;padding:4px 10px}.ngx-record-editor__row-btn:hover{border-color:var(--ngx-color-accent, #e5e7eb);color:var(--ngx-color-accent, inherit)}.ngx-record-editor__row-empty{font-size:var(--ngx-hint-size, 12px);color:var(--ngx-color-muted, #6b7280);margin:0}.ngx-record-editor__drawer{border:1px solid var(--ngx-color-border, #dbeafe);background:var(--ngx-color-surface-alt, #f8fafc);border-radius:var(--ngx-radius, 8px);padding:16px;display:flex;flex-direction:column;gap:12px}.ngx-record-editor__summary-panel{background:var(--ngx-color-surface-alt, #f9fafb);border:1px solid var(--ngx-color-border, #f3f4f6);border-radius:var(--ngx-radius, 8px);padding:12px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:4px var(--ngx-gap, 12px)}.ngx-record-editor__summary-item{display:flex;flex-direction:column;cursor:pointer;padding:6px 10px;border-radius:var(--ngx-radius-sm, 6px);min-width:0;transition:background .15s}.ngx-record-editor__summary-item:hover{background:var(--ngx-color-accent-soft, #eff6ff)}.ngx-record-editor__summary-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--ngx-color-muted, #6b7280);font-weight:600}.ngx-record-editor__summary-val{font-size:var(--ngx-font-size, 14px);font-weight:600;color:var(--ngx-color-text, #1f2937);overflow-wrap:anywhere}@media(prefers-reduced-motion:reduce){.ngx-record-editor__summary-item{transition:none}}\n"] }]
10009
10036
  }], propDecorators: { config: [{
@@ -10086,7 +10113,10 @@ const defaultSheetParser = async (file) => {
10086
10113
  throw new Error(`Cannot read "${file.name}". The built-in parser handles CSV only — register a ` +
10087
10114
  'sheetParser with provideNgxDynamicEntity({ sheetParser }) to accept other formats.');
10088
10115
  }
10089
- return parseCsv(await file.text());
10116
+ // A `.tsv` says what it is. Anything else is read the way `parseCsv` reads text — the
10117
+ // separator its header line uses most — which is also how the server reads an upload.
10118
+ const tabs = /\.tsv$/i.test(file?.name ?? '');
10119
+ return parseCsv(await file.text(), tabs ? { delimiter: '\t' } : {});
10090
10120
  };
10091
10121
 
10092
10122
  /**
@@ -10134,12 +10164,18 @@ class LocalImportTransport {
10134
10164
  }
10135
10165
  async preview(file, context) {
10136
10166
  const sheet = await this.read(file);
10137
- const { columns } = deriveImportColumns(context.config, { lang: context.lang });
10167
+ // Sized from the headers exactly as the server sizes them, so both offer the same slots.
10168
+ const arrayBound = arrayBoundFor(sheet.headers, context.config, null, context.lang);
10169
+ const { columns } = deriveImportColumns(context.config, { lang: context.lang, maxArrayRows: arrayBound });
10138
10170
  return {
10139
10171
  headers: sheet.headers,
10140
- sample: sheet.rows.slice(0, SAMPLE_ROWS),
10172
+ // Rendered the way the server renders it, so a typed date shows — and previews — as the
10173
+ // calendar date the commit will store, not as a timestamp.
10174
+ sample: sheet.rows.slice(0, SAMPLE_ROWS).map(row => row.map(cellText)),
10141
10175
  rowCount: sheet.rows.length,
10142
10176
  suggestion: suggestMapping(sheet.headers, columns, context.config?.entity ?? ''),
10177
+ arrayBound,
10178
+ ...(sheet.delimiter ? { delimiter: sheet.delimiter } : {}),
10143
10179
  };
10144
10180
  }
10145
10181
  async commit(file, plan, context) {
@@ -10148,6 +10184,8 @@ class LocalImportTransport {
10148
10184
  lang: context.lang,
10149
10185
  rules: context.rules,
10150
10186
  lookups: context.lookups,
10187
+ // A `;` file means `1,5` is one and a half — the same call the server makes.
10188
+ decimal: decimalMarkFor(sheet.delimiter),
10151
10189
  });
10152
10190
  }
10153
10191
  async template(spec, format) {
@@ -10169,10 +10207,10 @@ class LocalImportTransport {
10169
10207
  const csv = toCsv(spec.columns.map(column => column.header));
10170
10208
  return new Blob([csv], { type: 'text/csv;charset=utf-8' });
10171
10209
  }
10172
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LocalImportTransport, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
10173
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LocalImportTransport, providedIn: 'root' });
10210
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: LocalImportTransport, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
10211
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: LocalImportTransport, providedIn: 'root' });
10174
10212
  }
10175
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LocalImportTransport, decorators: [{
10213
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: LocalImportTransport, decorators: [{
10176
10214
  type: Injectable,
10177
10215
  args: [{ providedIn: 'root' }]
10178
10216
  }] });
@@ -10309,6 +10347,9 @@ class HttpImportTransport {
10309
10347
  sample: preview.sample ?? [],
10310
10348
  suggestion: preview.suggestion,
10311
10349
  rowCount: preview.rowCount ?? 0,
10350
+ // A server older than 2.3 does not say, and always sized for the default.
10351
+ arrayBound: preview.arrayBound ?? DEFAULT_ARRAY_ROWS,
10352
+ ...(preview.delimiter ? { delimiter: preview.delimiter } : {}),
10312
10353
  };
10313
10354
  }
10314
10355
  async commit(file, plan, context) {
@@ -10396,8 +10437,8 @@ class ImportErrorsComponent {
10396
10437
  .sort((a, b) => a[0] - b[0])
10397
10438
  .map(([row, problems]) => ({ row, problems }));
10398
10439
  }
10399
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportErrorsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10400
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: ImportErrorsComponent, isStandalone: true, selector: "ngx-import-errors", inputs: { errors: "errors", language: "language" }, ngImport: i0, template: `
10440
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ImportErrorsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10441
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: ImportErrorsComponent, isStandalone: true, selector: "ngx-import-errors", inputs: { errors: "errors", language: "language" }, ngImport: i0, template: `
10401
10442
  @if (errors.length) {
10402
10443
  <section class="ngx-import-errors" data-testid="import-errors">
10403
10444
  <h4 class="ngx-import-errors__heading">{{ ui.text('importErrorsHeading', language) }}</h4>
@@ -10429,7 +10470,7 @@ class ImportErrorsComponent {
10429
10470
  }
10430
10471
  `, isInline: true, styles: [".ngx-import-errors__heading{margin:0 0 8px;font-size:var(--ngx-font-size, 14px);color:var(--ngx-color-error, #b91c1c)}.ngx-import-errors__table{width:100%;border-collapse:collapse}.ngx-import-errors__table th,.ngx-import-errors__table td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--ngx-color-border, #e5e7eb);vertical-align:top}.ngx-import-errors__table thead th{color:var(--ngx-color-muted, #6b7280)}.ngx-import-errors__table tbody th{white-space:nowrap;color:var(--ngx-color-error, #b91c1c)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10431
10472
  }
10432
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportErrorsComponent, decorators: [{
10473
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ImportErrorsComponent, decorators: [{
10433
10474
  type: Component,
10434
10475
  args: [{ selector: 'ngx-import-errors', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [], template: `
10435
10476
  @if (errors.length) {
@@ -10468,6 +10509,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
10468
10509
  type: Input
10469
10510
  }] } });
10470
10511
 
10512
+ /** The select's value for "keep the plan's fixed value", which is not a column. */
10513
+ const CONSTANT = -1;
10471
10514
  /**
10472
10515
  * Match the sheet's columns to the config's fields.
10473
10516
  *
@@ -10497,18 +10540,70 @@ class ImportMapperComponent {
10497
10540
  * attachment column was never going to arrive.
10498
10541
  */
10499
10542
  unsupported = signal(0, ...(ngDevMode ? [{ debugName: "unsupported" }] : /* istanbul ignore next */ []));
10543
+ /** A plan entry whose field this config no longer has. Kept, and emitted, until removed. */
10544
+ stale = signal([], ...(ngDevMode ? [{ debugName: "stale" }] : /* istanbul ignore next */ []));
10545
+ /** Repeating fields the user can add a row to. */
10546
+ arrays = signal([], ...(ngDevMode ? [{ debugName: "arrays" }] : /* istanbul ignore next */ []));
10547
+ constant = CONSTANT;
10548
+ /** Rows added by hand per repeating field, on top of what the sheet and plan imply. */
10549
+ extraSlots = new Map();
10550
+ /** The incoming plan, read through the 2.2 alias. Every entry in it survives `toPlan`. */
10551
+ incoming = null;
10500
10552
  ngOnChanges() {
10501
- const { columns, unsupported } = deriveImportColumns(this.config, { lang: this.language });
10553
+ this.incoming = this.plan ? upgradeLegacyRefs(this.plan, this.config) : null;
10554
+ this.extraSlots.clear();
10555
+ this.rows.set([]);
10556
+ this.rebuild();
10557
+ }
10558
+ /**
10559
+ * Rows for every field, sized so nothing the plan or the sheet names is out of reach.
10560
+ *
10561
+ * Sizing from the default three meant a sheet with `Phone 4` could not be mapped at all,
10562
+ * and a stored plan reaching further lost those entries on the next change — `toPlan` only
10563
+ * ever emitted rows, and there were none for them.
10564
+ */
10565
+ rebuild() {
10566
+ const entries = (this.incoming?.entries ?? []).filter(Boolean);
10567
+ const base = arrayBoundFor(this.headers, this.config, this.incoming, this.language);
10568
+ const extra = Math.max(0, ...this.extraSlots.values());
10569
+ // Readonly and system fields are derived so a plan that maps one keeps it, but only
10570
+ // offered as a row when the plan does — nobody maps a column onto a field the form fills.
10571
+ const { columns, unsupported } = deriveImportColumns(this.config, {
10572
+ lang: this.language,
10573
+ maxArrayRows: base + extra,
10574
+ includeReadonly: true,
10575
+ includeSystemDefault: true,
10576
+ });
10502
10577
  this.unsupported.set(unsupported.length);
10503
- const byRef = new Map((this.plan?.entries ?? []).map(entry => [entry.ref, entry]));
10504
- this.rows.set(columns.map(column => {
10578
+ const byRef = new Map(entries.map(entry => [entry.ref, entry]));
10579
+ const known = new Set(columns.map(column => column.ref));
10580
+ const previous = new Map(this.rows().map(row => [row.column.ref, row]));
10581
+ const offered = columns.filter(column => (byRef.has(column.ref) || (!column.field.readonly && !column.field.systemDefault)) &&
10582
+ (column.arrayRef === undefined ||
10583
+ byRef.has(column.ref) ||
10584
+ column.arrayIndex < base + (this.extraSlots.get(column.arrayRef) ?? 0)));
10585
+ this.rows.set(offered.map(column => {
10586
+ // A choice already made survives a rebuild for an added slot.
10587
+ const kept = previous.get(column.ref);
10588
+ if (kept)
10589
+ return { ...kept, column };
10505
10590
  const entry = byRef.get(column.ref);
10506
10591
  return {
10507
10592
  column,
10508
- source: entry?.column ?? null,
10593
+ source: entry?.column ?? (entry?.constant !== undefined ? CONSTANT : null),
10509
10594
  guessed: entry?.confidence === 'guess',
10595
+ entry,
10596
+ dirty: false,
10510
10597
  };
10511
10598
  }));
10599
+ this.stale.set(entries.filter(entry => !known.has(entry.ref)));
10600
+ const arrays = new Map();
10601
+ for (const column of offered) {
10602
+ if (column.arrayRef !== undefined && !arrays.has(column.arrayRef)) {
10603
+ arrays.set(column.arrayRef, column.arrayLabel ?? column.arrayRef);
10604
+ }
10605
+ }
10606
+ this.arrays.set([...arrays].map(([arrayRef, label]) => ({ arrayRef, label })));
10512
10607
  }
10513
10608
  mappedCount() {
10514
10609
  return this.rows().filter(row => row.source !== null).length;
@@ -10516,32 +10611,83 @@ class ImportMapperComponent {
10516
10611
  missingRequired() {
10517
10612
  return this.rows().filter(row => row.column.required && row.source === null).length;
10518
10613
  }
10614
+ hasConstant(row) {
10615
+ return !!row.entry && row.entry.constant !== undefined && row.entry.column === undefined;
10616
+ }
10617
+ constantText(entry) {
10618
+ const value = entry.constant;
10619
+ return typeof value === 'string' ? value : JSON.stringify(value);
10620
+ }
10519
10621
  choose(row, source) {
10520
10622
  this.rows.update(rows => rows.map(candidate => candidate.column.ref === row.column.ref
10521
- ? // A hand-made choice is not a guess any more, so the badge goes.
10522
- { ...candidate, source, guessed: false }
10623
+ ? // A hand-made choice is not a guess any more, so the badge goes. Going back to
10624
+ // the plan's fixed value is going back to its entry, untouched.
10625
+ { ...candidate, source, guessed: false, dirty: source !== CONSTANT }
10523
10626
  : candidate));
10524
10627
  this.planChange.emit(this.toPlan());
10525
10628
  }
10526
- /** The current selections as the plan that will be applied. */
10629
+ /** One more row for one repeating field, for this session. */
10630
+ addSlot(arrayRef) {
10631
+ this.extraSlots.set(arrayRef, (this.extraSlots.get(arrayRef) ?? 0) + 1);
10632
+ this.rebuild();
10633
+ }
10634
+ removeStale(entry) {
10635
+ this.stale.update(stale => stale.filter(candidate => candidate !== entry));
10636
+ this.planChange.emit(this.toPlan());
10637
+ }
10638
+ /**
10639
+ * The current selections as the plan that will be applied.
10640
+ *
10641
+ * Every entry of the incoming plan survives in its own position — a fixed value, a ref the
10642
+ * config no longer has — unless the user changed or removed it, and an untouched entry is
10643
+ * emitted exactly as it arrived. New selections follow, in field order.
10644
+ */
10527
10645
  toPlan() {
10528
- const entries = this.rows()
10529
- .filter(row => row.source !== null)
10530
- .map(row => ({
10531
- ref: row.column.ref,
10532
- column: row.source,
10533
- header: this.headers[row.source],
10534
- confidence: row.guessed ? 'guess' : 'exact',
10535
- }));
10646
+ const rowByRef = new Map(this.rows().map(row => [row.column.ref, row]));
10647
+ const stale = new Set(this.stale());
10648
+ const emitted = new Set();
10649
+ const entries = [];
10650
+ const entryOf = (row) => {
10651
+ if (!row.dirty && row.entry)
10652
+ return row.entry;
10653
+ if (row.source === null || row.source === CONSTANT)
10654
+ return null;
10655
+ return {
10656
+ ref: row.column.ref,
10657
+ column: row.source,
10658
+ header: this.headers[row.source],
10659
+ confidence: row.guessed ? 'guess' : 'exact',
10660
+ };
10661
+ };
10662
+ for (const original of this.incoming?.entries ?? []) {
10663
+ const row = original ? rowByRef.get(original.ref) : undefined;
10664
+ if (row) {
10665
+ emitted.add(row.column.ref);
10666
+ const entry = entryOf(row);
10667
+ if (entry)
10668
+ entries.push(entry);
10669
+ }
10670
+ else if (stale.has(original)) {
10671
+ entries.push(original);
10672
+ }
10673
+ }
10674
+ for (const row of this.rows()) {
10675
+ if (emitted.has(row.column.ref))
10676
+ continue;
10677
+ const entry = entryOf(row);
10678
+ if (entry)
10679
+ entries.push(entry);
10680
+ }
10536
10681
  return {
10682
+ ...this.incoming,
10537
10683
  entity: this.config?.entity ?? '',
10538
10684
  configVersion: this.config?.version,
10539
10685
  sourceHeaders: [...this.headers],
10540
10686
  entries,
10541
10687
  };
10542
10688
  }
10543
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportMapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10544
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: ImportMapperComponent, isStandalone: true, selector: "ngx-import-mapper", inputs: { config: "config", headers: "headers", plan: "plan", language: "language" }, outputs: { planChange: "planChange" }, usesOnChanges: true, ngImport: i0, template: `
10689
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ImportMapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10690
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: ImportMapperComponent, isStandalone: true, selector: "ngx-import-mapper", inputs: { config: "config", headers: "headers", plan: "plan", language: "language" }, outputs: { planChange: "planChange" }, usesOnChanges: true, ngImport: i0, template: `
10545
10691
  <section class="ngx-import-mapper" data-testid="import-mapper">
10546
10692
  <p class="ngx-import-mapper__summary" data-testid="import-mapped-count">
10547
10693
  {{ ui.text('importMappedCount', language, { mapped: mappedCount(), total: rows().length }) }}
@@ -10582,6 +10728,11 @@ class ImportMapperComponent {
10582
10728
  (ngModelChange)="choose(row, $event)"
10583
10729
  >
10584
10730
  <option [ngValue]="null">{{ ui.text('importUnmapped', language) }}</option>
10731
+ @if (hasConstant(row)) {
10732
+ <option [ngValue]="constant">
10733
+ {{ ui.text('importConstant', language, { value: constantText(row.entry!) }) }}
10734
+ </option>
10735
+ }
10585
10736
  @for (header of headers; track $index) {
10586
10737
  <option [ngValue]="$index">{{ header || '—' }}</option>
10587
10738
  }
@@ -10596,10 +10747,49 @@ class ImportMapperComponent {
10596
10747
  }
10597
10748
  </tbody>
10598
10749
  </table>
10750
+
10751
+ @if (arrays().length) {
10752
+ <p class="ngx-import-mapper__slots">
10753
+ @for (array of arrays(); track array.arrayRef) {
10754
+ <button
10755
+ type="button"
10756
+ class="ngx-import-mapper__add-slot"
10757
+ [attr.data-testid]="'import-add-slot-' + array.arrayRef"
10758
+ (click)="addSlot(array.arrayRef)"
10759
+ >
10760
+ {{ ui.text('importAddSlot', language, { label: array.label }) }}
10761
+ </button>
10762
+ }
10763
+ </p>
10764
+ }
10765
+
10766
+ @if (stale().length) {
10767
+ <div class="ngx-import-mapper__stale" data-testid="import-stale" role="alert">
10768
+ <p>
10769
+ <strong>{{ ui.text('importStaleHeading', language) }}</strong>
10770
+ {{ ui.text('importStaleExplain', language) }}
10771
+ </p>
10772
+ <ul>
10773
+ @for (entry of stale(); track entry.ref) {
10774
+ <li [attr.data-testid]="'import-stale-' + entry.ref">
10775
+ <code>{{ entry.ref }}</code>
10776
+ <button
10777
+ type="button"
10778
+ class="ngx-import-mapper__remove"
10779
+ [attr.data-testid]="'import-remove-' + entry.ref"
10780
+ (click)="removeStale(entry)"
10781
+ >
10782
+ {{ ui.text('importRemoveEntry', language) }}
10783
+ </button>
10784
+ </li>
10785
+ }
10786
+ </ul>
10787
+ </div>
10788
+ }
10599
10789
  </section>
10600
- `, isInline: true, styles: [".ngx-import-mapper__summary,.ngx-import-mapper__warning,.ngx-import-mapper__note{margin:0 0 10px}.ngx-import-mapper__warning{color:var(--ngx-color-warning, #b45309);background:var(--ngx-color-warning-soft, #fffbeb);border:1px solid var(--ngx-color-border, #fef3c7);border-radius:var(--ngx-radius-sm, 6px);padding:8px 12px}.ngx-import-mapper__note{color:var(--ngx-color-muted, #6b7280)}.ngx-import-mapper__table{width:100%;border-collapse:collapse}.ngx-import-mapper__table th,.ngx-import-mapper__table td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--ngx-color-border, #e5e7eb);vertical-align:middle}.ngx-import-mapper__table thead th{color:var(--ngx-color-muted, #6b7280);font-size:var(--ngx-font-size, 13px);text-transform:uppercase;letter-spacing:.04em}.ngx-import-mapper__required{color:var(--ngx-color-error, #b91c1c);margin-left:2px}.ngx-import-mapper__select{min-width:220px;max-width:100%;padding:6px 8px;border:1px solid var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff);color:inherit;font:inherit}.ngx-import-mapper__guess{margin-left:8px;padding:2px 8px;border-radius:999px;font-size:12px;color:var(--ngx-color-info, #1d4ed8);background:var(--ngx-color-info-soft, #eff6ff)}@media(max-width:640px){.ngx-import-mapper__select{min-width:0;width:100%}}\n"], dependencies: [{ 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.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10790
+ `, isInline: true, styles: [".ngx-import-mapper__summary,.ngx-import-mapper__warning,.ngx-import-mapper__note{margin:0 0 10px}.ngx-import-mapper__warning{color:var(--ngx-color-warning, #b45309);background:var(--ngx-color-warning-soft, #fffbeb);border:1px solid var(--ngx-color-border, #fef3c7);border-radius:var(--ngx-radius-sm, 6px);padding:8px 12px}.ngx-import-mapper__note{color:var(--ngx-color-muted, #6b7280)}.ngx-import-mapper__table{width:100%;border-collapse:collapse}.ngx-import-mapper__table th,.ngx-import-mapper__table td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--ngx-color-border, #e5e7eb);vertical-align:middle}.ngx-import-mapper__table thead th{color:var(--ngx-color-muted, #6b7280);font-size:var(--ngx-font-size, 13px);text-transform:uppercase;letter-spacing:.04em}.ngx-import-mapper__required{color:var(--ngx-color-error, #b91c1c);margin-left:2px}.ngx-import-mapper__select{min-width:220px;max-width:100%;padding:6px 8px;border:1px solid var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff);color:inherit;font:inherit}.ngx-import-mapper__slots{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0 0}.ngx-import-mapper__add-slot,.ngx-import-mapper__remove{padding:4px 10px;border:1px solid var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff);color:inherit;font:inherit;cursor:pointer}.ngx-import-mapper__remove{margin-left:8px}.ngx-import-mapper__stale{margin-top:12px;padding:8px 12px;color:var(--ngx-color-error, #b91c1c);background:var(--ngx-color-error-soft, #fef2f2);border-radius:var(--ngx-radius-sm, 6px)}.ngx-import-mapper__stale p,.ngx-import-mapper__stale ul{margin:0 0 6px}.ngx-import-mapper__guess{margin-left:8px;padding:2px 8px;border-radius:999px;font-size:12px;color:var(--ngx-color-info, #1d4ed8);background:var(--ngx-color-info-soft, #eff6ff)}@media(max-width:640px){.ngx-import-mapper__select{min-width:0;width:100%}}\n"], dependencies: [{ 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.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10601
10791
  }
10602
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportMapperComponent, decorators: [{
10792
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ImportMapperComponent, decorators: [{
10603
10793
  type: Component,
10604
10794
  args: [{ selector: 'ngx-import-mapper', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [FormsModule], template: `
10605
10795
  <section class="ngx-import-mapper" data-testid="import-mapper">
@@ -10642,6 +10832,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
10642
10832
  (ngModelChange)="choose(row, $event)"
10643
10833
  >
10644
10834
  <option [ngValue]="null">{{ ui.text('importUnmapped', language) }}</option>
10835
+ @if (hasConstant(row)) {
10836
+ <option [ngValue]="constant">
10837
+ {{ ui.text('importConstant', language, { value: constantText(row.entry!) }) }}
10838
+ </option>
10839
+ }
10645
10840
  @for (header of headers; track $index) {
10646
10841
  <option [ngValue]="$index">{{ header || '—' }}</option>
10647
10842
  }
@@ -10656,8 +10851,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
10656
10851
  }
10657
10852
  </tbody>
10658
10853
  </table>
10854
+
10855
+ @if (arrays().length) {
10856
+ <p class="ngx-import-mapper__slots">
10857
+ @for (array of arrays(); track array.arrayRef) {
10858
+ <button
10859
+ type="button"
10860
+ class="ngx-import-mapper__add-slot"
10861
+ [attr.data-testid]="'import-add-slot-' + array.arrayRef"
10862
+ (click)="addSlot(array.arrayRef)"
10863
+ >
10864
+ {{ ui.text('importAddSlot', language, { label: array.label }) }}
10865
+ </button>
10866
+ }
10867
+ </p>
10868
+ }
10869
+
10870
+ @if (stale().length) {
10871
+ <div class="ngx-import-mapper__stale" data-testid="import-stale" role="alert">
10872
+ <p>
10873
+ <strong>{{ ui.text('importStaleHeading', language) }}</strong>
10874
+ {{ ui.text('importStaleExplain', language) }}
10875
+ </p>
10876
+ <ul>
10877
+ @for (entry of stale(); track entry.ref) {
10878
+ <li [attr.data-testid]="'import-stale-' + entry.ref">
10879
+ <code>{{ entry.ref }}</code>
10880
+ <button
10881
+ type="button"
10882
+ class="ngx-import-mapper__remove"
10883
+ [attr.data-testid]="'import-remove-' + entry.ref"
10884
+ (click)="removeStale(entry)"
10885
+ >
10886
+ {{ ui.text('importRemoveEntry', language) }}
10887
+ </button>
10888
+ </li>
10889
+ }
10890
+ </ul>
10891
+ </div>
10892
+ }
10659
10893
  </section>
10660
- `, styles: [".ngx-import-mapper__summary,.ngx-import-mapper__warning,.ngx-import-mapper__note{margin:0 0 10px}.ngx-import-mapper__warning{color:var(--ngx-color-warning, #b45309);background:var(--ngx-color-warning-soft, #fffbeb);border:1px solid var(--ngx-color-border, #fef3c7);border-radius:var(--ngx-radius-sm, 6px);padding:8px 12px}.ngx-import-mapper__note{color:var(--ngx-color-muted, #6b7280)}.ngx-import-mapper__table{width:100%;border-collapse:collapse}.ngx-import-mapper__table th,.ngx-import-mapper__table td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--ngx-color-border, #e5e7eb);vertical-align:middle}.ngx-import-mapper__table thead th{color:var(--ngx-color-muted, #6b7280);font-size:var(--ngx-font-size, 13px);text-transform:uppercase;letter-spacing:.04em}.ngx-import-mapper__required{color:var(--ngx-color-error, #b91c1c);margin-left:2px}.ngx-import-mapper__select{min-width:220px;max-width:100%;padding:6px 8px;border:1px solid var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff);color:inherit;font:inherit}.ngx-import-mapper__guess{margin-left:8px;padding:2px 8px;border-radius:999px;font-size:12px;color:var(--ngx-color-info, #1d4ed8);background:var(--ngx-color-info-soft, #eff6ff)}@media(max-width:640px){.ngx-import-mapper__select{min-width:0;width:100%}}\n"] }]
10894
+ `, styles: [".ngx-import-mapper__summary,.ngx-import-mapper__warning,.ngx-import-mapper__note{margin:0 0 10px}.ngx-import-mapper__warning{color:var(--ngx-color-warning, #b45309);background:var(--ngx-color-warning-soft, #fffbeb);border:1px solid var(--ngx-color-border, #fef3c7);border-radius:var(--ngx-radius-sm, 6px);padding:8px 12px}.ngx-import-mapper__note{color:var(--ngx-color-muted, #6b7280)}.ngx-import-mapper__table{width:100%;border-collapse:collapse}.ngx-import-mapper__table th,.ngx-import-mapper__table td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--ngx-color-border, #e5e7eb);vertical-align:middle}.ngx-import-mapper__table thead th{color:var(--ngx-color-muted, #6b7280);font-size:var(--ngx-font-size, 13px);text-transform:uppercase;letter-spacing:.04em}.ngx-import-mapper__required{color:var(--ngx-color-error, #b91c1c);margin-left:2px}.ngx-import-mapper__select{min-width:220px;max-width:100%;padding:6px 8px;border:1px solid var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff);color:inherit;font:inherit}.ngx-import-mapper__slots{display:flex;flex-wrap:wrap;gap:8px;margin:10px 0 0}.ngx-import-mapper__add-slot,.ngx-import-mapper__remove{padding:4px 10px;border:1px solid var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff);color:inherit;font:inherit;cursor:pointer}.ngx-import-mapper__remove{margin-left:8px}.ngx-import-mapper__stale{margin-top:12px;padding:8px 12px;color:var(--ngx-color-error, #b91c1c);background:var(--ngx-color-error-soft, #fef2f2);border-radius:var(--ngx-radius-sm, 6px)}.ngx-import-mapper__stale p,.ngx-import-mapper__stale ul{margin:0 0 6px}.ngx-import-mapper__guess{margin-left:8px;padding:2px 8px;border-radius:999px;font-size:12px;color:var(--ngx-color-info, #1d4ed8);background:var(--ngx-color-info-soft, #eff6ff)}@media(max-width:640px){.ngx-import-mapper__select{min-width:0;width:100%}}\n"] }]
10661
10895
  }], propDecorators: { config: [{
10662
10896
  type: Input,
10663
10897
  args: [{ required: true }]
@@ -10702,13 +10936,23 @@ class ImportPreviewComponent {
10702
10936
  /** The same rules the commit will apply; without them a rule-driven rejection is invisible. */
10703
10937
  rules;
10704
10938
  language = 'en';
10939
+ /** The decimal mark number cells use — the one the commit will read them with. */
10940
+ decimal = '.';
10705
10941
  ui = inject(UiTextService);
10706
10942
  /** The columns this plan fills, in the plan's own order. */
10707
10943
  columns = signal([], ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
10708
10944
  grid = signal([], ...(ngDevMode ? [{ debugName: "grid" }] : /* istanbul ignore next */ []));
10709
10945
  ngOnChanges() {
10710
- const byRef = new Map(deriveImportColumns(this.config, { lang: this.language }).columns.map(c => [c.ref, c]));
10711
- const entries = (this.plan?.entries ?? []).filter(entry => byRef.has(entry.ref));
10946
+ // Sized by the plan, so a mapped sixth phone is shown rather than silently left out, and
10947
+ // read through the 2.2 alias, so an old ref shows the field it now means.
10948
+ const plan = upgradeLegacyRefs(this.plan, this.config);
10949
+ const byRef = new Map(deriveImportColumns(this.config, {
10950
+ lang: this.language,
10951
+ maxArrayRows: arrayBoundFor([], this.config, plan),
10952
+ includeReadonly: true,
10953
+ includeSystemDefault: true,
10954
+ }).columns.map(c => [c.ref, c]));
10955
+ const entries = (plan?.entries ?? []).filter(entry => byRef.has(entry.ref));
10712
10956
  this.columns.set(entries.map(entry => byRef.get(entry.ref)));
10713
10957
  // The same engine the commit runs, over the sample only. Row 0 of the sample is numbered
10714
10958
  // 0 here rather than 2, because these row numbers index the preview, not the file.
@@ -10716,6 +10960,7 @@ class ImportPreviewComponent {
10716
10960
  lang: this.language,
10717
10961
  lookups: this.lookups,
10718
10962
  rules: this.rules,
10963
+ decimal: this.decimal,
10719
10964
  stamp: false,
10720
10965
  firstRowNumber: 0,
10721
10966
  });
@@ -10731,6 +10976,7 @@ class ImportPreviewComponent {
10731
10976
  const outcome = coerceCell(field, raw, {
10732
10977
  lang: this.language,
10733
10978
  lookups: this.lookups,
10979
+ decimal: this.decimal,
10734
10980
  });
10735
10981
  if ('error' in outcome)
10736
10982
  return { text: outcome.error, failed: true };
@@ -10741,8 +10987,8 @@ class ImportPreviewComponent {
10741
10987
  }),
10742
10988
  })));
10743
10989
  }
10744
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportPreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10745
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: ImportPreviewComponent, isStandalone: true, selector: "ngx-import-preview", inputs: { config: "config", plan: "plan", rows: "rows", lookups: "lookups", rules: "rules", language: "language" }, usesOnChanges: true, ngImport: i0, template: `
10990
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ImportPreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10991
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: ImportPreviewComponent, isStandalone: true, selector: "ngx-import-preview", inputs: { config: "config", plan: "plan", rows: "rows", lookups: "lookups", rules: "rules", language: "language", decimal: "decimal" }, usesOnChanges: true, ngImport: i0, template: `
10746
10992
  <section class="ngx-import-preview" data-testid="import-preview">
10747
10993
  <h4 class="ngx-import-preview__heading">
10748
10994
  {{ ui.text('importPreviewHeading', language, { count: rows.length }) }}
@@ -10780,7 +11026,7 @@ class ImportPreviewComponent {
10780
11026
  </section>
10781
11027
  `, isInline: true, styles: [".ngx-import-preview__heading{margin:0 0 8px;font-size:var(--ngx-font-size, 14px)}.ngx-import-preview__scroll{overflow-x:auto;border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px)}.ngx-import-preview__table{width:100%;border-collapse:collapse;white-space:nowrap}.ngx-import-preview__table th,.ngx-import-preview__table td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--ngx-color-border, #f3f4f6)}.ngx-import-preview__table thead th{background:var(--ngx-color-surface-alt, #f9fafb);color:var(--ngx-color-muted, #6b7280)}.ngx-import-preview__cell--error{color:var(--ngx-color-error, #b91c1c);background:var(--ngx-color-error-soft, #fef2f2)}.ngx-import-preview__row--rejected td{opacity:.65}.ngx-import-preview__reason td{white-space:normal;color:var(--ngx-color-error, #b91c1c);background:var(--ngx-color-error-soft, #fef2f2)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10782
11028
  }
10783
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportPreviewComponent, decorators: [{
11029
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ImportPreviewComponent, decorators: [{
10784
11030
  type: Component,
10785
11031
  args: [{ selector: 'ngx-import-preview', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [], template: `
10786
11032
  <section class="ngx-import-preview" data-testid="import-preview">
@@ -10833,6 +11079,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
10833
11079
  type: Input
10834
11080
  }], language: [{
10835
11081
  type: Input
11082
+ }], decimal: [{
11083
+ type: Input
10836
11084
  }] } });
10837
11085
 
10838
11086
  /**
@@ -10889,8 +11137,8 @@ class ImportTemplateComponent {
10889
11137
  selectAll(selected) {
10890
11138
  this.choices.update(choices => choices.map(choice => ({ ...choice, selected })));
10891
11139
  }
10892
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportTemplateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
10893
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: ImportTemplateComponent, isStandalone: true, selector: "ngx-import-template", inputs: { config: "config", language: "language" }, outputs: { download: "download" }, usesOnChanges: true, ngImport: i0, template: `
11140
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ImportTemplateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11141
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: ImportTemplateComponent, isStandalone: true, selector: "ngx-import-template", inputs: { config: "config", language: "language" }, outputs: { download: "download" }, usesOnChanges: true, ngImport: i0, template: `
10894
11142
  <section class="ngx-import-template" data-testid="import-template">
10895
11143
  <h4 class="ngx-import-template__heading">{{ ui.text('importTemplateHeading', language) }}</h4>
10896
11144
  <p class="ngx-import-template__explain">{{ ui.text('importTemplateExplain', language) }}</p>
@@ -10941,7 +11189,7 @@ class ImportTemplateComponent {
10941
11189
  </section>
10942
11190
  `, isInline: true, styles: [".ngx-import-template{padding:14px;border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface-alt, #f9fafb)}.ngx-import-template__heading{margin:0 0 4px;font-size:var(--ngx-font-size, 14px)}.ngx-import-template__explain{margin:0 0 10px;color:var(--ngx-color-muted, #6b7280)}.ngx-import-template__bulk{display:flex;gap:8px;margin-bottom:10px}.ngx-import-template__list{list-style:none;margin:0 0 10px;padding:0;max-height:240px;overflow-y:auto;display:grid;gap:4px}.ngx-import-template__item{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px}.ngx-import-template__item label{display:flex;align-items:center;gap:6px;cursor:pointer}.ngx-import-template__note{color:var(--ngx-color-muted, #6b7280);font-size:12px}.ngx-import-template__unsupported{margin:0 0 10px;color:var(--ngx-color-muted, #6b7280)}.ngx-import-template button{padding:6px 12px;border:1px solid var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff);color:inherit;font:inherit;cursor:pointer}.ngx-import-template button:disabled{opacity:.55;cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
10943
11191
  }
10944
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportTemplateComponent, decorators: [{
11192
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: ImportTemplateComponent, decorators: [{
10945
11193
  type: Component,
10946
11194
  args: [{ selector: 'ngx-import-template', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [], template: `
10947
11195
  <section class="ngx-import-template" data-testid="import-template">
@@ -11049,6 +11297,8 @@ class EntityImportComponent {
11049
11297
  headers = signal([], ...(ngDevMode ? [{ debugName: "headers" }] : /* istanbul ignore next */ []));
11050
11298
  sample = signal([], ...(ngDevMode ? [{ debugName: "sample" }] : /* istanbul ignore next */ []));
11051
11299
  rowCount = signal(0, ...(ngDevMode ? [{ debugName: "rowCount" }] : /* istanbul ignore next */ []));
11300
+ /** The separator a delimited file turned out to use, so the user can see how it was read. */
11301
+ delimiter = signal(undefined, ...(ngDevMode ? [{ debugName: "delimiter" }] : /* istanbul ignore next */ []));
11052
11302
  fileName = signal('', ...(ngDevMode ? [{ debugName: "fileName" }] : /* istanbul ignore next */ []));
11053
11303
  plan = signal(null, ...(ngDevMode ? [{ debugName: "plan" }] : /* istanbul ignore next */ []));
11054
11304
  result = signal(null, ...(ngDevMode ? [{ debugName: "result" }] : /* istanbul ignore next */ []));
@@ -11133,6 +11383,7 @@ class EntityImportComponent {
11133
11383
  this.headers.set(preview.headers);
11134
11384
  this.sample.set(preview.sample);
11135
11385
  this.rowCount.set(preview.rowCount);
11386
+ this.delimiter.set(preview.delimiter);
11136
11387
  this.plan.set(preview.suggestion);
11137
11388
  this.step.set('map');
11138
11389
  }
@@ -11216,19 +11467,37 @@ class EntityImportComponent {
11216
11467
  failedRowCount(result) {
11217
11468
  return result.failed ?? new Set(result.errors.map(error => error.row)).size;
11218
11469
  }
11470
+ /** The decimal mark the commit will read numbers with, so the preview reads them the same. */
11471
+ decimal() {
11472
+ return decimalMarkFor(this.delimiter());
11473
+ }
11474
+ /** How the file's columns were split, in words — or nothing for a workbook. */
11475
+ delimiterText() {
11476
+ switch (this.delimiter()) {
11477
+ case ',':
11478
+ return this.ui.text('importDelimiterComma', this.language);
11479
+ case ';':
11480
+ return this.ui.text('importDelimiterSemicolon', this.language);
11481
+ case '\t':
11482
+ return this.ui.text('importDelimiterTab', this.language);
11483
+ default:
11484
+ return null;
11485
+ }
11486
+ }
11219
11487
  restart() {
11220
11488
  this.file = null;
11221
11489
  this.fileName.set('');
11222
11490
  this.headers.set([]);
11223
11491
  this.sample.set([]);
11224
11492
  this.rowCount.set(0);
11493
+ this.delimiter.set(undefined);
11225
11494
  this.plan.set(null);
11226
11495
  this.result.set(null);
11227
11496
  this.problem.set(null);
11228
11497
  this.step.set('upload');
11229
11498
  }
11230
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityImportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11231
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: EntityImportComponent, isStandalone: true, selector: "ngx-entity-import", inputs: { config: "config", rules: "rules", lookups: "lookups", language: "language", templateFormat: "templateFormat" }, outputs: { importComplete: "importComplete", templateReady: "templateReady" }, ngImport: i0, template: `
11499
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityImportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11500
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.25", type: EntityImportComponent, isStandalone: true, selector: "ngx-entity-import", inputs: { config: "config", rules: "rules", lookups: "lookups", language: "language", templateFormat: "templateFormat" }, outputs: { importComplete: "importComplete", templateReady: "templateReady" }, ngImport: i0, template: `
11232
11501
  <div class="ngx-import" data-testid="entity-import">
11233
11502
  <ol class="ngx-import__steps" data-testid="import-steps">
11234
11503
  @for (name of steps; track name) {
@@ -11284,6 +11553,9 @@ class EntityImportComponent {
11284
11553
  }
11285
11554
 
11286
11555
  @case ('review') {
11556
+ @if (delimiterText(); as separator) {
11557
+ <p class="ngx-entity-import__delimiter" data-testid="import-delimiter">{{ separator }}</p>
11558
+ }
11287
11559
  @if (plan(); as current) {
11288
11560
  <ngx-import-preview
11289
11561
  [config]="config"
@@ -11292,6 +11564,7 @@ class EntityImportComponent {
11292
11564
  [lookups]="resolvedLookups()"
11293
11565
  [rules]="rules"
11294
11566
  [language]="language"
11567
+ [decimal]="decimal()"
11295
11568
  />
11296
11569
  }
11297
11570
  <button
@@ -11344,9 +11617,9 @@ class EntityImportComponent {
11344
11617
  }
11345
11618
  }
11346
11619
  </div>
11347
- `, isInline: true, styles: [".ngx-import{display:flex;flex-direction:column;gap:var(--ngx-gap, 16px);color:var(--ngx-color-text, #1f2937);font-size:var(--ngx-font-size, 14px)}.ngx-import__steps{display:flex;flex-wrap:wrap;gap:8px;list-style:none;margin:0;padding:0}.ngx-import__step{padding:4px 12px;border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px);color:var(--ngx-color-muted, #6b7280);background:var(--ngx-color-surface-alt, #f9fafb)}.ngx-import__step--current{color:var(--ngx-color-accent, #4f46e5);border-color:var(--ngx-color-accent, #4f46e5);background:var(--ngx-color-accent-soft, #e0e7ff);font-weight:600}.ngx-import__problem{margin:0;padding:10px 12px;border:1px solid var(--ngx-color-border, #fecaca);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-error-soft, #fef2f2);color:var(--ngx-color-error, #b91c1c);overflow-wrap:anywhere}.ngx-import__file{display:flex;flex-direction:column;gap:6px;padding:14px;border:1px dashed var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff)}.ngx-import button{align-self:flex-start;padding:8px 16px;border:1px solid var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff);color:inherit;font:inherit;cursor:pointer}.ngx-import button:hover:not(:disabled){border-color:var(--ngx-color-accent, #4f46e5);background:var(--ngx-color-accent-soft, #e0e7ff)}.ngx-import button:disabled{opacity:.55;cursor:not-allowed}\n"], dependencies: [{ kind: "component", type: ImportTemplateComponent, selector: "ngx-import-template", inputs: ["config", "language"], outputs: ["download"] }, { kind: "component", type: ImportMapperComponent, selector: "ngx-import-mapper", inputs: ["config", "headers", "plan", "language"], outputs: ["planChange"] }, { kind: "component", type: ImportPreviewComponent, selector: "ngx-import-preview", inputs: ["config", "plan", "rows", "lookups", "rules", "language"] }, { kind: "component", type: ImportErrorsComponent, selector: "ngx-import-errors", inputs: ["errors", "language"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11620
+ `, isInline: true, styles: [".ngx-import{display:flex;flex-direction:column;gap:var(--ngx-gap, 16px);color:var(--ngx-color-text, #1f2937);font-size:var(--ngx-font-size, 14px)}.ngx-import__steps{display:flex;flex-wrap:wrap;gap:8px;list-style:none;margin:0;padding:0}.ngx-import__step{padding:4px 12px;border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px);color:var(--ngx-color-muted, #6b7280);background:var(--ngx-color-surface-alt, #f9fafb)}.ngx-import__step--current{color:var(--ngx-color-accent, #4f46e5);border-color:var(--ngx-color-accent, #4f46e5);background:var(--ngx-color-accent-soft, #e0e7ff);font-weight:600}.ngx-import__problem{margin:0;padding:10px 12px;border:1px solid var(--ngx-color-border, #fecaca);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-error-soft, #fef2f2);color:var(--ngx-color-error, #b91c1c);overflow-wrap:anywhere}.ngx-import__file{display:flex;flex-direction:column;gap:6px;padding:14px;border:1px dashed var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff)}.ngx-import button{align-self:flex-start;padding:8px 16px;border:1px solid var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff);color:inherit;font:inherit;cursor:pointer}.ngx-import button:hover:not(:disabled){border-color:var(--ngx-color-accent, #4f46e5);background:var(--ngx-color-accent-soft, #e0e7ff)}.ngx-import button:disabled{opacity:.55;cursor:not-allowed}\n"], dependencies: [{ kind: "component", type: ImportTemplateComponent, selector: "ngx-import-template", inputs: ["config", "language"], outputs: ["download"] }, { kind: "component", type: ImportMapperComponent, selector: "ngx-import-mapper", inputs: ["config", "headers", "plan", "language"], outputs: ["planChange"] }, { kind: "component", type: ImportPreviewComponent, selector: "ngx-import-preview", inputs: ["config", "plan", "rows", "lookups", "rules", "language", "decimal"] }, { kind: "component", type: ImportErrorsComponent, selector: "ngx-import-errors", inputs: ["errors", "language"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11348
11621
  }
11349
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityImportComponent, decorators: [{
11622
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.25", ngImport: i0, type: EntityImportComponent, decorators: [{
11350
11623
  type: Component,
11351
11624
  args: [{ selector: 'ngx-entity-import', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
11352
11625
  ImportTemplateComponent,
@@ -11409,6 +11682,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
11409
11682
  }
11410
11683
 
11411
11684
  @case ('review') {
11685
+ @if (delimiterText(); as separator) {
11686
+ <p class="ngx-entity-import__delimiter" data-testid="import-delimiter">{{ separator }}</p>
11687
+ }
11412
11688
  @if (plan(); as current) {
11413
11689
  <ngx-import-preview
11414
11690
  [config]="config"
@@ -11417,6 +11693,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImpo
11417
11693
  [lookups]="resolvedLookups()"
11418
11694
  [rules]="rules"
11419
11695
  [language]="language"
11696
+ [decimal]="decimal()"
11420
11697
  />
11421
11698
  }
11422
11699
  <button