ngx-dynamic-entity 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +14 -0
  2. package/fesm2022/ngx-dynamic-entity.mjs +228 -150
  3. package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
  4. package/package.json +6 -7
  5. package/types/ngx-dynamic-entity.d.ts +1719 -0
  6. package/esm2022/lib/field-types/array-field.component.mjs +0 -168
  7. package/esm2022/lib/field-types/boolean-field.component.mjs +0 -101
  8. package/esm2022/lib/field-types/checkbox-field.component.mjs +0 -82
  9. package/esm2022/lib/field-types/currency-field.component.mjs +0 -102
  10. package/esm2022/lib/field-types/date-field.component.mjs +0 -91
  11. package/esm2022/lib/field-types/date-time-field.component.mjs +0 -138
  12. package/esm2022/lib/field-types/dropdown-field.component.mjs +0 -176
  13. package/esm2022/lib/field-types/email-field.component.mjs +0 -102
  14. package/esm2022/lib/field-types/entity-ref-field.component.mjs +0 -195
  15. package/esm2022/lib/field-types/field-component.contract.mjs +0 -2
  16. package/esm2022/lib/field-types/file-field.component.mjs +0 -157
  17. package/esm2022/lib/field-types/group-field.component.mjs +0 -89
  18. package/esm2022/lib/field-types/image-field.component.mjs +0 -169
  19. package/esm2022/lib/field-types/month-year-field.component.mjs +0 -152
  20. package/esm2022/lib/field-types/multi-select-field.component.mjs +0 -146
  21. package/esm2022/lib/field-types/number-field.component.mjs +0 -112
  22. package/esm2022/lib/field-types/password-field.component.mjs +0 -110
  23. package/esm2022/lib/field-types/radio-field.component.mjs +0 -153
  24. package/esm2022/lib/field-types/text-field.component.mjs +0 -121
  25. package/esm2022/lib/field-types/textarea-field.component.mjs +0 -85
  26. package/esm2022/lib/field-types/time-field.component.mjs +0 -96
  27. package/esm2022/lib/form/dynamic-field/dynamic-field.component.mjs +0 -152
  28. package/esm2022/lib/form/dynamic-form.component.mjs +0 -899
  29. package/esm2022/lib/form/dynamic-record-form.component.mjs +0 -531
  30. package/esm2022/lib/providers/provide-field-types.mjs +0 -75
  31. package/esm2022/lib/providers/provide-ngx-dynamic-entity.mjs +0 -27
  32. package/esm2022/lib/services/cascade-data.service.mjs +0 -129
  33. package/esm2022/lib/services/config-source.service.mjs +0 -67
  34. package/esm2022/lib/services/entity-ref-cache.mjs +0 -20
  35. package/esm2022/lib/services/entity-ref-registry.service.mjs +0 -31
  36. package/esm2022/lib/services/entity-ref-selection.service.mjs +0 -27
  37. package/esm2022/lib/services/entity-reference.service.mjs +0 -168
  38. package/esm2022/lib/services/field-registry.service.mjs +0 -55
  39. package/esm2022/lib/services/file-upload.service.mjs +0 -89
  40. package/esm2022/lib/services/hook-registry.service.mjs +0 -26
  41. package/esm2022/lib/services/lookup-registry.service.mjs +0 -246
  42. package/esm2022/lib/services/rbac.service.mjs +0 -46
  43. package/esm2022/lib/services/rules-evaluation.service.mjs +0 -27
  44. package/esm2022/lib/services/validation-messages.service.mjs +0 -56
  45. package/esm2022/lib/services/validator-registry.service.mjs +0 -99
  46. package/esm2022/lib/tokens/injection-tokens.mjs +0 -126
  47. package/esm2022/ngx-dynamic-entity.mjs +0 -5
  48. package/esm2022/public-api.mjs +0 -58
  49. package/index.d.ts +0 -5
  50. package/lib/field-types/array-field.component.d.ts +0 -28
  51. package/lib/field-types/boolean-field.component.d.ts +0 -18
  52. package/lib/field-types/checkbox-field.component.d.ts +0 -13
  53. package/lib/field-types/currency-field.component.d.ts +0 -17
  54. package/lib/field-types/date-field.component.d.ts +0 -15
  55. package/lib/field-types/date-time-field.component.d.ts +0 -30
  56. package/lib/field-types/dropdown-field.component.d.ts +0 -34
  57. package/lib/field-types/email-field.component.d.ts +0 -15
  58. package/lib/field-types/entity-ref-field.component.d.ts +0 -49
  59. package/lib/field-types/field-component.contract.d.ts +0 -49
  60. package/lib/field-types/file-field.component.d.ts +0 -25
  61. package/lib/field-types/group-field.component.d.ts +0 -17
  62. package/lib/field-types/image-field.component.d.ts +0 -30
  63. package/lib/field-types/month-year-field.component.d.ts +0 -28
  64. package/lib/field-types/multi-select-field.component.d.ts +0 -27
  65. package/lib/field-types/number-field.component.d.ts +0 -16
  66. package/lib/field-types/password-field.component.d.ts +0 -17
  67. package/lib/field-types/radio-field.component.d.ts +0 -33
  68. package/lib/field-types/text-field.component.d.ts +0 -23
  69. package/lib/field-types/textarea-field.component.d.ts +0 -14
  70. package/lib/field-types/time-field.component.d.ts +0 -27
  71. package/lib/form/dynamic-field/dynamic-field.component.d.ts +0 -78
  72. package/lib/form/dynamic-form.component.d.ts +0 -293
  73. package/lib/form/dynamic-record-form.component.d.ts +0 -199
  74. package/lib/providers/provide-field-types.d.ts +0 -24
  75. package/lib/providers/provide-ngx-dynamic-entity.d.ts +0 -56
  76. package/lib/services/cascade-data.service.d.ts +0 -45
  77. package/lib/services/config-source.service.d.ts +0 -14
  78. package/lib/services/entity-ref-cache.d.ts +0 -27
  79. package/lib/services/entity-ref-registry.service.d.ts +0 -21
  80. package/lib/services/entity-ref-selection.service.d.ts +0 -24
  81. package/lib/services/entity-reference.service.d.ts +0 -49
  82. package/lib/services/field-registry.service.d.ts +0 -32
  83. package/lib/services/file-upload.service.d.ts +0 -27
  84. package/lib/services/hook-registry.service.d.ts +0 -12
  85. package/lib/services/lookup-registry.service.d.ts +0 -94
  86. package/lib/services/rbac.service.d.ts +0 -27
  87. package/lib/services/rules-evaluation.service.d.ts +0 -17
  88. package/lib/services/validation-messages.service.d.ts +0 -43
  89. package/lib/services/validator-registry.service.d.ts +0 -32
  90. package/lib/tokens/injection-tokens.d.ts +0 -134
  91. package/public-api.d.ts +0 -42
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { InjectionToken, makeEnvironmentProviders, inject, Injectable, signal, Input, ChangeDetectionStrategy, Component, DestroyRef, Injector, isDevMode, ViewContainerRef, ViewChild, forwardRef, ChangeDetectorRef, Optional, Inject, EventEmitter, computed, HostListener, ViewChildren, Output, ElementRef, afterNextRender } from '@angular/core';
3
3
  import * as i1 from '@angular/forms';
4
4
  import { ReactiveFormsModule, Validators, FormBuilder, FormArray, FormGroup } from '@angular/forms';
5
- import { resolveLabel, formatDisplayValue, normalizeLookupValues, lookupValuesToOptions, valuesMatch, getOptionStoredValue, resolveOptionLabel, resolveOptionValue, buildReferenceCacheKey, normalizeReferenceOptions, entityKeyFromCacheKey, applyCascadeFilter, resolveEffectiveMask, shouldMaskField, findTab, fileRefName, evaluateFormRules, filterRulesForTab, evaluateFieldVisibility, normalizeConfigOptions, migrateRecord, getTabData, getValueByPath, setTabData, setValueByPath, normalizeArrayStructures, getTabPath, applyAutoPatch, applyPatchOnTrue } from '@dynamic-entity/core';
5
+ import { resolveLabel, formatDisplayValue, normalizeLookupValues, lookupValuesToOptions, valuesMatch, getOptionStoredValue, resolveOptionLabel, resolveOptionValue, buildReferenceCacheKey, normalizeReferenceOptions, entityKeyFromCacheKey, applyCascadeFilter, resolveEffectiveMask, shouldMaskField, findTab, fileRefName, evaluateFormRules, filterRulesForTab, evaluateFieldVisibility, normalizeConfigOptions, migrateRecord, getTabData, getValueByPath, setTabData, setValueByPath, normalizeArrayStructures, ambiguousFieldIds, toRefToken, collectFieldScopes, fieldRefFor, refOf, getTabPath, applyAutoPatch, applyPatchOnTrue } 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';
@@ -223,10 +223,10 @@ class ValidationMessagesService {
223
223
  return '';
224
224
  return typeof message === 'function' ? message(language, error) : message;
225
225
  }
226
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ValidationMessagesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
227
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ValidationMessagesService, providedIn: 'root' });
226
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ValidationMessagesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
227
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ValidationMessagesService, providedIn: 'root' });
228
228
  }
229
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ValidationMessagesService, decorators: [{
229
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ValidationMessagesService, decorators: [{
230
230
  type: Injectable,
231
231
  args: [{ providedIn: 'root' }]
232
232
  }] });
