ngx-dynamic-entity 1.2.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.
- package/README.md +15 -1
- package/fesm2022/ngx-dynamic-entity.mjs +512 -161
- package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
- package/package.json +6 -7
- package/types/ngx-dynamic-entity.d.ts +1719 -0
- package/esm2022/lib/field-types/array-field.component.mjs +0 -168
- package/esm2022/lib/field-types/boolean-field.component.mjs +0 -101
- package/esm2022/lib/field-types/checkbox-field.component.mjs +0 -82
- package/esm2022/lib/field-types/currency-field.component.mjs +0 -102
- package/esm2022/lib/field-types/date-field.component.mjs +0 -91
- package/esm2022/lib/field-types/dropdown-field.component.mjs +0 -176
- package/esm2022/lib/field-types/email-field.component.mjs +0 -102
- package/esm2022/lib/field-types/entity-ref-field.component.mjs +0 -195
- package/esm2022/lib/field-types/field-component.contract.mjs +0 -2
- package/esm2022/lib/field-types/file-field.component.mjs +0 -157
- package/esm2022/lib/field-types/group-field.component.mjs +0 -89
- package/esm2022/lib/field-types/image-field.component.mjs +0 -169
- package/esm2022/lib/field-types/month-year-field.component.mjs +0 -152
- package/esm2022/lib/field-types/multi-select-field.component.mjs +0 -146
- package/esm2022/lib/field-types/number-field.component.mjs +0 -112
- package/esm2022/lib/field-types/password-field.component.mjs +0 -110
- package/esm2022/lib/field-types/radio-field.component.mjs +0 -153
- package/esm2022/lib/field-types/text-field.component.mjs +0 -121
- package/esm2022/lib/field-types/textarea-field.component.mjs +0 -85
- package/esm2022/lib/form/dynamic-field/dynamic-field.component.mjs +0 -151
- package/esm2022/lib/form/dynamic-form.component.mjs +0 -890
- package/esm2022/lib/form/dynamic-record-form.component.mjs +0 -494
- package/esm2022/lib/providers/provide-field-types.mjs +0 -72
- package/esm2022/lib/providers/provide-ngx-dynamic-entity.mjs +0 -27
- package/esm2022/lib/services/cascade-data.service.mjs +0 -129
- package/esm2022/lib/services/config-source.service.mjs +0 -67
- package/esm2022/lib/services/entity-ref-cache.mjs +0 -20
- package/esm2022/lib/services/entity-ref-registry.service.mjs +0 -31
- package/esm2022/lib/services/entity-ref-selection.service.mjs +0 -27
- package/esm2022/lib/services/entity-reference.service.mjs +0 -168
- package/esm2022/lib/services/field-registry.service.mjs +0 -55
- package/esm2022/lib/services/file-upload.service.mjs +0 -89
- package/esm2022/lib/services/hook-registry.service.mjs +0 -26
- package/esm2022/lib/services/lookup-registry.service.mjs +0 -246
- package/esm2022/lib/services/rbac.service.mjs +0 -46
- package/esm2022/lib/services/rules-evaluation.service.mjs +0 -27
- package/esm2022/lib/services/validation-messages.service.mjs +0 -56
- package/esm2022/lib/services/validator-registry.service.mjs +0 -99
- package/esm2022/lib/tokens/injection-tokens.mjs +0 -126
- package/esm2022/ngx-dynamic-entity.mjs +0 -5
- package/esm2022/public-api.mjs +0 -56
- package/index.d.ts +0 -5
- package/lib/field-types/array-field.component.d.ts +0 -28
- package/lib/field-types/boolean-field.component.d.ts +0 -18
- package/lib/field-types/checkbox-field.component.d.ts +0 -13
- package/lib/field-types/currency-field.component.d.ts +0 -17
- package/lib/field-types/date-field.component.d.ts +0 -15
- package/lib/field-types/dropdown-field.component.d.ts +0 -34
- package/lib/field-types/email-field.component.d.ts +0 -15
- package/lib/field-types/entity-ref-field.component.d.ts +0 -49
- package/lib/field-types/field-component.contract.d.ts +0 -49
- package/lib/field-types/file-field.component.d.ts +0 -25
- package/lib/field-types/group-field.component.d.ts +0 -17
- package/lib/field-types/image-field.component.d.ts +0 -30
- package/lib/field-types/month-year-field.component.d.ts +0 -28
- package/lib/field-types/multi-select-field.component.d.ts +0 -27
- package/lib/field-types/number-field.component.d.ts +0 -16
- package/lib/field-types/password-field.component.d.ts +0 -17
- package/lib/field-types/radio-field.component.d.ts +0 -33
- package/lib/field-types/text-field.component.d.ts +0 -23
- package/lib/field-types/textarea-field.component.d.ts +0 -14
- package/lib/form/dynamic-field/dynamic-field.component.d.ts +0 -78
- package/lib/form/dynamic-form.component.d.ts +0 -291
- package/lib/form/dynamic-record-form.component.d.ts +0 -180
- package/lib/providers/provide-field-types.d.ts +0 -24
- package/lib/providers/provide-ngx-dynamic-entity.d.ts +0 -56
- package/lib/services/cascade-data.service.d.ts +0 -45
- package/lib/services/config-source.service.d.ts +0 -14
- package/lib/services/entity-ref-cache.d.ts +0 -27
- package/lib/services/entity-ref-registry.service.d.ts +0 -21
- package/lib/services/entity-ref-selection.service.d.ts +0 -24
- package/lib/services/entity-reference.service.d.ts +0 -49
- package/lib/services/field-registry.service.d.ts +0 -32
- package/lib/services/file-upload.service.d.ts +0 -27
- package/lib/services/hook-registry.service.d.ts +0 -12
- package/lib/services/lookup-registry.service.d.ts +0 -94
- package/lib/services/rbac.service.d.ts +0 -27
- package/lib/services/rules-evaluation.service.d.ts +0 -17
- package/lib/services/validation-messages.service.d.ts +0 -43
- package/lib/services/validator-registry.service.d.ts +0 -32
- package/lib/tokens/injection-tokens.d.ts +0 -134
- package/public-api.d.ts +0 -40
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, makeEnvironmentProviders, inject, Injectable, signal, Input, ChangeDetectionStrategy, Component, DestroyRef, Injector, isDevMode, ViewContainerRef, ViewChild, forwardRef, ChangeDetectorRef, Optional, Inject, EventEmitter, computed, HostListener, ViewChildren, Output } from '@angular/core';
|
|
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, 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
|
|
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: "
|
|
227
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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: "
|
|
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: "
|
|
268
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: "
|
|
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: "
|
|
362
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: "
|
|
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: "
|
|
448
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: "
|
|
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: "
|
|
559
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: "
|
|
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: "
|
|
646
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: "
|
|
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: "
|
|
747
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: "
|
|
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: "
|
|
844
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: "
|
|
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: "
|
|
926
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: "
|
|
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: "
|
|
1028
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: "
|
|
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,
|
|
@@ -1090,6 +1090,231 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1090
1090
|
type: Input
|
|
1091
1091
|
}] } });
|
|
1092
1092
|
|
|
1093
|
+
/**
|
|
1094
|
+
* Reads a stored value into a `Date`.
|
|
1095
|
+
*
|
|
1096
|
+
* A bare calendar date is pinned to *local* midnight. `new Date('2020-01-01')` reads a
|
|
1097
|
+
* date-only string as UTC midnight, which renders as the previous day anywhere west of
|
|
1098
|
+
* Greenwich — and every legacy `datetime` value has exactly that shape, because the type
|
|
1099
|
+
* used to render through `DateFieldComponent` and its `type="date"` input.
|
|
1100
|
+
*/
|
|
1101
|
+
function parseStored(value) {
|
|
1102
|
+
if (value === null || value === undefined || value === '')
|
|
1103
|
+
return null;
|
|
1104
|
+
if (value instanceof Date)
|
|
1105
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
1106
|
+
const raw = String(value);
|
|
1107
|
+
const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw);
|
|
1108
|
+
if (dateOnly)
|
|
1109
|
+
return new Date(+dateOnly[1], +dateOnly[2] - 1, +dateOnly[3]);
|
|
1110
|
+
const parsed = new Date(raw);
|
|
1111
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
1112
|
+
}
|
|
1113
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
1114
|
+
/**
|
|
1115
|
+
* Date & Time field: a single `datetime-local` input.
|
|
1116
|
+
*
|
|
1117
|
+
* The type is stored as ISO 8601 UTC, per the same convention `date` follows, while the
|
|
1118
|
+
* input works in the viewer's local zone — `datetime-local` has no offset, so the two
|
|
1119
|
+
* conversions here are what keep a value stable across a round trip.
|
|
1120
|
+
*/
|
|
1121
|
+
class DateTimeFieldComponent {
|
|
1122
|
+
field;
|
|
1123
|
+
control;
|
|
1124
|
+
language = 'en';
|
|
1125
|
+
readonly = false;
|
|
1126
|
+
masked = false;
|
|
1127
|
+
get label() {
|
|
1128
|
+
return resolveLabel(this.field?.label, this.language);
|
|
1129
|
+
}
|
|
1130
|
+
/** Stored ISO 8601 → the `YYYY-MM-DDTHH:mm` local form the input requires. */
|
|
1131
|
+
get inputValue() {
|
|
1132
|
+
const d = parseStored(this.control?.value);
|
|
1133
|
+
if (!d)
|
|
1134
|
+
return '';
|
|
1135
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
1136
|
+
}
|
|
1137
|
+
/** The input's local `YYYY-MM-DDTHH:mm` → stored ISO 8601 UTC. */
|
|
1138
|
+
onInput(value) {
|
|
1139
|
+
if (!value) {
|
|
1140
|
+
this.control.setValue(null);
|
|
1141
|
+
this.control.markAsTouched();
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const d = new Date(value);
|
|
1145
|
+
// An unparseable value is stored verbatim rather than silently discarded, so a validator
|
|
1146
|
+
// sees what the user actually entered.
|
|
1147
|
+
this.control.setValue(Number.isNaN(d.getTime()) ? value : d.toISOString());
|
|
1148
|
+
this.control.markAsTouched();
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Displays date *and* time. `DateFieldComponent` used `toLocaleDateString()` here, so a
|
|
1152
|
+
* datetime read back through the field showed no time while the same value in the record
|
|
1153
|
+
* summary — which goes through core's `formatDisplayValue` — showed it.
|
|
1154
|
+
*/
|
|
1155
|
+
formatDateTime(value) {
|
|
1156
|
+
const d = parseStored(value);
|
|
1157
|
+
return d ? d.toLocaleString() : '—';
|
|
1158
|
+
}
|
|
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
|
+
<div class="ngx-field ngx-field--datetime"
|
|
1162
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1163
|
+
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
|
|
1164
|
+
@if (masked) {
|
|
1165
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1166
|
+
} @else if (readonly) {
|
|
1167
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ formatDateTime(control.value) }}</span>
|
|
1168
|
+
} @else {
|
|
1169
|
+
<input
|
|
1170
|
+
[id]="field.id"
|
|
1171
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
1172
|
+
type="datetime-local"
|
|
1173
|
+
[value]="inputValue"
|
|
1174
|
+
(change)="onInput($any($event.target).value)"
|
|
1175
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
1176
|
+
/>
|
|
1177
|
+
@if (control.invalid && control.touched) {
|
|
1178
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
</div>
|
|
1182
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1183
|
+
}
|
|
1184
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DateTimeFieldComponent, decorators: [{
|
|
1185
|
+
type: Component,
|
|
1186
|
+
args: [{
|
|
1187
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1188
|
+
selector: 'ngx-date-time-field',
|
|
1189
|
+
standalone: true,
|
|
1190
|
+
imports: [ReactiveFormsModule],
|
|
1191
|
+
template: `
|
|
1192
|
+
<div class="ngx-field ngx-field--datetime"
|
|
1193
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1194
|
+
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
|
|
1195
|
+
@if (masked) {
|
|
1196
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1197
|
+
} @else if (readonly) {
|
|
1198
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ formatDateTime(control.value) }}</span>
|
|
1199
|
+
} @else {
|
|
1200
|
+
<input
|
|
1201
|
+
[id]="field.id"
|
|
1202
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
1203
|
+
type="datetime-local"
|
|
1204
|
+
[value]="inputValue"
|
|
1205
|
+
(change)="onInput($any($event.target).value)"
|
|
1206
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
1207
|
+
/>
|
|
1208
|
+
@if (control.invalid && control.touched) {
|
|
1209
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
</div>
|
|
1213
|
+
`,
|
|
1214
|
+
}]
|
|
1215
|
+
}], propDecorators: { field: [{
|
|
1216
|
+
type: Input
|
|
1217
|
+
}], control: [{
|
|
1218
|
+
type: Input
|
|
1219
|
+
}], language: [{
|
|
1220
|
+
type: Input
|
|
1221
|
+
}], readonly: [{
|
|
1222
|
+
type: Input
|
|
1223
|
+
}], masked: [{
|
|
1224
|
+
type: Input
|
|
1225
|
+
}] } });
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* Time field: a bare time of day, with no date and no zone attached.
|
|
1229
|
+
*
|
|
1230
|
+
* Stored as `HH:mm` — exactly the value `<input type="time">` reads and writes, so the
|
|
1231
|
+
* control binds straight through with no conversion. That is the whole reason this is a
|
|
1232
|
+
* separate type from `datetime`: a recurring 09:00 opening time is not a moment in time,
|
|
1233
|
+
* and converting it to UTC would move it whenever the offset changed.
|
|
1234
|
+
*/
|
|
1235
|
+
class TimeFieldComponent {
|
|
1236
|
+
field;
|
|
1237
|
+
control;
|
|
1238
|
+
language = 'en';
|
|
1239
|
+
readonly = false;
|
|
1240
|
+
masked = false;
|
|
1241
|
+
get label() {
|
|
1242
|
+
return resolveLabel(this.field?.label, this.language);
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Defers to core rather than parsing `HH:mm` locally. The other date-ish components each
|
|
1246
|
+
* grew their own formatter, and `datetime` ended up showing one thing in the field and
|
|
1247
|
+
* another in the record summary because the two drifted apart.
|
|
1248
|
+
*/
|
|
1249
|
+
formatTime(value) {
|
|
1250
|
+
return formatDisplayValue('time', undefined, value, this.language);
|
|
1251
|
+
}
|
|
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
|
+
<div class="ngx-field ngx-field--time"
|
|
1255
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1256
|
+
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
|
|
1257
|
+
@if (masked) {
|
|
1258
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1259
|
+
} @else if (readonly) {
|
|
1260
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ formatTime(control.value) }}</span>
|
|
1261
|
+
} @else {
|
|
1262
|
+
<input
|
|
1263
|
+
[id]="field.id"
|
|
1264
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
1265
|
+
type="time"
|
|
1266
|
+
[formControl]="$any(control)"
|
|
1267
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
1268
|
+
/>
|
|
1269
|
+
@if (control.invalid && control.touched) {
|
|
1270
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
</div>
|
|
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
|
+
}
|
|
1276
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: TimeFieldComponent, decorators: [{
|
|
1277
|
+
type: Component,
|
|
1278
|
+
args: [{
|
|
1279
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1280
|
+
selector: 'ngx-time-field',
|
|
1281
|
+
standalone: true,
|
|
1282
|
+
imports: [ReactiveFormsModule],
|
|
1283
|
+
template: `
|
|
1284
|
+
<div class="ngx-field ngx-field--time"
|
|
1285
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1286
|
+
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
|
|
1287
|
+
@if (masked) {
|
|
1288
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1289
|
+
} @else if (readonly) {
|
|
1290
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ formatTime(control.value) }}</span>
|
|
1291
|
+
} @else {
|
|
1292
|
+
<input
|
|
1293
|
+
[id]="field.id"
|
|
1294
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
1295
|
+
type="time"
|
|
1296
|
+
[formControl]="$any(control)"
|
|
1297
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
1298
|
+
/>
|
|
1299
|
+
@if (control.invalid && control.touched) {
|
|
1300
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
</div>
|
|
1304
|
+
`,
|
|
1305
|
+
}]
|
|
1306
|
+
}], propDecorators: { field: [{
|
|
1307
|
+
type: Input
|
|
1308
|
+
}], control: [{
|
|
1309
|
+
type: Input
|
|
1310
|
+
}], language: [{
|
|
1311
|
+
type: Input
|
|
1312
|
+
}], readonly: [{
|
|
1313
|
+
type: Input
|
|
1314
|
+
}], masked: [{
|
|
1315
|
+
type: Input
|
|
1316
|
+
}] } });
|
|
1317
|
+
|
|
1093
1318
|
const MONTH_NAMES = [
|
|
1094
1319
|
'January', 'February', 'March', 'April', 'May', 'June',
|
|
1095
1320
|
'July', 'August', 'September', 'October', 'November', 'December',
|
|
@@ -1137,8 +1362,8 @@ class MonthYearFieldComponent {
|
|
|
1137
1362
|
const monthName = MONTH_NAMES[parseInt(monthNum, 10) - 1];
|
|
1138
1363
|
return monthName && year ? `${monthName} ${year}` : String(value);
|
|
1139
1364
|
}
|
|
1140
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1141
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: `
|
|
1142
1367
|
<div class="ngx-field ngx-field--month-year"
|
|
1143
1368
|
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1144
1369
|
<label class="ngx-field__label">{{ label }}</label>
|
|
@@ -1178,7 +1403,7 @@ class MonthYearFieldComponent {
|
|
|
1178
1403
|
</div>
|
|
1179
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 });
|
|
1180
1405
|
}
|
|
1181
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1406
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MonthYearFieldComponent, decorators: [{
|
|
1182
1407
|
type: Component,
|
|
1183
1408
|
args: [{
|
|
1184
1409
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -1440,10 +1665,10 @@ class LookupRegistryService {
|
|
|
1440
1665
|
value !== null &&
|
|
1441
1666
|
typeof value.subscribe === 'function');
|
|
1442
1667
|
}
|
|
1443
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1444
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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' });
|
|
1445
1670
|
}
|
|
1446
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1671
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: LookupRegistryService, decorators: [{
|
|
1447
1672
|
type: Injectable,
|
|
1448
1673
|
args: [{ providedIn: 'root' }]
|
|
1449
1674
|
}] });
|
|
@@ -1503,7 +1728,7 @@ class DropdownFieldComponent {
|
|
|
1503
1728
|
readonly = false;
|
|
1504
1729
|
masked = false;
|
|
1505
1730
|
/** Inline `options`, or the field's named list resolved through the registry (§6.3). */
|
|
1506
|
-
options = signal([]);
|
|
1731
|
+
options = signal([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
1507
1732
|
compareFn = (o1, o2) => valuesMatch(o1, o2, this.language);
|
|
1508
1733
|
isObjectVal(option) {
|
|
1509
1734
|
const val = getOptionStoredValue(option);
|
|
@@ -1535,7 +1760,7 @@ class DropdownFieldComponent {
|
|
|
1535
1760
|
const cached = this.lookups.labelFor(this.field?.listName, value, this.language);
|
|
1536
1761
|
if (cached)
|
|
1537
1762
|
return cached;
|
|
1538
|
-
return typeof value === 'object' ? resolveLabel(value, this.language) : (value ?? '—');
|
|
1763
|
+
return typeof value === 'object' ? resolveLabel(value, this.language) : String(value ?? '—');
|
|
1539
1764
|
}
|
|
1540
1765
|
get errorMessage() {
|
|
1541
1766
|
if (!this.control || !this.control.errors || !this.control.touched)
|
|
@@ -1544,8 +1769,8 @@ class DropdownFieldComponent {
|
|
|
1544
1769
|
// dropdown than "This field is required", and both stay independently overridable.
|
|
1545
1770
|
return this.messages.resolve(this.control.errors, this.language, [['required', 'requiredSelection']], 'invalidSelection');
|
|
1546
1771
|
}
|
|
1547
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1548
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: `
|
|
1549
1774
|
<div
|
|
1550
1775
|
class="ngx-field ngx-field--dropdown"
|
|
1551
1776
|
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
|
|
@@ -1587,7 +1812,7 @@ class DropdownFieldComponent {
|
|
|
1587
1812
|
</div>
|
|
1588
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 });
|
|
1589
1814
|
}
|
|
1590
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1815
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DropdownFieldComponent, decorators: [{
|
|
1591
1816
|
type: Component,
|
|
1592
1817
|
args: [{
|
|
1593
1818
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -1672,7 +1897,7 @@ class RadioFieldComponent {
|
|
|
1672
1897
|
readonly = false;
|
|
1673
1898
|
masked = false;
|
|
1674
1899
|
/** Inline `options`, or the field's named list resolved through the registry (§6.3). */
|
|
1675
|
-
options = signal([]);
|
|
1900
|
+
options = signal([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
1676
1901
|
get label() {
|
|
1677
1902
|
return resolveLabel(this.field?.label, this.language);
|
|
1678
1903
|
}
|
|
@@ -1707,10 +1932,10 @@ class RadioFieldComponent {
|
|
|
1707
1932
|
const cached = this.lookups.labelFor(this.field?.listName, value, this.language);
|
|
1708
1933
|
if (cached)
|
|
1709
1934
|
return cached;
|
|
1710
|
-
return typeof value === 'object' ? resolveLabel(value, this.language) : (value ?? '—');
|
|
1935
|
+
return typeof value === 'object' ? resolveLabel(value, this.language) : String(value ?? '—');
|
|
1711
1936
|
}
|
|
1712
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1713
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: `
|
|
1714
1939
|
<div class="ngx-field ngx-field--radio"
|
|
1715
1940
|
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1716
1941
|
<fieldset class="ngx-field__fieldset">
|
|
@@ -1743,7 +1968,7 @@ class RadioFieldComponent {
|
|
|
1743
1968
|
</div>
|
|
1744
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 });
|
|
1745
1970
|
}
|
|
1746
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
1971
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RadioFieldComponent, decorators: [{
|
|
1747
1972
|
type: Component,
|
|
1748
1973
|
args: [{
|
|
1749
1974
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -1818,7 +2043,7 @@ class MultiSelectFieldComponent {
|
|
|
1818
2043
|
readonly = false;
|
|
1819
2044
|
masked = false;
|
|
1820
2045
|
/** Inline `options`, or the field's named list resolved through the registry (§6.3). */
|
|
1821
|
-
options = signal([]);
|
|
2046
|
+
options = signal([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
1822
2047
|
compareFn = (o1, o2) => valuesMatch(o1, o2, this.language);
|
|
1823
2048
|
isObjectVal(option) {
|
|
1824
2049
|
const val = getOptionStoredValue(option);
|
|
@@ -1845,12 +2070,12 @@ class MultiSelectFieldComponent {
|
|
|
1845
2070
|
const cached = this.lookups.labelFor(this.field?.listName, v, this.language);
|
|
1846
2071
|
if (cached)
|
|
1847
2072
|
return cached;
|
|
1848
|
-
return typeof v === 'object' ? resolveLabel(v, this.language) : String(v);
|
|
2073
|
+
return typeof v === 'object' && v !== null ? resolveLabel(v, this.language) : String(v);
|
|
1849
2074
|
})
|
|
1850
2075
|
.join(', ');
|
|
1851
2076
|
}
|
|
1852
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1853
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: `
|
|
1854
2079
|
<div class="ngx-field ngx-field--multiSelect"
|
|
1855
2080
|
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1856
2081
|
<label class="ngx-field__label">{{ label }}</label>
|
|
@@ -1883,7 +2108,7 @@ class MultiSelectFieldComponent {
|
|
|
1883
2108
|
</div>
|
|
1884
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 });
|
|
1885
2110
|
}
|
|
1886
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2111
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MultiSelectFieldComponent, decorators: [{
|
|
1887
2112
|
type: Component,
|
|
1888
2113
|
args: [{
|
|
1889
2114
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -1955,10 +2180,10 @@ class EntityRefRegistryService {
|
|
|
1955
2180
|
has(entityKey) {
|
|
1956
2181
|
return this.registry.has(entityKey);
|
|
1957
2182
|
}
|
|
1958
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
1959
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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' });
|
|
1960
2185
|
}
|
|
1961
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2186
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityRefRegistryService, decorators: [{
|
|
1962
2187
|
type: Injectable,
|
|
1963
2188
|
args: [{ providedIn: 'root' }]
|
|
1964
2189
|
}] });
|
|
@@ -2118,10 +2343,10 @@ class EntityReferenceService {
|
|
|
2118
2343
|
matches(a, b, lang = 'en') {
|
|
2119
2344
|
return valuesMatch(a, b, lang);
|
|
2120
2345
|
}
|
|
2121
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2122
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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' });
|
|
2123
2348
|
}
|
|
2124
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2349
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityReferenceService, decorators: [{
|
|
2125
2350
|
type: Injectable,
|
|
2126
2351
|
args: [{ providedIn: 'root' }]
|
|
2127
2352
|
}] });
|
|
@@ -2242,10 +2467,10 @@ class CascadeDataService {
|
|
|
2242
2467
|
walkTabs(config?.tabs);
|
|
2243
2468
|
return found;
|
|
2244
2469
|
}
|
|
2245
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2246
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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' });
|
|
2247
2472
|
}
|
|
2248
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2473
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: CascadeDataService, decorators: [{
|
|
2249
2474
|
type: Injectable,
|
|
2250
2475
|
args: [{ providedIn: 'root' }]
|
|
2251
2476
|
}] });
|
|
@@ -2266,10 +2491,10 @@ class EntityRefSelectionService {
|
|
|
2266
2491
|
emit(fieldId, option) {
|
|
2267
2492
|
this.subject.next({ fieldId, option });
|
|
2268
2493
|
}
|
|
2269
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2270
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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' });
|
|
2271
2496
|
}
|
|
2272
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2497
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityRefSelectionService, decorators: [{
|
|
2273
2498
|
type: Injectable,
|
|
2274
2499
|
args: [{ providedIn: 'root' }]
|
|
2275
2500
|
}] });
|
|
@@ -2291,10 +2516,10 @@ class EntityRefFieldComponent {
|
|
|
2291
2516
|
cascade = inject(CascadeDataService);
|
|
2292
2517
|
selectionBus = inject(EntityRefSelectionService);
|
|
2293
2518
|
destroyRef = inject(DestroyRef);
|
|
2294
|
-
options = signal([]);
|
|
2295
|
-
loading = signal(false);
|
|
2519
|
+
options = signal([], ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
2520
|
+
loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
2296
2521
|
/** True when this field cascades but its parent has no value yet. */
|
|
2297
|
-
awaitingParent = signal(false);
|
|
2522
|
+
awaitingParent = signal(false, ...(ngDevMode ? [{ debugName: "awaitingParent" }] : /* istanbul ignore next */ []));
|
|
2298
2523
|
get label() {
|
|
2299
2524
|
return resolveLabel(this.field?.label, this.language);
|
|
2300
2525
|
}
|
|
@@ -2373,8 +2598,8 @@ class EntityRefFieldComponent {
|
|
|
2373
2598
|
void this.reload();
|
|
2374
2599
|
});
|
|
2375
2600
|
}
|
|
2376
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2377
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: `
|
|
2378
2603
|
<div class="ngx-field ngx-field--entity-ref"
|
|
2379
2604
|
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2380
2605
|
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
@@ -2408,7 +2633,7 @@ class EntityRefFieldComponent {
|
|
|
2408
2633
|
</div>
|
|
2409
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 });
|
|
2410
2635
|
}
|
|
2411
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2636
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityRefFieldComponent, decorators: [{
|
|
2412
2637
|
type: Component,
|
|
2413
2638
|
args: [{
|
|
2414
2639
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -2505,10 +2730,10 @@ class FieldRegistryService {
|
|
|
2505
2730
|
registeredTypes() {
|
|
2506
2731
|
return [...new Set([...this.dynamicRegistry.keys(), ...this.consumerRegistry.keys()])].sort();
|
|
2507
2732
|
}
|
|
2508
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2509
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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' });
|
|
2510
2735
|
}
|
|
2511
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2736
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldRegistryService, decorators: [{
|
|
2512
2737
|
type: Injectable,
|
|
2513
2738
|
args: [{ providedIn: 'root' }]
|
|
2514
2739
|
}], ctorParameters: () => [] });
|
|
@@ -2547,10 +2772,10 @@ class RbacService {
|
|
|
2547
2772
|
canDelete: this.hasPermission(userRoles, config.permissions?.delete),
|
|
2548
2773
|
};
|
|
2549
2774
|
}
|
|
2550
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2551
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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' });
|
|
2552
2777
|
}
|
|
2553
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2778
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RbacService, decorators: [{
|
|
2554
2779
|
type: Injectable,
|
|
2555
2780
|
args: [{ providedIn: 'root' }]
|
|
2556
2781
|
}] });
|
|
@@ -2672,12 +2897,13 @@ class DynamicFieldComponent {
|
|
|
2672
2897
|
ref.setInput('readonly', this.readonly || !!this.field.readonly);
|
|
2673
2898
|
ref.setInput('masked', masked);
|
|
2674
2899
|
}
|
|
2675
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2676
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "
|
|
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 });
|
|
2677
2902
|
}
|
|
2678
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2903
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFieldComponent, decorators: [{
|
|
2679
2904
|
type: Component,
|
|
2680
2905
|
args: [{
|
|
2906
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2681
2907
|
selector: 'ngx-dynamic-field',
|
|
2682
2908
|
standalone: true,
|
|
2683
2909
|
template: `<ng-container #fieldHost></ng-container>`,
|
|
@@ -2717,8 +2943,8 @@ class GroupFieldComponent {
|
|
|
2717
2943
|
const group = this.control;
|
|
2718
2944
|
return group?.get(childId);
|
|
2719
2945
|
}
|
|
2720
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2721
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: `
|
|
2722
2948
|
<fieldset class="ngx-field ngx-field--group"
|
|
2723
2949
|
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2724
2950
|
<legend class="ngx-field__legend">{{ label }}</legend>
|
|
@@ -2743,7 +2969,7 @@ class GroupFieldComponent {
|
|
|
2743
2969
|
</fieldset>
|
|
2744
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 });
|
|
2745
2971
|
}
|
|
2746
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
2972
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: GroupFieldComponent, decorators: [{
|
|
2747
2973
|
type: Component,
|
|
2748
2974
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-group-field', standalone: true, imports: [ReactiveFormsModule, forwardRef(() => DynamicFieldComponent)], template: `
|
|
2749
2975
|
<fieldset class="ngx-field ngx-field--group"
|
|
@@ -2872,10 +3098,10 @@ class ValidatorRegistryService {
|
|
|
2872
3098
|
}
|
|
2873
3099
|
return out;
|
|
2874
3100
|
}
|
|
2875
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2876
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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' });
|
|
2877
3103
|
}
|
|
2878
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3104
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ValidatorRegistryService, decorators: [{
|
|
2879
3105
|
type: Injectable,
|
|
2880
3106
|
args: [{ providedIn: 'root' }]
|
|
2881
3107
|
}] });
|
|
@@ -2920,8 +3146,8 @@ class ArrayFieldComponent {
|
|
|
2920
3146
|
this.formArray.removeAt(index);
|
|
2921
3147
|
this.cdr.markForCheck();
|
|
2922
3148
|
}
|
|
2923
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
2924
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: `
|
|
2925
3151
|
<div class="ngx-field ngx-field--array"
|
|
2926
3152
|
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2927
3153
|
<div class="ngx-field__array-header">
|
|
@@ -2973,7 +3199,7 @@ class ArrayFieldComponent {
|
|
|
2973
3199
|
</div>
|
|
2974
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 });
|
|
2975
3201
|
}
|
|
2976
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3202
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ArrayFieldComponent, decorators: [{
|
|
2977
3203
|
type: Component,
|
|
2978
3204
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-array-field', standalone: true, imports: [ReactiveFormsModule, forwardRef(() => DynamicFieldComponent)], template: `
|
|
2979
3205
|
<div class="ngx-field ngx-field--array"
|
|
@@ -3120,10 +3346,10 @@ class FileUploadService {
|
|
|
3120
3346
|
value !== null &&
|
|
3121
3347
|
typeof value.subscribe === 'function');
|
|
3122
3348
|
}
|
|
3123
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3124
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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' });
|
|
3125
3351
|
}
|
|
3126
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3352
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FileUploadService, decorators: [{
|
|
3127
3353
|
type: Injectable,
|
|
3128
3354
|
args: [{ providedIn: 'root' }]
|
|
3129
3355
|
}] });
|
|
@@ -3141,8 +3367,8 @@ class ImageFieldComponent {
|
|
|
3141
3367
|
readonly = false;
|
|
3142
3368
|
masked = false;
|
|
3143
3369
|
uploads = inject(FileUploadService);
|
|
3144
|
-
uploading = signal(false);
|
|
3145
|
-
uploadError = signal(null);
|
|
3370
|
+
uploading = signal(false, ...(ngDevMode ? [{ debugName: "uploading" }] : /* istanbul ignore next */ []));
|
|
3371
|
+
uploadError = signal(null, ...(ngDevMode ? [{ debugName: "uploadError" }] : /* istanbul ignore next */ []));
|
|
3146
3372
|
/** Cached so an object URL is minted once per File, not once per change-detection pass. */
|
|
3147
3373
|
previewSource = null;
|
|
3148
3374
|
previewCache = null;
|
|
@@ -3181,8 +3407,8 @@ class ImageFieldComponent {
|
|
|
3181
3407
|
ngOnDestroy() {
|
|
3182
3408
|
this.uploads.revokePreviewUrl(this.previewCache);
|
|
3183
3409
|
}
|
|
3184
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3185
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: `
|
|
3186
3412
|
<div class="ngx-field ngx-field--image"
|
|
3187
3413
|
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
3188
3414
|
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
@@ -3228,7 +3454,7 @@ class ImageFieldComponent {
|
|
|
3228
3454
|
</div>
|
|
3229
3455
|
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3230
3456
|
}
|
|
3231
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3457
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ImageFieldComponent, decorators: [{
|
|
3232
3458
|
type: Component,
|
|
3233
3459
|
args: [{
|
|
3234
3460
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -3304,8 +3530,8 @@ class FileFieldComponent {
|
|
|
3304
3530
|
readonly = false;
|
|
3305
3531
|
masked = false;
|
|
3306
3532
|
uploads = inject(FileUploadService);
|
|
3307
|
-
uploading = signal(false);
|
|
3308
|
-
uploadError = signal(null);
|
|
3533
|
+
uploading = signal(false, ...(ngDevMode ? [{ debugName: "uploading" }] : /* istanbul ignore next */ []));
|
|
3534
|
+
uploadError = signal(null, ...(ngDevMode ? [{ debugName: "uploadError" }] : /* istanbul ignore next */ []));
|
|
3309
3535
|
get label() {
|
|
3310
3536
|
return resolveLabel(this.field?.label, this.language);
|
|
3311
3537
|
}
|
|
@@ -3338,8 +3564,8 @@ class FileFieldComponent {
|
|
|
3338
3564
|
this.control.setValue(null);
|
|
3339
3565
|
this.control.markAsTouched();
|
|
3340
3566
|
}
|
|
3341
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3342
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "
|
|
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: `
|
|
3343
3569
|
<div class="ngx-field ngx-field--file"
|
|
3344
3570
|
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
3345
3571
|
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
@@ -3383,7 +3609,7 @@ class FileFieldComponent {
|
|
|
3383
3609
|
</div>
|
|
3384
3610
|
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3385
3611
|
}
|
|
3386
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3612
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FileFieldComponent, decorators: [{
|
|
3387
3613
|
type: Component,
|
|
3388
3614
|
args: [{
|
|
3389
3615
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -3475,7 +3701,8 @@ function builtInFieldTypes() {
|
|
|
3475
3701
|
checkbox: CheckboxFieldComponent,
|
|
3476
3702
|
boolean: BooleanFieldComponent,
|
|
3477
3703
|
date: DateFieldComponent,
|
|
3478
|
-
datetime:
|
|
3704
|
+
datetime: DateTimeFieldComponent,
|
|
3705
|
+
time: TimeFieldComponent,
|
|
3479
3706
|
monthYear: MonthYearFieldComponent,
|
|
3480
3707
|
dropdown: DropdownFieldComponent,
|
|
3481
3708
|
radio: RadioFieldComponent,
|
|
@@ -3513,10 +3740,10 @@ class HookRegistryService {
|
|
|
3513
3740
|
return data;
|
|
3514
3741
|
return hook(data, context);
|
|
3515
3742
|
}
|
|
3516
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3517
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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' });
|
|
3518
3745
|
}
|
|
3519
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3746
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: HookRegistryService, decorators: [{
|
|
3520
3747
|
type: Injectable,
|
|
3521
3748
|
args: [{ providedIn: 'root' }]
|
|
3522
3749
|
}] });
|
|
@@ -3537,10 +3764,10 @@ class RulesEvaluationService {
|
|
|
3537
3764
|
filterForTab(rules, tabId, config) {
|
|
3538
3765
|
return filterRulesForTab(rules, tabId, config);
|
|
3539
3766
|
}
|
|
3540
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3541
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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' });
|
|
3542
3769
|
}
|
|
3543
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3770
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RulesEvaluationService, decorators: [{
|
|
3544
3771
|
type: Injectable,
|
|
3545
3772
|
args: [{ providedIn: 'root' }]
|
|
3546
3773
|
}] });
|
|
@@ -3595,10 +3822,10 @@ class ConfigSourceService {
|
|
|
3595
3822
|
this.cache.clear();
|
|
3596
3823
|
}
|
|
3597
3824
|
}
|
|
3598
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
3599
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
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' });
|
|
3600
3827
|
}
|
|
3601
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
3828
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: ConfigSourceService, decorators: [{
|
|
3602
3829
|
type: Injectable,
|
|
3603
3830
|
args: [{ providedIn: 'root' }]
|
|
3604
3831
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
@@ -3675,6 +3902,7 @@ class DynamicFormComponent {
|
|
|
3675
3902
|
*/
|
|
3676
3903
|
saveRejected = new EventEmitter();
|
|
3677
3904
|
// ─── Services ─────────────────────────────────────────────────────────────
|
|
3905
|
+
cdr = inject(ChangeDetectorRef);
|
|
3678
3906
|
fb = inject(FormBuilder);
|
|
3679
3907
|
validatorRegistry = inject(ValidatorRegistryService);
|
|
3680
3908
|
hookRegistry = inject(HookRegistryService);
|
|
@@ -3685,14 +3913,14 @@ class DynamicFormComponent {
|
|
|
3685
3913
|
migrations = inject(RECORD_MIGRATIONS, { optional: true }) ?? [];
|
|
3686
3914
|
Object = Object;
|
|
3687
3915
|
// ─── Signals (local reactive state) ───────────────────────────────────────
|
|
3688
|
-
activeTab = signal('');
|
|
3689
|
-
activeSubTab = signal('');
|
|
3690
|
-
isSaving = signal(false);
|
|
3691
|
-
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 */ []));
|
|
3692
3920
|
/** Baseline captured at first build, used when `originalData` is not supplied. */
|
|
3693
|
-
sessionBaseline = signal({});
|
|
3921
|
+
sessionBaseline = signal({}, ...(ngDevMode ? [{ debugName: "sessionBaseline" }] : /* istanbul ignore next */ []));
|
|
3694
3922
|
/** Critical fields the user has explicitly unlocked for editing. */
|
|
3695
|
-
unlockedFields = signal(new Set());
|
|
3923
|
+
unlockedFields = signal(new Set(), ...(ngDevMode ? [{ debugName: "unlockedFields" }] : /* istanbul ignore next */ []));
|
|
3696
3924
|
/** The active tab's field panel, focused after a tab switch. */
|
|
3697
3925
|
formPanel;
|
|
3698
3926
|
/** The hosted field components, so an external control-state change can reach them. */
|
|
@@ -3700,6 +3928,7 @@ class DynamicFormComponent {
|
|
|
3700
3928
|
// ─── Form ─────────────────────────────────────────────────────────────────
|
|
3701
3929
|
form;
|
|
3702
3930
|
valueSub;
|
|
3931
|
+
statusSub;
|
|
3703
3932
|
selectionSub;
|
|
3704
3933
|
/** Previous values, for detecting `patchOnTrue` false→true transitions. */
|
|
3705
3934
|
previousValues = {};
|
|
@@ -3713,7 +3942,7 @@ class DynamicFormComponent {
|
|
|
3713
3942
|
}
|
|
3714
3943
|
}
|
|
3715
3944
|
// ─── Computed ─────────────────────────────────────────────────────────────
|
|
3716
|
-
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 */ []));
|
|
3717
3946
|
/**
|
|
3718
3947
|
* Critical fields whose value differs from the session baseline.
|
|
3719
3948
|
* These drive the deferred `VALUE_CHANGED` banner — a critical edit is announced once,
|
|
@@ -3725,7 +3954,7 @@ class DynamicFormComponent {
|
|
|
3725
3954
|
return [];
|
|
3726
3955
|
const values = this.formValues();
|
|
3727
3956
|
return this.allFields().filter(f => f.criticalField && !this.sameValue(values[f.id], baseline[f.id]));
|
|
3728
|
-
});
|
|
3957
|
+
}, ...(ngDevMode ? [{ debugName: "changedCriticalFields" }] : /* istanbul ignore next */ []));
|
|
3729
3958
|
get tabs() {
|
|
3730
3959
|
return this.config?.tabs || [];
|
|
3731
3960
|
}
|
|
@@ -3766,8 +3995,8 @@ class DynamicFormComponent {
|
|
|
3766
3995
|
return [];
|
|
3767
3996
|
const rawFields = active ? (active.fields || []) : ((this.config?.tabs || []).flatMap(t => t.fields || []));
|
|
3768
3997
|
const currentValues = this.formValues();
|
|
3769
|
-
const hiddenFields = this.ruleResult().hiddenFields;
|
|
3770
|
-
return rawFields.filter(field => evaluateFieldVisibility(field, currentValues) && !
|
|
3998
|
+
const hiddenFields = new Set(this.ruleResult().hiddenFields);
|
|
3999
|
+
return rawFields.filter(field => evaluateFieldVisibility(field, currentValues) && !this.namesField(hiddenFields, field));
|
|
3771
4000
|
}
|
|
3772
4001
|
/**
|
|
3773
4002
|
* Entity-level permissions for the current roles.
|
|
@@ -3864,6 +4093,7 @@ class DynamicFormComponent {
|
|
|
3864
4093
|
}
|
|
3865
4094
|
ngOnDestroy() {
|
|
3866
4095
|
this.valueSub?.unsubscribe();
|
|
4096
|
+
this.statusSub?.unsubscribe();
|
|
3867
4097
|
this.selectionSub?.unsubscribe();
|
|
3868
4098
|
}
|
|
3869
4099
|
setActiveTab(tabId) {
|
|
@@ -3946,6 +4176,7 @@ class DynamicFormComponent {
|
|
|
3946
4176
|
};
|
|
3947
4177
|
buildTabControls(this.config.tabs || [], group);
|
|
3948
4178
|
this.form = this.fb.group(group);
|
|
4179
|
+
this.warnAmbiguousRuleReferences();
|
|
3949
4180
|
if (this.initialData) {
|
|
3950
4181
|
this.patchForm(this.upgradeRecord(this.initialData));
|
|
3951
4182
|
}
|
|
@@ -3971,6 +4202,12 @@ class DynamicFormComponent {
|
|
|
3971
4202
|
const changes$ = this.changeDebounceMs > 0
|
|
3972
4203
|
? this.form.valueChanges.pipe(debounceTime(this.changeDebounceMs))
|
|
3973
4204
|
: this.form.valueChanges;
|
|
4205
|
+
// Validity is not value: an async validator settling flips `pending` and then `invalid`
|
|
4206
|
+
// with no value change and no template event, so under OnPush the Save button would
|
|
4207
|
+
// never re-enable. submitBlocked reads form.pending and form.invalid, so this is what
|
|
4208
|
+
// keeps it honest.
|
|
4209
|
+
this.statusSub?.unsubscribe();
|
|
4210
|
+
this.statusSub = this.form.statusChanges.subscribe(() => this.cdr.markForCheck());
|
|
3974
4211
|
this.valueSub = changes$.subscribe(() => {
|
|
3975
4212
|
const flattened = this.flattenFormValues();
|
|
3976
4213
|
this.formValues.set(flattened);
|
|
@@ -4166,12 +4403,89 @@ class DynamicFormComponent {
|
|
|
4166
4403
|
record = normalizeArrayStructures(record, this.config);
|
|
4167
4404
|
return record;
|
|
4168
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
|
+
}
|
|
4169
4478
|
flattenFormValues() {
|
|
4170
4479
|
const out = {};
|
|
4171
|
-
for (const
|
|
4172
|
-
const
|
|
4173
|
-
if (
|
|
4174
|
-
|
|
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;
|
|
4175
4489
|
}
|
|
4176
4490
|
return out;
|
|
4177
4491
|
}
|
|
@@ -4278,7 +4592,7 @@ class DynamicFormComponent {
|
|
|
4278
4592
|
const ctrl = this.getControl(field.id);
|
|
4279
4593
|
if (!ctrl)
|
|
4280
4594
|
continue;
|
|
4281
|
-
const hidden =
|
|
4595
|
+
const hidden = this.namesField(hiddenByRule, field) || !evaluateFieldVisibility(field, values);
|
|
4282
4596
|
if (hidden) {
|
|
4283
4597
|
if (ctrl.enabled)
|
|
4284
4598
|
ctrl.disable({ emitEvent: false });
|
|
@@ -4431,12 +4745,12 @@ class DynamicFormComponent {
|
|
|
4431
4745
|
this.unlockedFields.set(new Set());
|
|
4432
4746
|
this.formReset.emit();
|
|
4433
4747
|
}
|
|
4434
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
4435
|
-
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 >\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"] }] });
|
|
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 });
|
|
4436
4750
|
}
|
|
4437
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
4751
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicFormComponent, decorators: [{
|
|
4438
4752
|
type: Component,
|
|
4439
|
-
args: [{ 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 >\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
|
|
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"] }]
|
|
4440
4754
|
}], propDecorators: { config: [{
|
|
4441
4755
|
type: Input
|
|
4442
4756
|
}], rules: [{
|
|
@@ -4522,8 +4836,10 @@ class DynamicRecordFormComponent {
|
|
|
4522
4836
|
sectionSave = new EventEmitter();
|
|
4523
4837
|
dynamicFormComp;
|
|
4524
4838
|
rulesEvaluation = inject(RulesEvaluationService);
|
|
4525
|
-
|
|
4526
|
-
|
|
4839
|
+
host = inject(ElementRef);
|
|
4840
|
+
injector = inject(Injector);
|
|
4841
|
+
currentData = signal({}, ...(ngDevMode ? [{ debugName: "currentData" }] : /* istanbul ignore next */ []));
|
|
4842
|
+
originalBaseline = signal({}, ...(ngDevMode ? [{ debugName: "originalBaseline" }] : /* istanbul ignore next */ []));
|
|
4527
4843
|
/**
|
|
4528
4844
|
* Info banners the user has dismissed, by field id.
|
|
4529
4845
|
*
|
|
@@ -4531,7 +4847,7 @@ class DynamicRecordFormComponent {
|
|
|
4531
4847
|
* value changes. Dismissal is keyed by field so re-triggering a different field's rule
|
|
4532
4848
|
* still shows. Re-armed when a different record is loaded.
|
|
4533
4849
|
*/
|
|
4534
|
-
dismissed = signal(new Set());
|
|
4850
|
+
dismissed = signal(new Set(), ...(ngDevMode ? [{ debugName: "dismissed" }] : /* istanbul ignore next */ []));
|
|
4535
4851
|
/**
|
|
4536
4852
|
* Info banners from the rules engine that are still showing.
|
|
4537
4853
|
*
|
|
@@ -4561,8 +4877,8 @@ class DynamicRecordFormComponent {
|
|
|
4561
4877
|
* building a row's FormGroup inside the tab view races the tab's own initialisation, so
|
|
4562
4878
|
* the row form is kept detached and only pushed into the FormArray on save.
|
|
4563
4879
|
*/
|
|
4564
|
-
inlineRowField = signal(null);
|
|
4565
|
-
inlineRowIndex = signal(null);
|
|
4880
|
+
inlineRowField = signal(null, ...(ngDevMode ? [{ debugName: "inlineRowField" }] : /* istanbul ignore next */ []));
|
|
4881
|
+
inlineRowIndex = signal(null, ...(ngDevMode ? [{ debugName: "inlineRowIndex" }] : /* istanbul ignore next */ []));
|
|
4566
4882
|
inlineRowForm = null;
|
|
4567
4883
|
/** `array` fields on the tab currently shown, so the template can list their rows. */
|
|
4568
4884
|
get arrayFieldsForActiveTab() {
|
|
@@ -4652,7 +4968,7 @@ class DynamicRecordFormComponent {
|
|
|
4652
4968
|
* validates only that section on save — a required field on an untouched tab must not
|
|
4653
4969
|
* block saving the tab you are actually working on.
|
|
4654
4970
|
*/
|
|
4655
|
-
editingTabId = signal(null);
|
|
4971
|
+
editingTabId = signal(null, ...(ngDevMode ? [{ debugName: "editingTabId" }] : /* istanbul ignore next */ []));
|
|
4656
4972
|
/**
|
|
4657
4973
|
* The tab the inner form is showing.
|
|
4658
4974
|
*
|
|
@@ -4661,7 +4977,7 @@ class DynamicRecordFormComponent {
|
|
|
4661
4977
|
* value mid-pass — an ExpressionChangedAfterItHasBeenChecked error. Seeded from the
|
|
4662
4978
|
* config and kept in step by the child's `activeTabChange` output.
|
|
4663
4979
|
*/
|
|
4664
|
-
activeTabIdSig = signal(null);
|
|
4980
|
+
activeTabIdSig = signal(null, ...(ngDevMode ? [{ debugName: "activeTabIdSig" }] : /* istanbul ignore next */ []));
|
|
4665
4981
|
get activeTabId() {
|
|
4666
4982
|
return this.activeTabIdSig();
|
|
4667
4983
|
}
|
|
@@ -4678,7 +4994,7 @@ class DynamicRecordFormComponent {
|
|
|
4678
4994
|
return this.config?.tabs?.find(t => t.visibility !== false)?.id ?? null;
|
|
4679
4995
|
}
|
|
4680
4996
|
/** Errors from the last save attempt, by field id. */
|
|
4681
|
-
sectionErrors = signal({});
|
|
4997
|
+
sectionErrors = signal({}, ...(ngDevMode ? [{ debugName: "sectionErrors" }] : /* istanbul ignore next */ []));
|
|
4682
4998
|
/** Template access to `Object.keys` for iterating the error map. */
|
|
4683
4999
|
Object = Object;
|
|
4684
5000
|
get isEditingActiveTab() {
|
|
@@ -4873,7 +5189,7 @@ class DynamicRecordFormComponent {
|
|
|
4873
5189
|
if (!orig || !curr || Object.keys(orig).length === 0)
|
|
4874
5190
|
return false;
|
|
4875
5191
|
return JSON.stringify(orig) !== JSON.stringify(curr);
|
|
4876
|
-
});
|
|
5192
|
+
}, ...(ngDevMode ? [{ debugName: "isModified" }] : /* istanbul ignore next */ []));
|
|
4877
5193
|
summaryFields = computed(() => {
|
|
4878
5194
|
const fields = [];
|
|
4879
5195
|
const walkTabs = (tabs = []) => {
|
|
@@ -4888,7 +5204,7 @@ class DynamicRecordFormComponent {
|
|
|
4888
5204
|
};
|
|
4889
5205
|
walkTabs(this.config?.tabs);
|
|
4890
5206
|
return fields;
|
|
4891
|
-
});
|
|
5207
|
+
}, ...(ngDevMode ? [{ debugName: "summaryFields" }] : /* istanbul ignore next */ []));
|
|
4892
5208
|
onFormChange(data) {
|
|
4893
5209
|
// Baseline is seeded from `initialData` in ngOnChanges; only fill it here when the
|
|
4894
5210
|
// record was opened empty (create flow), so the first emitted shape becomes the baseline.
|
|
@@ -4911,29 +5227,64 @@ class DynamicRecordFormComponent {
|
|
|
4911
5227
|
formatFieldValue(field) {
|
|
4912
5228
|
return formatDisplayValue(field.type, field.options, this.fieldValue(field.id), this.language);
|
|
4913
5229
|
}
|
|
4914
|
-
|
|
4915
|
-
|
|
5230
|
+
/**
|
|
5231
|
+
* Finds which tab — and, where the field lives one level down, which sub-tab — owns a
|
|
5232
|
+
* field. The walk used to check top-level `fields` only, so every field in a sub-tab was
|
|
5233
|
+
* simply not found and the jump did nothing at all.
|
|
5234
|
+
*/
|
|
5235
|
+
locateField(fieldId) {
|
|
4916
5236
|
for (const tab of this.config?.tabs || []) {
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
4924
|
-
el.focus();
|
|
4925
|
-
}
|
|
4926
|
-
}, 50);
|
|
4927
|
-
break;
|
|
5237
|
+
if ((tab.fields || []).some(f => f.id === fieldId))
|
|
5238
|
+
return { tabId: tab.id };
|
|
5239
|
+
for (const child of tab.children || []) {
|
|
5240
|
+
if ((child.fields || []).some(f => f.id === fieldId)) {
|
|
5241
|
+
return { tabId: tab.id, subTabId: child.id };
|
|
5242
|
+
}
|
|
4928
5243
|
}
|
|
4929
5244
|
}
|
|
5245
|
+
return null;
|
|
4930
5246
|
}
|
|
4931
|
-
|
|
4932
|
-
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"] }] });
|
|
5247
|
+
/**
|
|
5248
|
+
* Switches to the tab holding `fieldId`, then scrolls it into view and moves focus to it.
|
|
5249
|
+
*
|
|
5250
|
+
* The wait for the new tab to render used to be a 50 ms `setTimeout`, which was wrong three
|
|
5251
|
+
* ways: `document` is undefined on a server render, 50 ms is a guess that a large tab can
|
|
5252
|
+
* outrun, and nothing cancelled the timer if the component went away first.
|
|
5253
|
+
* `afterNextRender` fixes all three — it never runs on the server, it runs when the panel
|
|
5254
|
+
* has actually rendered rather than when a guess expires, and it is tied to the injector so
|
|
5255
|
+
* destroying the component cancels it. The query is scoped to this component's own element,
|
|
5256
|
+
* so the library no longer reaches for the global `document` at all.
|
|
5257
|
+
*/
|
|
5258
|
+
jumpToField(fieldId) {
|
|
5259
|
+
const location = this.locateField(fieldId);
|
|
5260
|
+
if (!location)
|
|
5261
|
+
return;
|
|
5262
|
+
this.dynamicFormComp?.setActiveTab(location.tabId);
|
|
5263
|
+
// `setActiveTab` resets to a tab's first child, so the sub-tab has to be selected after
|
|
5264
|
+
// it, not before. Without this the walk found sub-tab fields and then rendered the wrong
|
|
5265
|
+
// panel, so the element the callback below looks for never existed.
|
|
5266
|
+
if (location.subTabId)
|
|
5267
|
+
this.dynamicFormComp?.setActiveSubTab(location.subTabId);
|
|
5268
|
+
afterNextRender(() => {
|
|
5269
|
+
// The field id comes from config, so it never goes into a selector string: no
|
|
5270
|
+
// escaping to get wrong, and no need for `CSS.escape`, which jsdom and older
|
|
5271
|
+
// browsers do not provide and whose absence throws silently inside a render hook.
|
|
5272
|
+
const wanted = `field-container-${fieldId}`;
|
|
5273
|
+
const el = Array.from(this.host.nativeElement.querySelectorAll('[id^="field-container-"]')).find(slot => slot.id === wanted);
|
|
5274
|
+
if (!el)
|
|
5275
|
+
return;
|
|
5276
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
5277
|
+
// The slot carries tabindex="-1" so this actually moves focus. Without it the jump
|
|
5278
|
+
// scrolled the field into view and left focus behind on the link.
|
|
5279
|
+
el.focus();
|
|
5280
|
+
}, { injector: this.injector });
|
|
5281
|
+
}
|
|
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 });
|
|
4933
5284
|
}
|
|
4934
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
5285
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DynamicRecordFormComponent, decorators: [{
|
|
4935
5286
|
type: Component,
|
|
4936
|
-
args: [{ 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"] }]
|
|
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"] }]
|
|
4937
5288
|
}], propDecorators: { config: [{
|
|
4938
5289
|
type: Input
|
|
4939
5290
|
}], rules: [{
|
|
@@ -4975,5 +5326,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
4975
5326
|
* Generated bundle index. Do not edit.
|
|
4976
5327
|
*/
|
|
4977
5328
|
|
|
4978
|
-
export { ASYNC_VALIDATOR_REGISTRY, ArrayFieldComponent, BooleanFieldComponent, COMMON_MODULES_REGISTRY, CONFIG_SOURCE, CascadeDataService, CheckboxFieldComponent, ConfigSourceService, CurrencyFieldComponent, DEFAULT_VALIDATION_MESSAGES, DateFieldComponent, DropdownFieldComponent, DynamicFieldComponent, DynamicFormComponent, DynamicRecordFormComponent, ENTITY_REF_CACHE_STORE, ENTITY_REF_REGISTRY, EmailFieldComponent, EntityRefFieldComponent, EntityRefRegistryService, EntityRefSelectionService, EntityReferenceService, FIELD_TYPE_REGISTRY, FIELD_TYPE_SETS, FieldRegistryService, FileFieldComponent, GroupFieldComponent, HOOK_REGISTRY, HookRegistryService, ImageFieldComponent, InMemoryEntityRefCacheStore, LOOKUP_REGISTRY, LookupRegistryService, MASKED_ROLES, MonthYearFieldComponent, MultiSelectFieldComponent, NumberFieldComponent, PasswordFieldComponent, RECORD_MIGRATIONS, RadioFieldComponent, RbacService, RulesEvaluationService, SYSTEM_DEFAULT_CAN_EDIT, TextFieldComponent, TextareaFieldComponent, UPLOAD_HANDLER, VALIDATION_MESSAGES, VALIDATOR_REGISTRY, ValidationMessagesService, ValidatorRegistryService, builtInFieldTypes, provideBuiltInFieldTypes, provideFieldTypes, provideNgxDynamicEntity, refreshChoiceOptions };
|
|
5329
|
+
export { ASYNC_VALIDATOR_REGISTRY, ArrayFieldComponent, BooleanFieldComponent, COMMON_MODULES_REGISTRY, CONFIG_SOURCE, CascadeDataService, CheckboxFieldComponent, ConfigSourceService, CurrencyFieldComponent, DEFAULT_VALIDATION_MESSAGES, DateFieldComponent, DateTimeFieldComponent, DropdownFieldComponent, DynamicFieldComponent, DynamicFormComponent, DynamicRecordFormComponent, ENTITY_REF_CACHE_STORE, ENTITY_REF_REGISTRY, EmailFieldComponent, EntityRefFieldComponent, EntityRefRegistryService, EntityRefSelectionService, EntityReferenceService, FIELD_TYPE_REGISTRY, FIELD_TYPE_SETS, FieldRegistryService, FileFieldComponent, GroupFieldComponent, HOOK_REGISTRY, HookRegistryService, ImageFieldComponent, InMemoryEntityRefCacheStore, LOOKUP_REGISTRY, LookupRegistryService, MASKED_ROLES, MonthYearFieldComponent, MultiSelectFieldComponent, NumberFieldComponent, PasswordFieldComponent, RECORD_MIGRATIONS, RadioFieldComponent, RbacService, RulesEvaluationService, SYSTEM_DEFAULT_CAN_EDIT, TextFieldComponent, TextareaFieldComponent, TimeFieldComponent, UPLOAD_HANDLER, VALIDATION_MESSAGES, VALIDATOR_REGISTRY, ValidationMessagesService, ValidatorRegistryService, builtInFieldTypes, provideBuiltInFieldTypes, provideFieldTypes, provideNgxDynamicEntity, refreshChoiceOptions };
|
|
4979
5330
|
//# sourceMappingURL=ngx-dynamic-entity.mjs.map
|