@@ -249,7 +249,7 @@ class TextFieldComponent {
249
249
  readonly = false;
250
250
  masked = false;
251
251
  /** Readonly markup reads this signal so an external patch (autoPatch) is visible under OnPush. */
252
- displayValue = signal(null);
252
+ displayValue = signal(null, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
253
253
  ngOnDestroy() {
254
254
  this.displaySub?.unsubscribe();
255
255
  }
@@ -264,8 +264,8 @@ class TextFieldComponent {
264
264
  return '';
265
265
  return this.messages.resolve(this.control.errors, this.language, ['required', 'email', 'minlength', 'maxlength', 'pattern']);
266
266
  }
267
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TextFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
268
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: TextFieldComponent, isStandalone: true, selector: "ngx-text-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
267
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: TextFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
268
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: TextFieldComponent, isStandalone: true, selector: "ngx-text-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
269
269
  <div
270
270
  class="ngx-field ngx-field--text"
271
271
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
@@ -296,7 +296,7 @@ class TextFieldComponent {
296
296
  </div>
297
297
  `, 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 });
298
298
  }
299
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TextFieldComponent, decorators: [{
299
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: TextFieldComponent, decorators: [{
300
300
  type: Component,
301
301
  args: [{
302
302
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -358,8 +358,8 @@ class TextareaFieldComponent {
358
358
  get placeholder() {
359
359
  return resolveLabel(this.field?.placeholder, this.language);
360
360
  }
361
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TextareaFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
362
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: TextareaFieldComponent, isStandalone: true, selector: "ngx-textarea-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
361
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: TextareaFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
362
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: TextareaFieldComponent, isStandalone: true, selector: "ngx-textarea-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
363
363
  <div class="ngx-field ngx-field--textarea"
364
364
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
365
365
  <label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
@@ -383,7 +383,7 @@ class TextareaFieldComponent {
383
383
  </div>
384
384
  `, 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 });
385
385
  }
386
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TextareaFieldComponent, decorators: [{
386
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: TextareaFieldComponent, decorators: [{
387
387
  type: Component,
388
388
  args: [{
389
389
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -444,8 +444,8 @@ class NumberFieldComponent {
444
444
  return '';
445
445
  return this.messages.resolve(this.control.errors, this.language, ['required', 'min', 'max'], 'invalidNumber');
446
446
  }
447
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NumberFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
448
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: NumberFieldComponent, isStandalone: true, selector: "ngx-number-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
447
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NumberFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
448
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: NumberFieldComponent, isStandalone: true, selector: "ngx-number-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
449
449
  <div
450
450
  class="ngx-field ngx-field--number"
451
451
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
@@ -479,7 +479,7 @@ class NumberFieldComponent {
479
479
  </div>
480
480
  `, 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 });
481
481
  }
482
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NumberFieldComponent, decorators: [{
482
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: NumberFieldComponent, decorators: [{
483
483
  type: Component,
484
484
  args: [{
485
485
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -555,8 +555,8 @@ class CurrencyFieldComponent {
555
555
  return '$';
556
556
  }
557
557
  }
558
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CurrencyFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
559
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: CurrencyFieldComponent, isStandalone: true, selector: "ngx-currency-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
558
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: CurrencyFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
559
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: CurrencyFieldComponent, isStandalone: true, selector: "ngx-currency-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
560
560
  <div class="ngx-field ngx-field--currency"
561
561
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
562
562
  <label class="ngx-field__label">{{ label }}</label>
@@ -583,7 +583,7 @@ class CurrencyFieldComponent {
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.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 });
585
585
  }
586
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CurrencyFieldComponent, decorators: [{
586
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: CurrencyFieldComponent, decorators: [{
587
587
  type: Component,
588
588
  args: [{
589
589
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -642,8 +642,8 @@ class EmailFieldComponent {
642
642
  get placeholder() {
643
643
  return resolveLabel(this.field?.placeholder, this.language);
644
644
  }
645
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EmailFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
646
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EmailFieldComponent, isStandalone: true, selector: "ngx-email-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
645
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EmailFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
646
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: EmailFieldComponent, isStandalone: true, selector: "ngx-email-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
647
647
  <div class="ngx-field ngx-field--email"
648
648
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
649
649
  <label class="ngx-field__label">{{ label }}</label>
@@ -675,7 +675,7 @@ class EmailFieldComponent {
675
675
  </div>
676
676
  `, 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 });
677
677
  }
678
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EmailFieldComponent, decorators: [{
678
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EmailFieldComponent, decorators: [{
679
679
  type: Component,
680
680
  args: [{
681
681
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -733,7 +733,7 @@ class PasswordFieldComponent {
733
733
  language = 'en';
734
734
  readonly = false;
735
735
  masked = false;
736
- visible = signal(false);
736
+ visible = signal(false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
737
737
  get label() {
738
738
  return resolveLabel(this.field?.label, this.language);
739
739
  }
@@ -743,8 +743,8 @@ class PasswordFieldComponent {
743
743
  toggleVisibility() {
744
744
  this.visible.update(v => !v);
745
745
  }
746
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PasswordFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
747
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: PasswordFieldComponent, isStandalone: true, selector: "ngx-password-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
746
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: PasswordFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
747
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: PasswordFieldComponent, isStandalone: true, selector: "ngx-password-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
748
748
  <div class="ngx-field ngx-field--password"
749
749
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
750
750
  <label class="ngx-field__label">{{ label }}</label>
@@ -778,7 +778,7 @@ class PasswordFieldComponent {
778
778
  </div>
779
779
  `, 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 });
780
780
  }
781
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PasswordFieldComponent, decorators: [{
781
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: PasswordFieldComponent, decorators: [{
782
782
  type: Component,
783
783
  args: [{
784
784
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -840,8 +840,8 @@ class CheckboxFieldComponent {
840
840
  get label() {
841
841
  return resolveLabel(this.field?.label, this.language);
842
842
  }
843
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
844
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: CheckboxFieldComponent, isStandalone: true, selector: "ngx-checkbox-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
843
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: CheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
844
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: CheckboxFieldComponent, isStandalone: true, selector: "ngx-checkbox-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
845
845
  <div class="ngx-field ngx-field--checkbox"
846
846
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
847
847
  @if (masked) {
@@ -865,7 +865,7 @@ class CheckboxFieldComponent {
865
865
  </div>
866
866
  `, 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 });
867
867
  }
868
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CheckboxFieldComponent, decorators: [{
868
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: CheckboxFieldComponent, decorators: [{
869
869
  type: Component,
870
870
  args: [{
871
871
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -922,8 +922,8 @@ class BooleanFieldComponent {
922
922
  get label() {
923
923
  return resolveLabel(this.field?.label, this.language);
924
924
  }
925
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BooleanFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
926
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: BooleanFieldComponent, isStandalone: true, selector: "ngx-boolean-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
925
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: BooleanFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
926
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: BooleanFieldComponent, isStandalone: true, selector: "ngx-boolean-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
927
927
  <div class="ngx-field ngx-field--boolean"
928
928
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
929
929
  @if (masked) {
@@ -954,7 +954,7 @@ class BooleanFieldComponent {
954
954
  </div>
955
955
  `, 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 });
956
956
  }
957
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BooleanFieldComponent, decorators: [{
957
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: BooleanFieldComponent, decorators: [{
958
958
  type: Component,
959
959
  args: [{
960
960
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -1024,8 +1024,8 @@ class DateFieldComponent {
1024
1024
  return value;
1025
1025
  }
1026
1026
  }
1027
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DateFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1028
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: DateFieldComponent, isStandalone: true, selector: "ngx-date-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1027
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DateFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1028
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: DateFieldComponent, isStandalone: true, selector: "ngx-date-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1029
1029
  <div class="ngx-field ngx-field--date"
1030
1030
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
1031
1031
  <label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
@@ -1048,7 +1048,7 @@ class DateFieldComponent {
1048
1048
  </div>
1049
1049
  `, 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 });
1050
1050
  }
1051
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DateFieldComponent, decorators: [{
1051
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DateFieldComponent, decorators: [{
1052
1052
  type: Component,
1053
1053
  args: [{
1054
1054
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -1156,8 +1156,8 @@ class DateTimeFieldComponent {
1156
1156
  const d = parseStored(value);
1157
1157
  return d ? d.toLocaleString() : '—';
1158
1158
  }
1159
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DateTimeFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1160
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: DateTimeFieldComponent, isStandalone: true, selector: "ngx-date-time-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1159
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DateTimeFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1160
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: DateTimeFieldComponent, isStandalone: true, selector: "ngx-date-time-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1161
1161
  <div class="ngx-field ngx-field--datetime"
1162
1162
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
1163
1163
  <label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
@@ -1181,7 +1181,7 @@ class DateTimeFieldComponent {
1181
1181
  </div>
1182
1182
  `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1183
1183
  }
1184
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DateTimeFieldComponent, decorators: [{
1184
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DateTimeFieldComponent, decorators: [{
1185
1185
  type: Component,
1186
1186
  args: [{
1187
1187
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -1249,8 +1249,8 @@ class TimeFieldComponent {
1249
1249
  formatTime(value) {
1250
1250
  return formatDisplayValue('time', undefined, value, this.language);
1251
1251
  }
1252
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TimeFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1253
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: TimeFieldComponent, isStandalone: true, selector: "ngx-time-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1252
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: TimeFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1253
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: TimeFieldComponent, isStandalone: true, selector: "ngx-time-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1254
1254
  <div class="ngx-field ngx-field--time"
1255
1255
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
1256
1256
  <label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
@@ -1273,7 +1273,7 @@ class TimeFieldComponent {
1273
1273
  </div>
1274
1274
  `, 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 });
1275
1275
  }
1276
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TimeFieldComponent, decorators: [{
1276
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: TimeFieldComponent, decorators: [{
1277
1277
  type: Component,
1278
1278
  args: [{
1279
1279
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -1362,8 +1362,8 @@ class MonthYearFieldComponent {
1362
1362
  const monthName = MONTH_NAMES[parseInt(monthNum, 10) - 1];
1363
1363
  return monthName && year ? `${monthName} ${year}` : String(value);
1364
1364
  }
1365
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MonthYearFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1366
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: MonthYearFieldComponent, isStandalone: true, selector: "ngx-month-year-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1365
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MonthYearFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1366
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: MonthYearFieldComponent, isStandalone: true, selector: "ngx-month-year-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1367
1367
  <div class="ngx-field ngx-field--month-year"
1368
1368
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
1369
1369
  <label class="ngx-field__label">{{ label }}</label>
@@ -1403,7 +1403,7 @@ class MonthYearFieldComponent {
1403
1403
  </div>
1404
1404
  `, 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 });
1405
1405
  }
1406
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MonthYearFieldComponent, decorators: [{
1406
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MonthYearFieldComponent, decorators: [{
1407
1407
  type: Component,
1408
1408
  args: [{
1409
1409
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -1665,10 +1665,10 @@ class LookupRegistryService {
1665
1665
  value !== null &&
1666
1666
  typeof value.subscribe === 'function');
1667
1667
  }
1668
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: LookupRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1669
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: LookupRegistryService, providedIn: 'root' });
1668
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: LookupRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1669
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: LookupRegistryService, providedIn: 'root' });
1670
1670
  }
1671
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: LookupRegistryService, decorators: [{
1671
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: LookupRegistryService, decorators: [{
1672
1672
  type: Injectable,
1673
1673
  args: [{ providedIn: 'root' }]
1674
1674
  }] });
@@ -1728,7 +1728,7 @@ class DropdownFieldComponent {
1728
1728
  readonly = false;
1729
1729
  masked = false;
1730
1730
  /** Inline `options`, or the field's named list resolved through the registry (§6.3). */
1731
- options = signal([]);
1731
+ options = signal([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
1732
1732
  compareFn = (o1, o2) => valuesMatch(o1, o2, this.language);
1733
1733
  isObjectVal(option) {
1734
1734
  const val = getOptionStoredValue(option);
@@ -1760,7 +1760,7 @@ class DropdownFieldComponent {
1760
1760
  const cached = this.lookups.labelFor(this.field?.listName, value, this.language);
1761
1761
  if (cached)
1762
1762
  return cached;
1763
- return typeof value === 'object' ? resolveLabel(value, this.language) : (value ?? '—');
1763
+ return typeof value === 'object' ? resolveLabel(value, this.language) : String(value ?? '—');
1764
1764
  }
1765
1765
  get errorMessage() {
1766
1766
  if (!this.control || !this.control.errors || !this.control.touched)
@@ -1769,8 +1769,8 @@ class DropdownFieldComponent {
1769
1769
  // dropdown than "This field is required", and both stay independently overridable.
1770
1770
  return this.messages.resolve(this.control.errors, this.language, [['required', 'requiredSelection']], 'invalidSelection');
1771
1771
  }
1772
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DropdownFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1773
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: DropdownFieldComponent, isStandalone: true, selector: "ngx-dropdown-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1772
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DropdownFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1773
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: DropdownFieldComponent, isStandalone: true, selector: "ngx-dropdown-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1774
1774
  <div
1775
1775
  class="ngx-field ngx-field--dropdown"
1776
1776
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
@@ -1812,7 +1812,7 @@ class DropdownFieldComponent {
1812
1812
  </div>
1813
1813
  `, 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 });
1814
1814
  }
1815
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DropdownFieldComponent, decorators: [{
1815
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DropdownFieldComponent, decorators: [{
1816
1816
  type: Component,
1817
1817
  args: [{
1818
1818
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -1897,7 +1897,7 @@ class RadioFieldComponent {
1897
1897
  readonly = false;
1898
1898
  masked = false;
1899
1899
  /** Inline `options`, or the field's named list resolved through the registry (§6.3). */
1900
- options = signal([]);
1900
+ options = signal([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
1901
1901
  get label() {
1902
1902
  return resolveLabel(this.field?.label, this.language);
1903
1903
  }
@@ -1932,10 +1932,10 @@ class RadioFieldComponent {
1932
1932
  const cached = this.lookups.labelFor(this.field?.listName, value, this.language);
1933
1933
  if (cached)
1934
1934
  return cached;
1935
- return typeof value === 'object' ? resolveLabel(value, this.language) : (value ?? '—');
1935
+ return typeof value === 'object' ? resolveLabel(value, this.language) : String(value ?? '—');
1936
1936
  }
1937
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RadioFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1938
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: RadioFieldComponent, isStandalone: true, selector: "ngx-radio-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1937
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RadioFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1938
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: RadioFieldComponent, isStandalone: true, selector: "ngx-radio-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
1939
1939
  <div class="ngx-field ngx-field--radio"
1940
1940
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
1941
1941
  <fieldset class="ngx-field__fieldset">
@@ -1968,7 +1968,7 @@ class RadioFieldComponent {
1968
1968
  </div>
1969
1969
  `, 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.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { 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 });
1970
1970
  }
1971
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RadioFieldComponent, decorators: [{
1971
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RadioFieldComponent, decorators: [{
1972
1972
  type: Component,
1973
1973
  args: [{
1974
1974
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -2043,7 +2043,7 @@ class MultiSelectFieldComponent {
2043
2043
  readonly = false;
2044
2044
  masked = false;
2045
2045
  /** Inline `options`, or the field's named list resolved through the registry (§6.3). */
2046
- options = signal([]);
2046
+ options = signal([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
2047
2047
  compareFn = (o1, o2) => valuesMatch(o1, o2, this.language);
2048
2048
  isObjectVal(option) {
2049
2049
  const val = getOptionStoredValue(option);
@@ -2070,12 +2070,12 @@ class MultiSelectFieldComponent {
2070
2070
  const cached = this.lookups.labelFor(this.field?.listName, v, this.language);
2071
2071
  if (cached)
2072
2072
  return cached;
2073
- return typeof v === 'object' ? resolveLabel(v, this.language) : String(v);
2073
+ return typeof v === 'object' && v !== null ? resolveLabel(v, this.language) : String(v);
2074
2074
  })
2075
2075
  .join(', ');
2076
2076
  }
2077
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MultiSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2078
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: MultiSelectFieldComponent, isStandalone: true, selector: "ngx-multi-select-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
2077
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MultiSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2078
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: MultiSelectFieldComponent, isStandalone: true, selector: "ngx-multi-select-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
2079
2079
  <div class="ngx-field ngx-field--multiSelect"
2080
2080
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
2081
2081
  <label class="ngx-field__label">{{ label }}</label>
@@ -2108,7 +2108,7 @@ class MultiSelectFieldComponent {
2108
2108
  </div>
2109
2109
  `, 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 });
2110
2110
  }
2111
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MultiSelectFieldComponent, decorators: [{
2111
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MultiSelectFieldComponent, decorators: [{
2112
2112
  type: Component,
2113
2113
  args: [{
2114
2114
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -2180,10 +2180,10 @@ class EntityRefRegistryService {
2180
2180
  has(entityKey) {
2181
2181
  return this.registry.has(entityKey);
2182
2182
  }
2183
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityRefRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2184
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityRefRegistryService, providedIn: 'root' });
2183
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityRefRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2184
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityRefRegistryService, providedIn: 'root' });
2185
2185
  }
2186
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityRefRegistryService, decorators: [{
2186
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityRefRegistryService, decorators: [{
2187
2187
  type: Injectable,
2188
2188
  args: [{ providedIn: 'root' }]
2189
2189
  }] });
@@ -2343,10 +2343,10 @@ class EntityReferenceService {
2343
2343
  matches(a, b, lang = 'en') {
2344
2344
  return valuesMatch(a, b, lang);
2345
2345
  }
2346
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityReferenceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2347
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityReferenceService, providedIn: 'root' });
2346
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityReferenceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2347
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityReferenceService, providedIn: 'root' });
2348
2348
  }
2349
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityReferenceService, decorators: [{
2349
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityReferenceService, decorators: [{
2350
2350
  type: Injectable,
2351
2351
  args: [{ providedIn: 'root' }]
2352
2352
  }] });
@@ -2467,10 +2467,10 @@ class CascadeDataService {
2467
2467
  walkTabs(config?.tabs);
2468
2468
  return found;
2469
2469
  }
2470
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CascadeDataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2471
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CascadeDataService, providedIn: 'root' });
2470
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: CascadeDataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2471
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: CascadeDataService, providedIn: 'root' });
2472
2472
  }
2473
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CascadeDataService, decorators: [{
2473
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: CascadeDataService, decorators: [{
2474
2474
  type: Injectable,
2475
2475
  args: [{ providedIn: 'root' }]
2476
2476
  }] });
@@ -2491,10 +2491,10 @@ class EntityRefSelectionService {
2491
2491
  emit(fieldId, option) {
2492
2492
  this.subject.next({ fieldId, option });
2493
2493
  }
2494
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityRefSelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2495
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityRefSelectionService, providedIn: 'root' });
2494
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityRefSelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2495
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityRefSelectionService, providedIn: 'root' });
2496
2496
  }
2497
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityRefSelectionService, decorators: [{
2497
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityRefSelectionService, decorators: [{
2498
2498
  type: Injectable,
2499
2499
  args: [{ providedIn: 'root' }]
2500
2500
  }] });
@@ -2516,10 +2516,10 @@ class EntityRefFieldComponent {
2516
2516
  cascade = inject(CascadeDataService);
2517
2517
  selectionBus = inject(EntityRefSelectionService);
2518
2518
  destroyRef = inject(DestroyRef);
2519
- options = signal([]);
2520
- loading = signal(false);
2519
+ options = signal([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
2520
+ loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
2521
2521
  /** True when this field cascades but its parent has no value yet. */
2522
- awaitingParent = signal(false);
2522
+ awaitingParent = signal(false, ...(ngDevMode ? [{ debugName: "awaitingParent" }] : /* istanbul ignore next */ []));
2523
2523
  get label() {
2524
2524
  return resolveLabel(this.field?.label, this.language);
2525
2525
  }
@@ -2598,8 +2598,8 @@ class EntityRefFieldComponent {
2598
2598
  void this.reload();
2599
2599
  });
2600
2600
  }
2601
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityRefFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2602
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EntityRefFieldComponent, isStandalone: true, selector: "ngx-entity-ref-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
2601
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityRefFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2602
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: EntityRefFieldComponent, isStandalone: true, selector: "ngx-entity-ref-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
2603
2603
  <div class="ngx-field ngx-field--entity-ref"
2604
2604
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
2605
2605
  <label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
@@ -2633,7 +2633,7 @@ class EntityRefFieldComponent {
2633
2633
  </div>
2634
2634
  `, 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 });
2635
2635
  }
2636
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityRefFieldComponent, decorators: [{
2636
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityRefFieldComponent, decorators: [{
2637
2637
  type: Component,
2638
2638
  args: [{
2639
2639
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -2730,10 +2730,10 @@ class FieldRegistryService {
2730
2730
  registeredTypes() {
2731
2731
  return [...new Set([...this.dynamicRegistry.keys(), ...this.consumerRegistry.keys()])].sort();
2732
2732
  }
2733
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FieldRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2734
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FieldRegistryService, providedIn: 'root' });
2733
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2734
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldRegistryService, providedIn: 'root' });
2735
2735
  }
2736
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FieldRegistryService, decorators: [{
2736
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldRegistryService, decorators: [{
2737
2737
  type: Injectable,
2738
2738
  args: [{ providedIn: 'root' }]
2739
2739
  }], ctorParameters: () => [] });
@@ -2772,10 +2772,10 @@ class RbacService {
2772
2772
  canDelete: this.hasPermission(userRoles, config.permissions?.delete),
2773
2773
  };
2774
2774
  }
2775
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RbacService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2776
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RbacService, providedIn: 'root' });
2775
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RbacService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2776
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RbacService, providedIn: 'root' });
2777
2777
  }
2778
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RbacService, decorators: [{
2778
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RbacService, decorators: [{
2779
2779
  type: Injectable,
2780
2780
  args: [{ providedIn: 'root' }]
2781
2781
  }] });
@@ -2897,10 +2897,10 @@ class DynamicFieldComponent {
2897
2897
  ref.setInput('readonly', this.readonly || !!this.field.readonly);
2898
2898
  ref.setInput('masked', masked);
2899
2899
  }
2900
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DynamicFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2901
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", 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, changeDetection: i0.ChangeDetectionStrategy.OnPush });
2900
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2901
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.22", 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, changeDetection: i0.ChangeDetectionStrategy.OnPush });
2902
2902
  }
2903
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DynamicFieldComponent, decorators: [{
2903
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFieldComponent, decorators: [{
2904
2904
  type: Component,
2905
2905
  args: [{
2906
2906
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -2943,8 +2943,8 @@ class GroupFieldComponent {
2943
2943
  const group = this.control;
2944
2944
  return group?.get(childId);
2945
2945
  }
2946
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: GroupFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2947
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", 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: `
2946
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: GroupFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2947
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", 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: `
2948
2948
  <fieldset class="ngx-field ngx-field--group"
2949
2949
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
2950
2950
  <legend class="ngx-field__legend">{{ label }}</legend>
@@ -2969,7 +2969,7 @@ class GroupFieldComponent {
2969
2969
  </fieldset>
2970
2970
  `, 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 });
2971
2971
  }
2972
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: GroupFieldComponent, decorators: [{
2972
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: GroupFieldComponent, decorators: [{
2973
2973
  type: Component,
2974
2974
  args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-group-field', standalone: true, imports: [ReactiveFormsModule, forwardRef(() => DynamicFieldComponent)], template: `
2975
2975
  <fieldset class="ngx-field ngx-field--group"
@@ -3098,10 +3098,10 @@ class ValidatorRegistryService {
3098
3098
  }
3099
3099
  return out;
3100
3100
  }
3101
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ValidatorRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3102
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ValidatorRegistryService, providedIn: 'root' });
3101
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ValidatorRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3102
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ValidatorRegistryService, providedIn: 'root' });
3103
3103
  }
3104
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ValidatorRegistryService, decorators: [{
3104
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ValidatorRegistryService, decorators: [{
3105
3105
  type: Injectable,
3106
3106
  args: [{ providedIn: 'root' }]
3107
3107
  }] });
@@ -3146,8 +3146,8 @@ class ArrayFieldComponent {
3146
3146
  this.formArray.removeAt(index);
3147
3147
  this.cdr.markForCheck();
3148
3148
  }
3149
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3150
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", 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: `
3149
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3150
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", 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: `
3151
3151
  <div class="ngx-field ngx-field--array"
3152
3152
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
3153
3153
  <div class="ngx-field__array-header">
@@ -3199,7 +3199,7 @@ class ArrayFieldComponent {
3199
3199
  </div>
3200
3200
  `, 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 });
3201
3201
  }
3202
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ArrayFieldComponent, decorators: [{
3202
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ArrayFieldComponent, decorators: [{
3203
3203
  type: Component,
3204
3204
  args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-array-field', standalone: true, imports: [ReactiveFormsModule, forwardRef(() => DynamicFieldComponent)], template: `
3205
3205
  <div class="ngx-field ngx-field--array"
@@ -3346,10 +3346,10 @@ class FileUploadService {
3346
3346
  value !== null &&
3347
3347
  typeof value.subscribe === 'function');
3348
3348
  }
3349
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FileUploadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3350
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FileUploadService, providedIn: 'root' });
3349
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FileUploadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3350
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FileUploadService, providedIn: 'root' });
3351
3351
  }
3352
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FileUploadService, decorators: [{
3352
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FileUploadService, decorators: [{
3353
3353
  type: Injectable,
3354
3354
  args: [{ providedIn: 'root' }]
3355
3355
  }] });
@@ -3367,8 +3367,8 @@ class ImageFieldComponent {
3367
3367
  readonly = false;
3368
3368
  masked = false;
3369
3369
  uploads = inject(FileUploadService);
3370
- uploading = signal(false);
3371
- uploadError = signal(null);
3370
+ uploading = signal(false, ...(ngDevMode ? [{ debugName: "uploading" }] : /* istanbul ignore next */ []));
3371
+ uploadError = signal(null, ...(ngDevMode ? [{ debugName: "uploadError" }] : /* istanbul ignore next */ []));
3372
3372
  /** Cached so an object URL is minted once per File, not once per change-detection pass. */
3373
3373
  previewSource = null;
3374
3374
  previewCache = null;
@@ -3407,8 +3407,8 @@ class ImageFieldComponent {
3407
3407
  ngOnDestroy() {
3408
3408
  this.uploads.revokePreviewUrl(this.previewCache);
3409
3409
  }
3410
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ImageFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3411
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: ImageFieldComponent, isStandalone: true, selector: "ngx-image-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
3410
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ImageFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3411
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: ImageFieldComponent, isStandalone: true, selector: "ngx-image-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
3412
3412
  <div class="ngx-field ngx-field--image"
3413
3413
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
3414
3414
  <label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
@@ -3454,7 +3454,7 @@ class ImageFieldComponent {
3454
3454
  </div>
3455
3455
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
3456
3456
  }
3457
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ImageFieldComponent, decorators: [{
3457
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ImageFieldComponent, decorators: [{
3458
3458
  type: Component,
3459
3459
  args: [{
3460
3460
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -3530,8 +3530,8 @@ class FileFieldComponent {
3530
3530
  readonly = false;
3531
3531
  masked = false;
3532
3532
  uploads = inject(FileUploadService);
3533
- uploading = signal(false);
3534
- uploadError = signal(null);
3533
+ uploading = signal(false, ...(ngDevMode ? [{ debugName: "uploading" }] : /* istanbul ignore next */ []));
3534
+ uploadError = signal(null, ...(ngDevMode ? [{ debugName: "uploadError" }] : /* istanbul ignore next */ []));
3535
3535
  get label() {
3536
3536
  return resolveLabel(this.field?.label, this.language);
3537
3537
  }
@@ -3564,8 +3564,8 @@ class FileFieldComponent {
3564
3564
  this.control.setValue(null);
3565
3565
  this.control.markAsTouched();
3566
3566
  }
3567
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FileFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3568
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: FileFieldComponent, isStandalone: true, selector: "ngx-file-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
3567
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FileFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3568
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: FileFieldComponent, isStandalone: true, selector: "ngx-file-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
3569
3569
  <div class="ngx-field ngx-field--file"
3570
3570
  [attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
3571
3571
  <label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
@@ -3609,7 +3609,7 @@ class FileFieldComponent {
3609
3609
  </div>
3610
3610
  `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
3611
3611
  }
3612
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FileFieldComponent, decorators: [{
3612
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FileFieldComponent, decorators: [{
3613
3613
  type: Component,
3614
3614
  args: [{
3615
3615
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -3740,10 +3740,10 @@ class HookRegistryService {
3740
3740
  return data;
3741
3741
  return hook(data, context);
3742
3742
  }
3743
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HookRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3744
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HookRegistryService, providedIn: 'root' });
3743
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: HookRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3744
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: HookRegistryService, providedIn: 'root' });
3745
3745
  }
3746
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HookRegistryService, decorators: [{
3746
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: HookRegistryService, decorators: [{
3747
3747
  type: Injectable,
3748
3748
  args: [{ providedIn: 'root' }]
3749
3749
  }] });
@@ -3764,10 +3764,10 @@ class RulesEvaluationService {
3764
3764
  filterForTab(rules, tabId, config) {
3765
3765
  return filterRulesForTab(rules, tabId, config);
3766
3766
  }
3767
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RulesEvaluationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3768
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RulesEvaluationService, providedIn: 'root' });
3767
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RulesEvaluationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3768
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RulesEvaluationService, providedIn: 'root' });
3769
3769
  }
3770
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RulesEvaluationService, decorators: [{
3770
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RulesEvaluationService, decorators: [{
3771
3771
  type: Injectable,
3772
3772
  args: [{ providedIn: 'root' }]
3773
3773
  }] });
@@ -3822,10 +3822,10 @@ class ConfigSourceService {
3822
3822
  this.cache.clear();
3823
3823
  }
3824
3824
  }
3825
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ConfigSourceService, deps: [{ token: CONFIG_SOURCE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
3826
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ConfigSourceService, providedIn: 'root' });
3825
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ConfigSourceService, deps: [{ token: CONFIG_SOURCE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
3826
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ConfigSourceService, providedIn: 'root' });
3827
3827
  }
3828
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ConfigSourceService, decorators: [{
3828
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ConfigSourceService, decorators: [{
3829
3829
  type: Injectable,
3830
3830
  args: [{ providedIn: 'root' }]
3831
3831
  }], ctorParameters: () => [{ type: undefined, decorators: [{
@@ -3913,14 +3913,14 @@ class DynamicFormComponent {
3913
3913
  migrations = inject(RECORD_MIGRATIONS, { optional: true }) ?? [];
3914
3914
  Object = Object;
3915
3915
  // ─── Signals (local reactive state) ───────────────────────────────────────
3916
- activeTab = signal('');
3917
- activeSubTab = signal('');
3918
- isSaving = signal(false);
3919
- formValues = signal({});
3916
+ activeTab = signal('', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
3917
+ activeSubTab = signal('', ...(ngDevMode ? [{ debugName: "activeSubTab" }] : /* istanbul ignore next */ []));
3918
+ isSaving = signal(false, ...(ngDevMode ? [{ debugName: "isSaving" }] : /* istanbul ignore next */ []));
3919
+ formValues = signal({}, ...(ngDevMode ? [{ debugName: "formValues" }] : /* istanbul ignore next */ []));
3920
3920
  /** Baseline captured at first build, used when `originalData` is not supplied. */
3921
- sessionBaseline = signal({});
3921
+ sessionBaseline = signal({}, ...(ngDevMode ? [{ debugName: "sessionBaseline" }] : /* istanbul ignore next */ []));
3922
3922
  /** Critical fields the user has explicitly unlocked for editing. */
3923
- unlockedFields = signal(new Set());
3923
+ unlockedFields = signal(new Set(), ...(ngDevMode ? [{ debugName: "unlockedFields" }] : /* istanbul ignore next */ []));
3924
3924
  /** The active tab's field panel, focused after a tab switch. */
3925
3925
  formPanel;
3926
3926
  /** The hosted field components, so an external control-state change can reach them. */
@@ -3942,7 +3942,7 @@ class DynamicFormComponent {
3942
3942
  }
3943
3943
  }
3944
3944
  // ─── Computed ─────────────────────────────────────────────────────────────
3945
- ruleResult = computed(() => this.rulesEvaluation.evaluate(this.rules, this.formValues(), this.baseline()));
3945
+ ruleResult = computed(() => this.rulesEvaluation.evaluate(this.rules, this.formValues(), this.baseline()), ...(ngDevMode ? [{ debugName: "ruleResult" }] : /* istanbul ignore next */ []));
3946
3946
  /**
3947
3947
  * Critical fields whose value differs from the session baseline.
3948
3948
  * These drive the deferred `VALUE_CHANGED` banner — a critical edit is announced once,
@@ -3954,7 +3954,7 @@ class DynamicFormComponent {
3954
3954
  return [];
3955
3955
  const values = this.formValues();
3956
3956
  return this.allFields().filter(f => f.criticalField && !this.sameValue(values[f.id], baseline[f.id]));
3957
- });
3957
+ }, ...(ngDevMode ? [{ debugName: "changedCriticalFields" }] : /* istanbul ignore next */ []));
3958
3958
  get tabs() {
3959
3959
  return this.config?.tabs || [];
3960
3960
  }
@@ -3995,8 +3995,8 @@ class DynamicFormComponent {
3995
3995
  return [];
3996
3996
  const rawFields = active ? (active.fields || []) : ((this.config?.tabs || []).flatMap(t => t.fields || []));
3997
3997
  const currentValues = this.formValues();
3998
- const hiddenFields = this.ruleResult().hiddenFields;
3999
- return rawFields.filter(field => evaluateFieldVisibility(field, currentValues) && !hiddenFields.includes(field.id));
3998
+ const hiddenFields = new Set(this.ruleResult().hiddenFields);
3999
+ return rawFields.filter(field => evaluateFieldVisibility(field, currentValues) && !this.namesField(hiddenFields, field));
4000
4000
  }
4001
4001
  /**
4002
4002
  * Entity-level permissions for the current roles.
@@ -4176,6 +4176,7 @@ class DynamicFormComponent {
4176
4176
  };
4177
4177
  buildTabControls(this.config.tabs || [], group);
4178
4178
  this.form = this.fb.group(group);
4179
+ this.warnAmbiguousRuleReferences();
4179
4180
  if (this.initialData) {
4180
4181
  this.patchForm(this.upgradeRecord(this.initialData));
4181
4182
  }
@@ -4402,12 +4403,89 @@ class DynamicFormComponent {
4402
4403
  record = normalizeArrayStructures(record, this.config);
4403
4404
  return record;
4404
4405
  }
4406
+ /**
4407
+ * Warns when a rule names a field id that two scopes define.
4408
+ *
4409
+ * Field ids are unique per scope, so `address` may exist on both Personal Details and Work
4410
+ * Details — but a rule carries a bare id and no scope, so it cannot say which one it means.
4411
+ * `flattenFormValues` keys by bare id, so one of the two silently wins and the rule reads
4412
+ * whichever landed there.
4413
+ *
4414
+ * `validateConfig` catches this for `showWhen` and cascade parents, which live in the
4415
+ * config. Rules do not: they arrive as a separate `@Input`, so the config alone cannot be
4416
+ * checked for them and this is the only place both are in hand.
4417
+ */
4418
+ warnAmbiguousRuleReferences() {
4419
+ if (!isDevMode() || !this.rules?.length || !this.config)
4420
+ return;
4421
+ const ambiguous = ambiguousFieldIds(this.config);
4422
+ if (ambiguous.size === 0)
4423
+ return;
4424
+ const referenced = new Set();
4425
+ for (const rule of this.rules) {
4426
+ if (rule.fieldId)
4427
+ referenced.add(rule.fieldId);
4428
+ for (const condition of rule.conditions ?? []) {
4429
+ if (condition.compareToField)
4430
+ referenced.add(condition.compareToField);
4431
+ }
4432
+ for (const target of rule.targets ?? []) {
4433
+ if (target.type === 'field' && target.id)
4434
+ referenced.add(target.id);
4435
+ }
4436
+ }
4437
+ for (const id of referenced) {
4438
+ const scopes = ambiguous.get(id);
4439
+ if (!scopes || DynamicFormComponent.warnedAmbiguousIds.has(id))
4440
+ continue;
4441
+ DynamicFormComponent.warnedAmbiguousIds.add(id);
4442
+ console.warn(`[ngx-dynamic-entity] A rule references field "${id}", which is defined in ${scopes.join(' and ')}. ` +
4443
+ `Rules address a field by bare id, so the rule will read whichever one the form finds first. ` +
4444
+ `Give one of them a distinct id.`);
4445
+ }
4446
+ }
4447
+ static warnedAmbiguousIds = new Set();
4448
+ /**
4449
+ * The value map rules and `showWhen` are evaluated against.
4450
+ *
4451
+ * Every field appears under two keys: its bare id, and its `refererField` path wrapped in brackets.
4452
+ * The bare id is what every config written so far uses and is kept exactly as it was — but
4453
+ * ids are unique per scope, so when two scopes define one the last field walked wins and
4454
+ * the rule reads whichever that is. `[personal.address]` names one field and cannot be
4455
+ * ambiguous, which is why a rule that has to distinguish them uses the ref.
4456
+ *
4457
+ * Both live in one flat map on purpose: `evaluateFormRules` takes a `Record<string,
4458
+ * unknown>` and needs no knowledge of refs at all — the extra keys simply resolve.
4459
+ */
4460
+ /**
4461
+ * Whether a rule result names this field.
4462
+ *
4463
+ * A rule target is either a bare field id — how every config so far addresses a field — or
4464
+ * a bracketed path, which is the only way to name one of two fields that share an id.
4465
+ * Both have to match, or a rule written either way would fail to hide what it targeted.
4466
+ */
4467
+ namesField(names, field) {
4468
+ if (names.has(field.id))
4469
+ return true;
4470
+ const ref = field.refererField ?? this.pathFor(field);
4471
+ return ref ? names.has(toRefToken(ref)) : false;
4472
+ }
4473
+ /** The field's declared path, or the one its position implies when it carries none. */
4474
+ pathFor(field) {
4475
+ const entry = collectFieldScopes(this.config).find(e => e.field === field);
4476
+ return entry ? fieldRefFor(entry.scope, field.id) : null;
4477
+ }
4405
4478
  flattenFormValues() {
4406
4479
  const out = {};
4407
- for (const field of this.allFields()) {
4408
- const ctrl = this.getControl(field.id);
4409
- if (ctrl)
4410
- out[field.id] = ctrl.value;
4480
+ for (const entry of collectFieldScopes(this.config)) {
4481
+ const field = entry.field;
4482
+ if (!field?.id)
4483
+ continue;
4484
+ const ctrl = this.getControl(field.id, entry.scope.split('.').pop());
4485
+ if (!ctrl)
4486
+ continue;
4487
+ out[field.id] = ctrl.value;
4488
+ out[toRefToken(refOf(field, entry.scope))] = ctrl.value;
4411
4489
  }
4412
4490
  return out;
4413
4491
  }
@@ -4514,7 +4592,7 @@ class DynamicFormComponent {
4514
4592
  const ctrl = this.getControl(field.id);
4515
4593
  if (!ctrl)
4516
4594
  continue;
4517
- const hidden = hiddenByRule.has(field.id) || !evaluateFieldVisibility(field, values);
4595
+ const hidden = this.namesField(hiddenByRule, field) || !evaluateFieldVisibility(field, values);
4518
4596
  if (hidden) {
4519
4597
  if (ctrl.enabled)
4520
4598
  ctrl.disable({ emitEvent: false });
@@ -4667,12 +4745,12 @@ class DynamicFormComponent {
4667
4745
  this.unlockedFields.set(new Set());
4668
4746
  this.formReset.emit();
4669
4747
  }
4670
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DynamicFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4671
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", 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" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", activeTabChange: "activeTabChange", saveRejected: "saveRejected" }, host: { listeners: { "window:keydown": "handleKeyboardEvent($event)" } }, providers: [EntityRefSelectionService], 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\" novalidate>\n\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 <!-- 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 \uD83D\uDD12 Critical field changed:\n @for (field of changedCriticalFields(); track field.id) {\n <span>{{ resolveFieldLabel(field) }}{{ $last ? '' : ', ' }}</span>\n }\n \u2014 this differs from the value at the start of this session.\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 </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 class=\"ngx-form__tabs ngx-form__tabs--sub\" role=\"tablist\" data-testid=\"subtab-strip\" style=\"margin-top: -12px; margin-bottom: 12px; font-size: 13px;\">\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 </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\n role=\"tabpanel\"\n tabindex=\"-1\"\n #formPanel\n class=\"ngx-form__tabpanel\"\n [attr.aria-label]=\"activeTabLabel\"\n >\n @if (activeTabModuleComponent) {\n <div class=\"ngx-form__module-panel\" data-testid=\"module-panel\">\n <ng-container *ngComponentOutlet=\"activeTabModuleComponent; inputs: (activeSubTabConfig || activeTabConfig)?.moduleInputs\"></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 @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 (isFieldLocked(field) ? 'Unlock ' : 'Lock ') + resolveFieldLabel(field)\n \"\n [title]=\"\n isFieldLocked(field)\n ? 'Critical field \u2014 click to unlock for editing'\n : 'Critical field \u2014 click to lock'\n \"\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\n type=\"button\"\n class=\"ngx-form__reset\"\n data-testid=\"form-reset\"\n (click)=\"reset()\"\n [disabled]=\"isSaving()\"\n >\n Reset\n </button>\n <button\n type=\"submit\"\n class=\"ngx-form__submit\"\n data-testid=\"form-submit\"\n [disabled]=\"submitBlocked\"\n title=\"Shortcut: Ctrl+S\"\n >\n @if (isSaving() || loading) {\n Saving\u2026\n } @else {\n Save\n }\n </button>\n </div>\n }\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 You do not have permission to view this record.\n </div>\n}\n", styles: [".ngx-form__panel{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:16px 20px;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:6px;cursor:pointer;font-size:14px;line-height:1;padding:4px 6px}.ngx-form__lock:hover{border-color:#d1d5db;background:#f9fafb}@media (max-width: 768px){.ngx-form__panel{grid-template-columns:1fr}.ngx-form__field-slot{grid-column:span 12!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],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", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4748
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4749
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", 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" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", activeTabChange: "activeTabChange", saveRejected: "saveRejected" }, host: { listeners: { "window:keydown": "handleKeyboardEvent($event)" } }, providers: [EntityRefSelectionService], 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\" novalidate>\n\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 <!-- 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 \uD83D\uDD12 Critical field changed:\n @for (field of changedCriticalFields(); track field.id) {\n <span>{{ resolveFieldLabel(field) }}{{ $last ? '' : ', ' }}</span>\n }\n \u2014 this differs from the value at the start of this session.\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 </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 class=\"ngx-form__tabs ngx-form__tabs--sub\" role=\"tablist\" data-testid=\"subtab-strip\" style=\"margin-top: -12px; margin-bottom: 12px; font-size: 13px;\">\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 </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\n role=\"tabpanel\"\n tabindex=\"-1\"\n #formPanel\n class=\"ngx-form__tabpanel\"\n [attr.aria-label]=\"activeTabLabel\"\n >\n @if (activeTabModuleComponent) {\n <div class=\"ngx-form__module-panel\" data-testid=\"module-panel\">\n <ng-container *ngComponentOutlet=\"activeTabModuleComponent; inputs: (activeSubTabConfig || activeTabConfig)?.moduleInputs\"></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 @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 (isFieldLocked(field) ? 'Unlock ' : 'Lock ') + resolveFieldLabel(field)\n \"\n [title]=\"\n isFieldLocked(field)\n ? 'Critical field \u2014 click to unlock for editing'\n : 'Critical field \u2014 click to lock'\n \"\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\n type=\"button\"\n class=\"ngx-form__reset\"\n data-testid=\"form-reset\"\n (click)=\"reset()\"\n [disabled]=\"isSaving()\"\n >\n Reset\n </button>\n <button\n type=\"submit\"\n class=\"ngx-form__submit\"\n data-testid=\"form-submit\"\n [disabled]=\"submitBlocked\"\n title=\"Shortcut: Ctrl+S\"\n >\n @if (isSaving() || loading) {\n Saving\u2026\n } @else {\n Save\n }\n </button>\n </div>\n }\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 You do not have permission to view this record.\n </div>\n}\n", styles: [".ngx-form__panel{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:16px 20px;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:6px;cursor:pointer;font-size:14px;line-height:1;padding:4px 6px}.ngx-form__lock:hover{border-color:#d1d5db;background:#f9fafb}@media(max-width:768px){.ngx-form__panel{grid-template-columns:1fr}.ngx-form__field-slot{grid-column:span 12!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 });
4672
4750
  }
4673
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DynamicFormComponent, decorators: [{
4751
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormComponent, decorators: [{
4674
4752
  type: Component,
4675
- args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-dynamic-form', standalone: true, imports: [ReactiveFormsModule, DynamicFieldComponent, NgComponentOutlet], providers: [EntityRefSelectionService], template: "@if (canView) {\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\" class=\"ngx-form\" novalidate>\n\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 <!-- 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 \uD83D\uDD12 Critical field changed:\n @for (field of changedCriticalFields(); track field.id) {\n <span>{{ resolveFieldLabel(field) }}{{ $last ? '' : ', ' }}</span>\n }\n \u2014 this differs from the value at the start of this session.\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 </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 class=\"ngx-form__tabs ngx-form__tabs--sub\" role=\"tablist\" data-testid=\"subtab-strip\" style=\"margin-top: -12px; margin-bottom: 12px; font-size: 13px;\">\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 </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\n role=\"tabpanel\"\n tabindex=\"-1\"\n #formPanel\n class=\"ngx-form__tabpanel\"\n [attr.aria-label]=\"activeTabLabel\"\n >\n @if (activeTabModuleComponent) {\n <div class=\"ngx-form__module-panel\" data-testid=\"module-panel\">\n <ng-container *ngComponentOutlet=\"activeTabModuleComponent; inputs: (activeSubTabConfig || activeTabConfig)?.moduleInputs\"></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 @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 (isFieldLocked(field) ? 'Unlock ' : 'Lock ') + resolveFieldLabel(field)\n \"\n [title]=\"\n isFieldLocked(field)\n ? 'Critical field \u2014 click to unlock for editing'\n : 'Critical field \u2014 click to lock'\n \"\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\n type=\"button\"\n class=\"ngx-form__reset\"\n data-testid=\"form-reset\"\n (click)=\"reset()\"\n [disabled]=\"isSaving()\"\n >\n Reset\n </button>\n <button\n type=\"submit\"\n class=\"ngx-form__submit\"\n data-testid=\"form-submit\"\n [disabled]=\"submitBlocked\"\n title=\"Shortcut: Ctrl+S\"\n >\n @if (isSaving() || loading) {\n Saving\u2026\n } @else {\n Save\n }\n </button>\n </div>\n }\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 You do not have permission to view this record.\n </div>\n}\n", styles: [".ngx-form__panel{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:16px 20px;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:6px;cursor:pointer;font-size:14px;line-height:1;padding:4px 6px}.ngx-form__lock:hover{border-color:#d1d5db;background:#f9fafb}@media (max-width: 768px){.ngx-form__panel{grid-template-columns:1fr}.ngx-form__field-slot{grid-column:span 12!important}}\n"] }]
4753
+ args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-dynamic-form', standalone: true, imports: [ReactiveFormsModule, DynamicFieldComponent, NgComponentOutlet], providers: [EntityRefSelectionService], template: "@if (canView) {\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\" class=\"ngx-form\" novalidate>\n\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 <!-- 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 \uD83D\uDD12 Critical field changed:\n @for (field of changedCriticalFields(); track field.id) {\n <span>{{ resolveFieldLabel(field) }}{{ $last ? '' : ', ' }}</span>\n }\n \u2014 this differs from the value at the start of this session.\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 </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 class=\"ngx-form__tabs ngx-form__tabs--sub\" role=\"tablist\" data-testid=\"subtab-strip\" style=\"margin-top: -12px; margin-bottom: 12px; font-size: 13px;\">\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 </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\n role=\"tabpanel\"\n tabindex=\"-1\"\n #formPanel\n class=\"ngx-form__tabpanel\"\n [attr.aria-label]=\"activeTabLabel\"\n >\n @if (activeTabModuleComponent) {\n <div class=\"ngx-form__module-panel\" data-testid=\"module-panel\">\n <ng-container *ngComponentOutlet=\"activeTabModuleComponent; inputs: (activeSubTabConfig || activeTabConfig)?.moduleInputs\"></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 @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 (isFieldLocked(field) ? 'Unlock ' : 'Lock ') + resolveFieldLabel(field)\n \"\n [title]=\"\n isFieldLocked(field)\n ? 'Critical field \u2014 click to unlock for editing'\n : 'Critical field \u2014 click to lock'\n \"\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\n type=\"button\"\n class=\"ngx-form__reset\"\n data-testid=\"form-reset\"\n (click)=\"reset()\"\n [disabled]=\"isSaving()\"\n >\n Reset\n </button>\n <button\n type=\"submit\"\n class=\"ngx-form__submit\"\n data-testid=\"form-submit\"\n [disabled]=\"submitBlocked\"\n title=\"Shortcut: Ctrl+S\"\n >\n @if (isSaving() || loading) {\n Saving\u2026\n } @else {\n Save\n }\n </button>\n </div>\n }\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 You do not have permission to view this record.\n </div>\n}\n", styles: [".ngx-form__panel{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:16px 20px;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:6px;cursor:pointer;font-size:14px;line-height:1;padding:4px 6px}.ngx-form__lock:hover{border-color:#d1d5db;background:#f9fafb}@media(max-width:768px){.ngx-form__panel{grid-template-columns:1fr}.ngx-form__field-slot{grid-column:span 12!important}}\n"] }]
4676
4754
  }], propDecorators: { config: [{
4677
4755
  type: Input
4678
4756
  }], rules: [{
@@ -4760,8 +4838,8 @@ class DynamicRecordFormComponent {
4760
4838
  rulesEvaluation = inject(RulesEvaluationService);
4761
4839
  host = inject(ElementRef);
4762
4840
  injector = inject(Injector);
4763
- currentData = signal({});
4764
- originalBaseline = signal({});
4841
+ currentData = signal({}, ...(ngDevMode ? [{ debugName: "currentData" }] : /* istanbul ignore next */ []));
4842
+ originalBaseline = signal({}, ...(ngDevMode ? [{ debugName: "originalBaseline" }] : /* istanbul ignore next */ []));
4765
4843
  /**
4766
4844
  * Info banners the user has dismissed, by field id.
4767
4845
  *
@@ -4769,7 +4847,7 @@ class DynamicRecordFormComponent {
4769
4847
  * value changes. Dismissal is keyed by field so re-triggering a different field's rule
4770
4848
  * still shows. Re-armed when a different record is loaded.
4771
4849
  */
4772
- dismissed = signal(new Set());
4850
+ dismissed = signal(new Set(), ...(ngDevMode ? [{ debugName: "dismissed" }] : /* istanbul ignore next */ []));
4773
4851
  /**
4774
4852
  * Info banners from the rules engine that are still showing.
4775
4853
  *
@@ -4799,8 +4877,8 @@ class DynamicRecordFormComponent {
4799
4877
  * building a row's FormGroup inside the tab view races the tab's own initialisation, so
4800
4878
  * the row form is kept detached and only pushed into the FormArray on save.
4801
4879
  */
4802
- inlineRowField = signal(null);
4803
- inlineRowIndex = signal(null);
4880
+ inlineRowField = signal(null, ...(ngDevMode ? [{ debugName: "inlineRowField" }] : /* istanbul ignore next */ []));
4881
+ inlineRowIndex = signal(null, ...(ngDevMode ? [{ debugName: "inlineRowIndex" }] : /* istanbul ignore next */ []));
4804
4882
  inlineRowForm = null;
4805
4883
  /** `array` fields on the tab currently shown, so the template can list their rows. */
4806
4884
  get arrayFieldsForActiveTab() {
@@ -4890,7 +4968,7 @@ class DynamicRecordFormComponent {
4890
4968
  * validates only that section on save — a required field on an untouched tab must not
4891
4969
  * block saving the tab you are actually working on.
4892
4970
  */
4893
- editingTabId = signal(null);
4971
+ editingTabId = signal(null, ...(ngDevMode ? [{ debugName: "editingTabId" }] : /* istanbul ignore next */ []));
4894
4972
  /**
4895
4973
  * The tab the inner form is showing.
4896
4974
  *
@@ -4899,7 +4977,7 @@ class DynamicRecordFormComponent {
4899
4977
  * value mid-pass — an ExpressionChangedAfterItHasBeenChecked error. Seeded from the
4900
4978
  * config and kept in step by the child's `activeTabChange` output.
4901
4979
  */
4902
- activeTabIdSig = signal(null);
4980
+ activeTabIdSig = signal(null, ...(ngDevMode ? [{ debugName: "activeTabIdSig" }] : /* istanbul ignore next */ []));
4903
4981
  get activeTabId() {
4904
4982
  return this.activeTabIdSig();
4905
4983
  }
@@ -4916,7 +4994,7 @@ class DynamicRecordFormComponent {
4916
4994
  return this.config?.tabs?.find(t => t.visibility !== false)?.id ?? null;
4917
4995
  }
4918
4996
  /** Errors from the last save attempt, by field id. */
4919
- sectionErrors = signal({});
4997
+ sectionErrors = signal({}, ...(ngDevMode ? [{ debugName: "sectionErrors" }] : /* istanbul ignore next */ []));
4920
4998
  /** Template access to `Object.keys` for iterating the error map. */
4921
4999
  Object = Object;
4922
5000
  get isEditingActiveTab() {
@@ -5111,7 +5189,7 @@ class DynamicRecordFormComponent {
5111
5189
  if (!orig || !curr || Object.keys(orig).length === 0)
5112
5190
  return false;
5113
5191
  return JSON.stringify(orig) !== JSON.stringify(curr);
5114
- });
5192
+ }, ...(ngDevMode ? [{ debugName: "isModified" }] : /* istanbul ignore next */ []));
5115
5193
  summaryFields = computed(() => {
5116
5194
  const fields = [];
5117
5195
  const walkTabs = (tabs = []) => {
@@ -5126,7 +5204,7 @@ class DynamicRecordFormComponent {
5126
5204
  };
5127
5205
  walkTabs(this.config?.tabs);
5128
5206
  return fields;
5129
- });
5207
+ }, ...(ngDevMode ? [{ debugName: "summaryFields" }] : /* istanbul ignore next */ []));
5130
5208
  onFormChange(data) {
5131
5209
  // Baseline is seeded from `initialData` in ngOnChanges; only fill it here when the
5132
5210
  // record was opened empty (create flow), so the first emitted shape becomes the baseline.
@@ -5201,10 +5279,10 @@ class DynamicRecordFormComponent {
5201
5279
  el.focus();
5202
5280
  }, { injector: this.injector });
5203
5281
  }
5204
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DynamicRecordFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5205
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", 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", viewMode: "viewMode" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", sectionSave: "sectionSave" }, 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]=\"recordTitle\"\n />\n } @else {\n <div class=\"ngx-record-editor__avatar\" data-testid=\"header-avatar-initial\">\n {{ avatarLetter }}\n </div>\n }\n <div>\n <h2 class=\"ngx-record-editor__title\" data-testid=\"record-title\">\n {{ recordTitle }}\n </h2>\n <span class=\"ngx-record-editor__subtitle\">Entity: {{ config.entity }}</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\">{{ headerToggleValue ? 'Yes' : 'No' }}</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]=\"'Dismiss: ' + banner.message\"\n (click)=\"dismissInfoBanner(banner.fieldId)\"\n >\u2715</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 \uD83D\uDD12 Record modified from session baseline \u2014 unsaved changes pending.\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]=\"'Click to jump to ' + 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\">Editing this section</span>\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-section\" (click)=\"cancelSection()\">\n Cancel\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-section\" (click)=\"saveSection()\">\n Save section\n </button>\n } @else {\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"edit-section\" (click)=\"editSection()\">\n Edit section\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 >+ Row</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 type=\"button\" class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'edit-row-' + arrayField.id + '-' + $index\"\n (click)=\"openEditRow(arrayField, $index)\">Edit</button>\n <button type=\"button\" class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'delete-row-' + arrayField.id + '-' + $index\"\n (click)=\"deleteRow(arrayField, $index)\">Delete</button>\n }\n </div>\n } @empty {\n <p class=\"ngx-record-editor__row-empty\">No rows yet.</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>{{ inlineRowIndex() === null ? 'Add' : 'Edit' }} {{ formatFieldLabel(rowField) }} row</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()\">Cancel</button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-row\" (click)=\"saveRow()\">Save row</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 [showInfoBanners]=\"false\"\n [loading]=\"loading\"\n [error]=\"error\"\n (formChange)=\"onFormChange($event)\"\n (formSubmit)=\"onFormSubmit($event)\"\n (formReset)=\"onFormReset()\"\n (activeTabChange)=\"onActiveTabChange($event)\"\n />\n</div>\n", styles: [".ngx-record-editor{display:flex;flex-direction:column;gap:16px;background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:20px;box-shadow:0 4px 6px -1px #0000000d}.ngx-record-editor__header{display:flex;align-items:center;justify-content:space-between;padding-bottom:16px;border-bottom:1px solid #f3f4f6}.ngx-record-editor__title-group{display:flex;align-items:center;gap:12px}.ngx-record-editor__avatar{width:48px;height:48px;border-radius:50%;object-fit:cover;background:#e0e7ff;color:#4f46e5;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:18px}.ngx-record-editor__title{font-size:20px;font-weight:700;color:#111827;margin:0}.ngx-record-editor__subtitle{font-size:13px;color:#6b7280}.ngx-record-editor__header-toggle{display:flex;align-items:center;gap:8px;font-size:13px;color:#374151}.ngx-record-editor__header-toggle-text{font-weight:600}.ngx-record-editor__banner{padding:10px 14px;border-radius:8px;font-size:13px;font-weight:500}.ngx-record-editor__banner--info{background:#eff6ff;border:1px solid #dbeafe;color:#1d4ed8;display:flex;align-items:center;justify-content:space-between;gap:12px}.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__banner--error{background:#fef2f2;border:1px solid #fecaca;color:#b91c1c}.ngx-record-editor__section-bar{display:flex;align-items:center;justify-content:flex-end;gap:8px}.ngx-record-editor__section-hint{margin-right:auto;font-size:12px;color:#6b7280}.ngx-record-editor__banner--warning{background:#fffbeb;border:1px solid #fef3c7;color:#b45309}.ngx-record-editor__rows{border:1px solid #f3f4f6;border-radius: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:6px 8px;border-radius:6px;background:#f9fafb}.ngx-record-editor__row-text{flex:1;font-size:13px;overflow-wrap:anywhere}.ngx-record-editor__row-btn{background:none;border:1px solid #e5e7eb;border-radius:6px;cursor:pointer;font-size:12px;padding:2px 8px}.ngx-record-editor__row-empty{font-size:12px;color:#6b7280;margin:0}.ngx-record-editor__drawer{border:1px solid #dbeafe;background:#f8fafc;border-radius:8px;padding:14px;display:flex;flex-direction:column;gap:12px}.ngx-record-editor__summary-panel{background:#f9fafb;border:1px solid #f3f4f6;border-radius:8px;padding:12px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}.ngx-record-editor__summary-item{display:flex;flex-direction:column;cursor:pointer;padding:6px 10px;border-radius:6px;transition:background .15s}.ngx-record-editor__summary-item:hover{background:#eff6ff}.ngx-record-editor__summary-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#6b7280;font-weight:600}.ngx-record-editor__summary-val{font-size:14px;font-weight:600;color:#1f2937}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],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"], 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 });
5282
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicRecordFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5283
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", 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", viewMode: "viewMode" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", sectionSave: "sectionSave" }, 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]=\"recordTitle\"\n />\n } @else {\n <div class=\"ngx-record-editor__avatar\" data-testid=\"header-avatar-initial\">\n {{ avatarLetter }}\n </div>\n }\n <div>\n <h2 class=\"ngx-record-editor__title\" data-testid=\"record-title\">\n {{ recordTitle }}\n </h2>\n <span class=\"ngx-record-editor__subtitle\">Entity: {{ config.entity }}</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\">{{ headerToggleValue ? 'Yes' : 'No' }}</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]=\"'Dismiss: ' + banner.message\"\n (click)=\"dismissInfoBanner(banner.fieldId)\"\n >\u2715</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 \uD83D\uDD12 Record modified from session baseline \u2014 unsaved changes pending.\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]=\"'Click to jump to ' + 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\">Editing this section</span>\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-section\" (click)=\"cancelSection()\">\n Cancel\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-section\" (click)=\"saveSection()\">\n Save section\n </button>\n } @else {\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"edit-section\" (click)=\"editSection()\">\n Edit section\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 >+ Row</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 type=\"button\" class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'edit-row-' + arrayField.id + '-' + $index\"\n (click)=\"openEditRow(arrayField, $index)\">Edit</button>\n <button type=\"button\" class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'delete-row-' + arrayField.id + '-' + $index\"\n (click)=\"deleteRow(arrayField, $index)\">Delete</button>\n }\n </div>\n } @empty {\n <p class=\"ngx-record-editor__row-empty\">No rows yet.</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>{{ inlineRowIndex() === null ? 'Add' : 'Edit' }} {{ formatFieldLabel(rowField) }} row</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()\">Cancel</button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-row\" (click)=\"saveRow()\">Save row</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 [showInfoBanners]=\"false\"\n [loading]=\"loading\"\n [error]=\"error\"\n (formChange)=\"onFormChange($event)\"\n (formSubmit)=\"onFormSubmit($event)\"\n (formReset)=\"onFormReset()\"\n (activeTabChange)=\"onActiveTabChange($event)\"\n />\n</div>\n", styles: [".ngx-record-editor{display:flex;flex-direction:column;gap:16px;background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:20px;box-shadow:0 4px 6px -1px #0000000d}.ngx-record-editor__header{display:flex;align-items:center;justify-content:space-between;padding-bottom:16px;border-bottom:1px solid #f3f4f6}.ngx-record-editor__title-group{display:flex;align-items:center;gap:12px}.ngx-record-editor__avatar{width:48px;height:48px;border-radius:50%;object-fit:cover;background:#e0e7ff;color:#4f46e5;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:18px}.ngx-record-editor__title{font-size:20px;font-weight:700;color:#111827;margin:0}.ngx-record-editor__subtitle{font-size:13px;color:#6b7280}.ngx-record-editor__header-toggle{display:flex;align-items:center;gap:8px;font-size:13px;color:#374151}.ngx-record-editor__header-toggle-text{font-weight:600}.ngx-record-editor__banner{padding:10px 14px;border-radius:8px;font-size:13px;font-weight:500}.ngx-record-editor__banner--info{background:#eff6ff;border:1px solid #dbeafe;color:#1d4ed8;display:flex;align-items:center;justify-content:space-between;gap:12px}.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__banner--error{background:#fef2f2;border:1px solid #fecaca;color:#b91c1c}.ngx-record-editor__section-bar{display:flex;align-items:center;justify-content:flex-end;gap:8px}.ngx-record-editor__section-hint{margin-right:auto;font-size:12px;color:#6b7280}.ngx-record-editor__banner--warning{background:#fffbeb;border:1px solid #fef3c7;color:#b45309}.ngx-record-editor__rows{border:1px solid #f3f4f6;border-radius: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:6px 8px;border-radius:6px;background:#f9fafb}.ngx-record-editor__row-text{flex:1;font-size:13px;overflow-wrap:anywhere}.ngx-record-editor__row-btn{background:none;border:1px solid #e5e7eb;border-radius:6px;cursor:pointer;font-size:12px;padding:2px 8px}.ngx-record-editor__row-empty{font-size:12px;color:#6b7280;margin:0}.ngx-record-editor__drawer{border:1px solid #dbeafe;background:#f8fafc;border-radius:8px;padding:14px;display:flex;flex-direction:column;gap:12px}.ngx-record-editor__summary-panel{background:#f9fafb;border:1px solid #f3f4f6;border-radius:8px;padding:12px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}.ngx-record-editor__summary-item{display:flex;flex-direction:column;cursor:pointer;padding:6px 10px;border-radius:6px;transition:background .15s}.ngx-record-editor__summary-item:hover{background:#eff6ff}.ngx-record-editor__summary-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#6b7280;font-weight:600}.ngx-record-editor__summary-val{font-size:14px;font-weight:600;color:#1f2937}\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"], 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 });
5206
5284
  }
5207
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DynamicRecordFormComponent, decorators: [{
5285
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicRecordFormComponent, decorators: [{
5208
5286
  type: Component,
5209
5287
  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]=\"recordTitle\"\n />\n } @else {\n <div class=\"ngx-record-editor__avatar\" data-testid=\"header-avatar-initial\">\n {{ avatarLetter }}\n </div>\n }\n <div>\n <h2 class=\"ngx-record-editor__title\" data-testid=\"record-title\">\n {{ recordTitle }}\n </h2>\n <span class=\"ngx-record-editor__subtitle\">Entity: {{ config.entity }}</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\">{{ headerToggleValue ? 'Yes' : 'No' }}</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]=\"'Dismiss: ' + banner.message\"\n (click)=\"dismissInfoBanner(banner.fieldId)\"\n >\u2715</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 \uD83D\uDD12 Record modified from session baseline \u2014 unsaved changes pending.\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]=\"'Click to jump to ' + 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\">Editing this section</span>\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-section\" (click)=\"cancelSection()\">\n Cancel\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-section\" (click)=\"saveSection()\">\n Save section\n </button>\n } @else {\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"edit-section\" (click)=\"editSection()\">\n Edit section\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 >+ Row</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 type=\"button\" class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'edit-row-' + arrayField.id + '-' + $index\"\n (click)=\"openEditRow(arrayField, $index)\">Edit</button>\n <button type=\"button\" class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'delete-row-' + arrayField.id + '-' + $index\"\n (click)=\"deleteRow(arrayField, $index)\">Delete</button>\n }\n </div>\n } @empty {\n <p class=\"ngx-record-editor__row-empty\">No rows yet.</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>{{ inlineRowIndex() === null ? 'Add' : 'Edit' }} {{ formatFieldLabel(rowField) }} row</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()\">Cancel</button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-row\" (click)=\"saveRow()\">Save row</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 [showInfoBanners]=\"false\"\n [loading]=\"loading\"\n [error]=\"error\"\n (formChange)=\"onFormChange($event)\"\n (formSubmit)=\"onFormSubmit($event)\"\n (formReset)=\"onFormReset()\"\n (activeTabChange)=\"onActiveTabChange($event)\"\n />\n</div>\n", styles: [".ngx-record-editor{display:flex;flex-direction:column;gap:16px;background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:20px;box-shadow:0 4px 6px -1px #0000000d}.ngx-record-editor__header{display:flex;align-items:center;justify-content:space-between;padding-bottom:16px;border-bottom:1px solid #f3f4f6}.ngx-record-editor__title-group{display:flex;align-items:center;gap:12px}.ngx-record-editor__avatar{width:48px;height:48px;border-radius:50%;object-fit:cover;background:#e0e7ff;color:#4f46e5;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:18px}.ngx-record-editor__title{font-size:20px;font-weight:700;color:#111827;margin:0}.ngx-record-editor__subtitle{font-size:13px;color:#6b7280}.ngx-record-editor__header-toggle{display:flex;align-items:center;gap:8px;font-size:13px;color:#374151}.ngx-record-editor__header-toggle-text{font-weight:600}.ngx-record-editor__banner{padding:10px 14px;border-radius:8px;font-size:13px;font-weight:500}.ngx-record-editor__banner--info{background:#eff6ff;border:1px solid #dbeafe;color:#1d4ed8;display:flex;align-items:center;justify-content:space-between;gap:12px}.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__banner--error{background:#fef2f2;border:1px solid #fecaca;color:#b91c1c}.ngx-record-editor__section-bar{display:flex;align-items:center;justify-content:flex-end;gap:8px}.ngx-record-editor__section-hint{margin-right:auto;font-size:12px;color:#6b7280}.ngx-record-editor__banner--warning{background:#fffbeb;border:1px solid #fef3c7;color:#b45309}.ngx-record-editor__rows{border:1px solid #f3f4f6;border-radius: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:6px 8px;border-radius:6px;background:#f9fafb}.ngx-record-editor__row-text{flex:1;font-size:13px;overflow-wrap:anywhere}.ngx-record-editor__row-btn{background:none;border:1px solid #e5e7eb;border-radius:6px;cursor:pointer;font-size:12px;padding:2px 8px}.ngx-record-editor__row-empty{font-size:12px;color:#6b7280;margin:0}.ngx-record-editor__drawer{border:1px solid #dbeafe;background:#f8fafc;border-radius:8px;padding:14px;display:flex;flex-direction:column;gap:12px}.ngx-record-editor__summary-panel{background:#f9fafb;border:1px solid #f3f4f6;border-radius:8px;padding:12px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}.ngx-record-editor__summary-item{display:flex;flex-direction:column;cursor:pointer;padding:6px 10px;border-radius:6px;transition:background .15s}.ngx-record-editor__summary-item:hover{background:#eff6ff}.ngx-record-editor__summary-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#6b7280;font-weight:600}.ngx-record-editor__summary-val{font-size:14px;font-weight:600;color:#1f2937}\n"] }]
5210
5288
  }], propDecorators: { config: [{