ngx-dynamic-entity 2.0.2 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { InjectionToken, makeEnvironmentProviders, inject, Injectable, signal, Input, ChangeDetectionStrategy, Component, ChangeDetectorRef, DestroyRef, Injector, isDevMode, ViewContainerRef, ViewChild, forwardRef, Optional, Inject, EventEmitter, ElementRef, computed, afterNextRender, HostListener, ViewChildren, Output } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { ReactiveFormsModule, Validators, FormBuilder, FormGroup, FormArray, FormsModule } from '@angular/forms';
|
|
5
|
-
import { resolveLabel, formatDisplayValue, normalizeLookupValues, lookupValuesToOptions, valuesMatch, resolveOptionLabel, resolveOptionValue, buildReferenceCacheKey, normalizeReferenceOptions, entityKeyFromCacheKey, applyCascadeFilter, resolveEffectiveMask, shouldMaskField, findTab, fileRefName, evaluateFormRules, filterRulesForTab, evaluateFieldVisibility, getTabPath, parseFieldRef, setTabData, setValueByPath, normalizeArrayStructures, getTabData, getValueByPath, normalizeConfigOptions, migrateRecord, ambiguousFieldIds,
|
|
5
|
+
import { resolveLabel, formatDisplayValue, normalizeLookupValues, lookupValuesToOptions, valuesMatch, resolveOptionLabel, resolveOptionValue, buildReferenceCacheKey, normalizeReferenceOptions, entityKeyFromCacheKey, applyCascadeFilter, resolveEffectiveMask, shouldMaskField, findTab, fileRefName, evaluateFormRules, filterRulesForTab, evaluateFieldVisibility, getTabPath, fieldRefFor, toRefToken, refOf, parseFieldRef, setTabData, setValueByPath, normalizeArrayStructures, getTabData, getValueByPath, normalizeConfigOptions, migrateRecord, ambiguousFieldIds, collectFieldScopes, applyAutoPatch, isUnsafePath, fieldsUnderTab, valuesEqual, parseCsv, deriveImportColumns, suggestMapping, applyMapping, toCsv, stripIndices, CORE_VERSION, coerceCell, buildTemplateSpec, collectLeafTargets, formatConfigProblems } from '@dynamic-entity/core';
|
|
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';
|
|
@@ -196,7 +196,7 @@ const ENTITY_REF_CACHE_STORE = new InjectionToken('ENTITY_REF_CACHE_STORE', {
|
|
|
196
196
|
*/
|
|
197
197
|
const SYSTEM_DEFAULT_CAN_EDIT = new InjectionToken('SYSTEM_DEFAULT_CAN_EDIT');
|
|
198
198
|
/**
|
|
199
|
-
* Registry/Resolver for resolving an EntityFormConfig by entity key across entities
|
|
199
|
+
* Registry/Resolver for resolving an EntityFormConfig by entity key across entities.
|
|
200
200
|
* Enables cross-entity field referencing (`isReferenced`, `referencedEntityKey`, `referencedFieldId`, `hasDrift`).
|
|
201
201
|
*/
|
|
202
202
|
const CONFIG_SOURCE = new InjectionToken('CONFIG_SOURCE');
|
|
@@ -224,8 +224,7 @@ const MARKDOWN_RENDERER = new InjectionToken('MARKDOWN_RENDERER');
|
|
|
224
224
|
*
|
|
225
225
|
* Masking is presentational — see SECURITY.md — and what it prints is a product decision:
|
|
226
226
|
* `••••••••` reads as a redaction, "Hidden" reads as a permission, and a localized string
|
|
227
|
-
* reads as neither in English
|
|
228
|
-
* there was no way to choose.
|
|
227
|
+
* reads as neither in English, so it is the host's to choose.
|
|
229
228
|
*
|
|
230
229
|
* @example
|
|
231
230
|
* { provide: MASKED_PLACEHOLDER, useValue: '••••••••' }
|
|
@@ -363,12 +362,10 @@ class ValidationMessagesService {
|
|
|
363
362
|
/**
|
|
364
363
|
* The message for a field, chosen by its **type** rather than by a list the caller supplies.
|
|
365
364
|
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
* rendered
|
|
369
|
-
*
|
|
370
|
-
* (the offending field is usually on a tab that is not rendered), so it would have needed a
|
|
371
|
-
* second copy of the same table, and a dropdown would have said "This field is required" in
|
|
365
|
+
* Both the field itself and the error summary above the form render this message, and the
|
|
366
|
+
* summary has no field component to ask (the offending field is usually on a tab that is
|
|
367
|
+
* not rendered). Per-component key orders would drift, and a dropdown would say "This
|
|
368
|
+
* field is required" in
|
|
372
369
|
* the summary and "Please select an option" under the control.
|
|
373
370
|
*
|
|
374
371
|
* One table, read from both places, is what keeps those two sentences the same sentence.
|
|
@@ -388,10 +385,10 @@ class ValidationMessagesService {
|
|
|
388
385
|
return '';
|
|
389
386
|
return typeof message === 'function' ? message(language, error) : message;
|
|
390
387
|
}
|
|
391
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
392
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
388
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ValidationMessagesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
389
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ValidationMessagesService, providedIn: 'root' });
|
|
393
390
|
}
|
|
394
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
391
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ValidationMessagesService, decorators: [{
|
|
395
392
|
type: Injectable,
|
|
396
393
|
args: [{ providedIn: 'root' }]
|
|
397
394
|
}] });
|
|
@@ -399,14 +396,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
399
396
|
/**
|
|
400
397
|
* DOM ids that survive being rendered more than once.
|
|
401
398
|
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
* the first row's input, and a screen reader read the same association twice. Duplicate ids on
|
|
407
|
-
* focusable elements are also a WCAG failure in their own right.
|
|
399
|
+
* `field.id` is unique in a config and *not* unique in a document: an `array` field renders
|
|
400
|
+
* the same child fields once per row. `<label for>` resolves to the first match, so a second
|
|
401
|
+
* row's label would focus the first row's input — and duplicate ids on focusable elements are
|
|
402
|
+
* a WCAG failure in their own right.
|
|
408
403
|
*
|
|
409
|
-
* The
|
|
404
|
+
* The answer is a counter, which is what Angular Material does for the same reason: the id stays
|
|
410
405
|
* readable — `email-de7` — and is unique for the lifetime of the page. It is deliberately not
|
|
411
406
|
* derived from the row index, because a field component is given exactly five inputs and none
|
|
412
407
|
* of them says where it is; adding a sixth would change the contract every custom field type
|
|
@@ -506,8 +501,8 @@ class TextFieldComponent {
|
|
|
506
501
|
return '';
|
|
507
502
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
508
503
|
}
|
|
509
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
510
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
504
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: TextFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
505
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: TextFieldComponent, isStandalone: true, selector: "ngx-text-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
511
506
|
<div
|
|
512
507
|
class="ngx-field ngx-field--text"
|
|
513
508
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -567,7 +562,7 @@ class TextFieldComponent {
|
|
|
567
562
|
</div>
|
|
568
563
|
`, 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 });
|
|
569
564
|
}
|
|
570
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
565
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: TextFieldComponent, decorators: [{
|
|
571
566
|
type: Component,
|
|
572
567
|
args: [{
|
|
573
568
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -669,12 +664,6 @@ class TextareaFieldComponent {
|
|
|
669
664
|
get placeholder() {
|
|
670
665
|
return resolveLabel(this.field?.placeholder, this.language);
|
|
671
666
|
}
|
|
672
|
-
/**
|
|
673
|
-
* Resolved through `ValidationMessagesService`, so `provideNgxDynamicEntity({
|
|
674
|
-
* validationMessages })` reaches this field. It used to render a fixed
|
|
675
|
-
* "This field has an error", which made a documented, configurable feature work on three
|
|
676
|
-
* of fifteen field types.
|
|
677
|
-
*/
|
|
678
667
|
/** Author help text, shown under the control and named by `aria-describedby`. */
|
|
679
668
|
get hint() {
|
|
680
669
|
return resolveLabel(this.field?.hint, this.language);
|
|
@@ -688,8 +677,8 @@ class TextareaFieldComponent {
|
|
|
688
677
|
return '';
|
|
689
678
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
690
679
|
}
|
|
691
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
692
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
680
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: TextareaFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
681
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: TextareaFieldComponent, isStandalone: true, selector: "ngx-textarea-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
693
682
|
<div
|
|
694
683
|
class="ngx-field ngx-field--textarea"
|
|
695
684
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -738,7 +727,7 @@ class TextareaFieldComponent {
|
|
|
738
727
|
</div>
|
|
739
728
|
`, 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 });
|
|
740
729
|
}
|
|
741
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
730
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: TextareaFieldComponent, decorators: [{
|
|
742
731
|
type: Component,
|
|
743
732
|
args: [{
|
|
744
733
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -1007,10 +996,10 @@ class UiTextService {
|
|
|
1007
996
|
text(key, language = 'en', params) {
|
|
1008
997
|
return resolveUiText(this.overrides, DEFAULT_UI_TEXT, key, language, params);
|
|
1009
998
|
}
|
|
1010
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1011
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
999
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: UiTextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1000
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: UiTextService, providedIn: 'root' });
|
|
1012
1001
|
}
|
|
1013
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1002
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: UiTextService, decorators: [{
|
|
1014
1003
|
type: Injectable,
|
|
1015
1004
|
args: [{ providedIn: 'root' }]
|
|
1016
1005
|
}] });
|
|
@@ -1124,12 +1113,6 @@ class MarkdownFieldComponent {
|
|
|
1124
1113
|
this.cached = { source, html };
|
|
1125
1114
|
return html;
|
|
1126
1115
|
}
|
|
1127
|
-
/**
|
|
1128
|
-
* Resolved through `ValidationMessagesService`, so `provideNgxDynamicEntity({
|
|
1129
|
-
* validationMessages })` reaches this field. It used to render a fixed
|
|
1130
|
-
* "This field has an error", which made a documented, configurable feature work on three
|
|
1131
|
-
* of fifteen field types.
|
|
1132
|
-
*/
|
|
1133
1116
|
/** Author help text, shown under the control and named by `aria-describedby`. */
|
|
1134
1117
|
get hint() {
|
|
1135
1118
|
return resolveLabel(this.field?.hint, this.language);
|
|
@@ -1143,8 +1126,8 @@ class MarkdownFieldComponent {
|
|
|
1143
1126
|
return '';
|
|
1144
1127
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
1145
1128
|
}
|
|
1146
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1147
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
1129
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: MarkdownFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1130
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: MarkdownFieldComponent, isStandalone: true, selector: "ngx-markdown-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, usesOnChanges: true, ngImport: i0, template: `
|
|
1148
1131
|
<div
|
|
1149
1132
|
class="ngx-field ngx-field--markdown"
|
|
1150
1133
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -1244,7 +1227,7 @@ class MarkdownFieldComponent {
|
|
|
1244
1227
|
</div>
|
|
1245
1228
|
`, 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 });
|
|
1246
1229
|
}
|
|
1247
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1230
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: MarkdownFieldComponent, decorators: [{
|
|
1248
1231
|
type: Component,
|
|
1249
1232
|
args: [{
|
|
1250
1233
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -1399,8 +1382,8 @@ class NumberFieldComponent {
|
|
|
1399
1382
|
return '';
|
|
1400
1383
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
1401
1384
|
}
|
|
1402
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1403
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
1385
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: NumberFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1386
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: NumberFieldComponent, isStandalone: true, selector: "ngx-number-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
1404
1387
|
<div
|
|
1405
1388
|
class="ngx-field ngx-field--number"
|
|
1406
1389
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -1460,7 +1443,7 @@ class NumberFieldComponent {
|
|
|
1460
1443
|
</div>
|
|
1461
1444
|
`, 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 });
|
|
1462
1445
|
}
|
|
1463
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1446
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: NumberFieldComponent, decorators: [{
|
|
1464
1447
|
type: Component,
|
|
1465
1448
|
args: [{
|
|
1466
1449
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -1575,12 +1558,6 @@ class CurrencyFieldComponent {
|
|
|
1575
1558
|
return '$';
|
|
1576
1559
|
}
|
|
1577
1560
|
}
|
|
1578
|
-
/**
|
|
1579
|
-
* Resolved through `ValidationMessagesService`, so `provideNgxDynamicEntity({
|
|
1580
|
-
* validationMessages })` reaches this field. It used to render a fixed
|
|
1581
|
-
* "This field has an error", which made a documented, configurable feature work on three
|
|
1582
|
-
* of fifteen field types.
|
|
1583
|
-
*/
|
|
1584
1561
|
/** Author help text, shown under the control and named by `aria-describedby`. */
|
|
1585
1562
|
get hint() {
|
|
1586
1563
|
return resolveLabel(this.field?.hint, this.language);
|
|
@@ -1594,8 +1571,8 @@ class CurrencyFieldComponent {
|
|
|
1594
1571
|
return '';
|
|
1595
1572
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
1596
1573
|
}
|
|
1597
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1598
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
1574
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: CurrencyFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1575
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: CurrencyFieldComponent, isStandalone: true, selector: "ngx-currency-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
1599
1576
|
<div
|
|
1600
1577
|
class="ngx-field ngx-field--currency"
|
|
1601
1578
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -1650,7 +1627,7 @@ class CurrencyFieldComponent {
|
|
|
1650
1627
|
</div>
|
|
1651
1628
|
`, 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 });
|
|
1652
1629
|
}
|
|
1653
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1630
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: CurrencyFieldComponent, decorators: [{
|
|
1654
1631
|
type: Component,
|
|
1655
1632
|
args: [{
|
|
1656
1633
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -1748,12 +1725,6 @@ class EmailFieldComponent {
|
|
|
1748
1725
|
get placeholder() {
|
|
1749
1726
|
return resolveLabel(this.field?.placeholder, this.language);
|
|
1750
1727
|
}
|
|
1751
|
-
/**
|
|
1752
|
-
* Resolved through `ValidationMessagesService`, so `provideNgxDynamicEntity({
|
|
1753
|
-
* validationMessages })` reaches this field. It used to render a fixed
|
|
1754
|
-
* "This field has an error", which made a documented, configurable feature work on three
|
|
1755
|
-
* of fifteen field types.
|
|
1756
|
-
*/
|
|
1757
1728
|
/** Author help text, shown under the control and named by `aria-describedby`. */
|
|
1758
1729
|
get hint() {
|
|
1759
1730
|
return resolveLabel(this.field?.hint, this.language);
|
|
@@ -1767,8 +1738,8 @@ class EmailFieldComponent {
|
|
|
1767
1738
|
return '';
|
|
1768
1739
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
1769
1740
|
}
|
|
1770
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1771
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
1741
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EmailFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1742
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: EmailFieldComponent, isStandalone: true, selector: "ngx-email-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
1772
1743
|
<div
|
|
1773
1744
|
class="ngx-field ngx-field--email"
|
|
1774
1745
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -1816,10 +1787,7 @@ class EmailFieldComponent {
|
|
|
1816
1787
|
[attr.disabled]="field.disabled ? true : null"
|
|
1817
1788
|
/>
|
|
1818
1789
|
@if (errorMessage) {
|
|
1819
|
-
<!-- One branch, resolved by the service
|
|
1820
|
-
required" and "Enter a valid email address" itself, so a host configuring
|
|
1821
|
-
validationMessages was overridden by markup it could not reach. The service
|
|
1822
|
-
defaults say the same thing. -->
|
|
1790
|
+
<!-- One branch, resolved by the service, so a host's validationMessages apply. -->
|
|
1823
1791
|
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'" [id]="domId('-error')">{{
|
|
1824
1792
|
errorMessage
|
|
1825
1793
|
}}</span>
|
|
@@ -1828,7 +1796,7 @@ class EmailFieldComponent {
|
|
|
1828
1796
|
</div>
|
|
1829
1797
|
`, 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 });
|
|
1830
1798
|
}
|
|
1831
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1799
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EmailFieldComponent, decorators: [{
|
|
1832
1800
|
type: Component,
|
|
1833
1801
|
args: [{
|
|
1834
1802
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -1883,10 +1851,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1883
1851
|
[attr.disabled]="field.disabled ? true : null"
|
|
1884
1852
|
/>
|
|
1885
1853
|
@if (errorMessage) {
|
|
1886
|
-
<!-- One branch, resolved by the service
|
|
1887
|
-
required" and "Enter a valid email address" itself, so a host configuring
|
|
1888
|
-
validationMessages was overridden by markup it could not reach. The service
|
|
1889
|
-
defaults say the same thing. -->
|
|
1854
|
+
<!-- One branch, resolved by the service, so a host's validationMessages apply. -->
|
|
1890
1855
|
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'" [id]="domId('-error')">{{
|
|
1891
1856
|
errorMessage
|
|
1892
1857
|
}}</span>
|
|
@@ -1937,12 +1902,6 @@ class PasswordFieldComponent {
|
|
|
1937
1902
|
toggleVisibility() {
|
|
1938
1903
|
this.visible.update(v => !v);
|
|
1939
1904
|
}
|
|
1940
|
-
/**
|
|
1941
|
-
* Resolved through `ValidationMessagesService`, so `provideNgxDynamicEntity({
|
|
1942
|
-
* validationMessages })` reaches this field. It used to render a fixed
|
|
1943
|
-
* "This field has an error", which made a documented, configurable feature work on three
|
|
1944
|
-
* of fifteen field types.
|
|
1945
|
-
*/
|
|
1946
1905
|
/** Author help text, shown under the control and named by `aria-describedby`. */
|
|
1947
1906
|
get hint() {
|
|
1948
1907
|
return resolveLabel(this.field?.hint, this.language);
|
|
@@ -1956,8 +1915,8 @@ class PasswordFieldComponent {
|
|
|
1956
1915
|
return '';
|
|
1957
1916
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
1958
1917
|
}
|
|
1959
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1960
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
1918
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: PasswordFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1919
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: PasswordFieldComponent, isStandalone: true, selector: "ngx-password-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
1961
1920
|
<div
|
|
1962
1921
|
class="ngx-field ngx-field--password"
|
|
1963
1922
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -2017,7 +1976,7 @@ class PasswordFieldComponent {
|
|
|
2017
1976
|
</div>
|
|
2018
1977
|
`, 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 });
|
|
2019
1978
|
}
|
|
2020
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1979
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: PasswordFieldComponent, decorators: [{
|
|
2021
1980
|
type: Component,
|
|
2022
1981
|
args: [{
|
|
2023
1982
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -2118,12 +2077,6 @@ class CheckboxFieldComponent {
|
|
|
2118
2077
|
get label() {
|
|
2119
2078
|
return resolveLabel(this.field?.label, this.language);
|
|
2120
2079
|
}
|
|
2121
|
-
/**
|
|
2122
|
-
* Configured messages reach this field too.
|
|
2123
|
-
*
|
|
2124
|
-
* It rendered no error element at all, so a required checkbox left unticked told the user
|
|
2125
|
-
* nothing — and `validationMessages` could not reach a field that never displayed one.
|
|
2126
|
-
*/
|
|
2127
2080
|
/** Author help text, shown under the control and named by `aria-describedby`. */
|
|
2128
2081
|
get hint() {
|
|
2129
2082
|
return resolveLabel(this.field?.hint, this.language);
|
|
@@ -2137,8 +2090,8 @@ class CheckboxFieldComponent {
|
|
|
2137
2090
|
return '';
|
|
2138
2091
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
2139
2092
|
}
|
|
2140
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
2141
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
2093
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: CheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2094
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: CheckboxFieldComponent, isStandalone: true, selector: "ngx-checkbox-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
2142
2095
|
<div
|
|
2143
2096
|
class="ngx-field ngx-field--checkbox"
|
|
2144
2097
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -2194,7 +2147,7 @@ class CheckboxFieldComponent {
|
|
|
2194
2147
|
</div>
|
|
2195
2148
|
`, 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 });
|
|
2196
2149
|
}
|
|
2197
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2150
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: CheckboxFieldComponent, decorators: [{
|
|
2198
2151
|
type: Component,
|
|
2199
2152
|
args: [{
|
|
2200
2153
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -2296,12 +2249,6 @@ class BooleanFieldComponent {
|
|
|
2296
2249
|
get label() {
|
|
2297
2250
|
return resolveLabel(this.field?.label, this.language);
|
|
2298
2251
|
}
|
|
2299
|
-
/**
|
|
2300
|
-
* Configured messages reach this field too.
|
|
2301
|
-
*
|
|
2302
|
-
* It rendered no error element at all, so a required checkbox left unticked told the user
|
|
2303
|
-
* nothing — and `validationMessages` could not reach a field that never displayed one.
|
|
2304
|
-
*/
|
|
2305
2252
|
/** Author help text, shown under the control and named by `aria-describedby`. */
|
|
2306
2253
|
get hint() {
|
|
2307
2254
|
return resolveLabel(this.field?.hint, this.language);
|
|
@@ -2315,8 +2262,8 @@ class BooleanFieldComponent {
|
|
|
2315
2262
|
return '';
|
|
2316
2263
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
2317
2264
|
}
|
|
2318
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
2319
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
2265
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: BooleanFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2266
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: BooleanFieldComponent, isStandalone: true, selector: "ngx-boolean-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
2320
2267
|
<div
|
|
2321
2268
|
class="ngx-field ngx-field--boolean"
|
|
2322
2269
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -2380,7 +2327,7 @@ class BooleanFieldComponent {
|
|
|
2380
2327
|
</div>
|
|
2381
2328
|
`, 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 });
|
|
2382
2329
|
}
|
|
2383
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2330
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: BooleanFieldComponent, decorators: [{
|
|
2384
2331
|
type: Component,
|
|
2385
2332
|
args: [{
|
|
2386
2333
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -2486,14 +2433,10 @@ class DateFieldComponent {
|
|
|
2486
2433
|
/**
|
|
2487
2434
|
* ISO 8601 date → display string, through the formatter the host configured.
|
|
2488
2435
|
*
|
|
2489
|
-
*
|
|
2490
|
-
*
|
|
2491
|
-
* field but not this one. A host that configured formatters got them almost everywhere,
|
|
2492
|
-
* and silently did not get them on the field type most likely to be the reason they
|
|
2493
|
-
* configured formatters at all.
|
|
2436
|
+
* Never call `toLocaleDateString()` directly here: `setDateFormatters` is the one seam for
|
|
2437
|
+
* choosing how a date renders, and every date display has to go through it.
|
|
2494
2438
|
*
|
|
2495
|
-
* The default formatter is `toLocaleDateString()` with no locale
|
|
2496
|
-
* has not called `setDateFormatters` renders exactly what it rendered before.
|
|
2439
|
+
* The default formatter is `toLocaleDateString()` with no locale.
|
|
2497
2440
|
*/
|
|
2498
2441
|
formatDate(value) {
|
|
2499
2442
|
if (!value)
|
|
@@ -2508,12 +2451,6 @@ class DateFieldComponent {
|
|
|
2508
2451
|
return value;
|
|
2509
2452
|
return formatDisplayValue('date', undefined, value, this.language);
|
|
2510
2453
|
}
|
|
2511
|
-
/**
|
|
2512
|
-
* Resolved through `ValidationMessagesService`, so `provideNgxDynamicEntity({
|
|
2513
|
-
* validationMessages })` reaches this field. It used to render a fixed
|
|
2514
|
-
* "This field has an error", which made a documented, configurable feature work on three
|
|
2515
|
-
* of fifteen field types.
|
|
2516
|
-
*/
|
|
2517
2454
|
/** Author help text, shown under the control and named by `aria-describedby`. */
|
|
2518
2455
|
get hint() {
|
|
2519
2456
|
return resolveLabel(this.field?.hint, this.language);
|
|
@@ -2527,8 +2464,8 @@ class DateFieldComponent {
|
|
|
2527
2464
|
return '';
|
|
2528
2465
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
2529
2466
|
}
|
|
2530
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
2531
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
2467
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DateFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2468
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: DateFieldComponent, isStandalone: true, selector: "ngx-date-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
2532
2469
|
<div
|
|
2533
2470
|
class="ngx-field ngx-field--date"
|
|
2534
2471
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -2578,7 +2515,7 @@ class DateFieldComponent {
|
|
|
2578
2515
|
</div>
|
|
2579
2516
|
`, 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 });
|
|
2580
2517
|
}
|
|
2581
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2518
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DateFieldComponent, decorators: [{
|
|
2582
2519
|
type: Component,
|
|
2583
2520
|
args: [{
|
|
2584
2521
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -2652,8 +2589,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2652
2589
|
*
|
|
2653
2590
|
* A bare calendar date is pinned to *local* midnight. `new Date('2020-01-01')` reads a
|
|
2654
2591
|
* date-only string as UTC midnight, which renders as the previous day anywhere west of
|
|
2655
|
-
* Greenwich — and
|
|
2656
|
-
*
|
|
2592
|
+
* Greenwich — and legacy `datetime` values written by a `type="date"` input have exactly
|
|
2593
|
+
* that shape.
|
|
2657
2594
|
*/
|
|
2658
2595
|
function parseStored(value) {
|
|
2659
2596
|
if (value === null || value === undefined || value === '')
|
|
@@ -2734,12 +2671,6 @@ class DateTimeFieldComponent {
|
|
|
2734
2671
|
return '—';
|
|
2735
2672
|
return formatDisplayValue('datetime', undefined, d.toISOString(), this.language);
|
|
2736
2673
|
}
|
|
2737
|
-
/**
|
|
2738
|
-
* Resolved through `ValidationMessagesService`, so `provideNgxDynamicEntity({
|
|
2739
|
-
* validationMessages })` reaches this field. It used to render a fixed
|
|
2740
|
-
* "This field has an error", which made a documented, configurable feature work on three
|
|
2741
|
-
* of fifteen field types.
|
|
2742
|
-
*/
|
|
2743
2674
|
/** Author help text, shown under the control and named by `aria-describedby`. */
|
|
2744
2675
|
get hint() {
|
|
2745
2676
|
return resolveLabel(this.field?.hint, this.language);
|
|
@@ -2753,8 +2684,8 @@ class DateTimeFieldComponent {
|
|
|
2753
2684
|
return '';
|
|
2754
2685
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
2755
2686
|
}
|
|
2756
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
2757
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
2687
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DateTimeFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2688
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: DateTimeFieldComponent, isStandalone: true, selector: "ngx-date-time-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
2758
2689
|
<div
|
|
2759
2690
|
class="ngx-field ngx-field--datetime"
|
|
2760
2691
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -2805,7 +2736,7 @@ class DateTimeFieldComponent {
|
|
|
2805
2736
|
</div>
|
|
2806
2737
|
`, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2807
2738
|
}
|
|
2808
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2739
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DateTimeFieldComponent, decorators: [{
|
|
2809
2740
|
type: Component,
|
|
2810
2741
|
args: [{
|
|
2811
2742
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -2911,12 +2842,6 @@ class TimeFieldComponent {
|
|
|
2911
2842
|
formatTime(value) {
|
|
2912
2843
|
return formatDisplayValue('time', undefined, value, this.language);
|
|
2913
2844
|
}
|
|
2914
|
-
/**
|
|
2915
|
-
* Resolved through `ValidationMessagesService`, so `provideNgxDynamicEntity({
|
|
2916
|
-
* validationMessages })` reaches this field. It used to render a fixed
|
|
2917
|
-
* "This field has an error", which made a documented, configurable feature work on three
|
|
2918
|
-
* of fifteen field types.
|
|
2919
|
-
*/
|
|
2920
2845
|
/** Author help text, shown under the control and named by `aria-describedby`. */
|
|
2921
2846
|
get hint() {
|
|
2922
2847
|
return resolveLabel(this.field?.hint, this.language);
|
|
@@ -2930,8 +2855,8 @@ class TimeFieldComponent {
|
|
|
2930
2855
|
return '';
|
|
2931
2856
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
2932
2857
|
}
|
|
2933
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
2934
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
2858
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: TimeFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2859
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: TimeFieldComponent, isStandalone: true, selector: "ngx-time-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
2935
2860
|
<div
|
|
2936
2861
|
class="ngx-field ngx-field--time"
|
|
2937
2862
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -2981,7 +2906,7 @@ class TimeFieldComponent {
|
|
|
2981
2906
|
</div>
|
|
2982
2907
|
`, 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 });
|
|
2983
2908
|
}
|
|
2984
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2909
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: TimeFieldComponent, decorators: [{
|
|
2985
2910
|
type: Component,
|
|
2986
2911
|
args: [{
|
|
2987
2912
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -3120,12 +3045,6 @@ class MonthYearFieldComponent {
|
|
|
3120
3045
|
const monthName = MONTH_NAMES[parseInt(monthNum, 10) - 1];
|
|
3121
3046
|
return monthName && year ? `${monthName} ${year}` : String(value);
|
|
3122
3047
|
}
|
|
3123
|
-
/**
|
|
3124
|
-
* Resolved through `ValidationMessagesService`, so `provideNgxDynamicEntity({
|
|
3125
|
-
* validationMessages })` reaches this field. It used to render a fixed
|
|
3126
|
-
* "This field has an error", which made a documented, configurable feature work on three
|
|
3127
|
-
* of fifteen field types.
|
|
3128
|
-
*/
|
|
3129
3048
|
/** Author help text, shown under the control and named by `aria-describedby`. */
|
|
3130
3049
|
get hint() {
|
|
3131
3050
|
return resolveLabel(this.field?.hint, this.language);
|
|
@@ -3139,8 +3058,8 @@ class MonthYearFieldComponent {
|
|
|
3139
3058
|
return '';
|
|
3140
3059
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
3141
3060
|
}
|
|
3142
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
3143
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
3061
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: MonthYearFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3062
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: MonthYearFieldComponent, isStandalone: true, selector: "ngx-month-year-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
3144
3063
|
<div
|
|
3145
3064
|
class="ngx-field ngx-field--month-year"
|
|
3146
3065
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -3219,7 +3138,7 @@ class MonthYearFieldComponent {
|
|
|
3219
3138
|
</div>
|
|
3220
3139
|
`, 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 });
|
|
3221
3140
|
}
|
|
3222
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
3141
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: MonthYearFieldComponent, decorators: [{
|
|
3223
3142
|
type: Component,
|
|
3224
3143
|
args: [{
|
|
3225
3144
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -3520,10 +3439,10 @@ class LookupRegistryService {
|
|
|
3520
3439
|
value !== null &&
|
|
3521
3440
|
typeof value.subscribe === 'function');
|
|
3522
3441
|
}
|
|
3523
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
3524
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
3442
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LookupRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3443
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LookupRegistryService, providedIn: 'root' });
|
|
3525
3444
|
}
|
|
3526
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
3445
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LookupRegistryService, decorators: [{
|
|
3527
3446
|
type: Injectable,
|
|
3528
3447
|
args: [{ providedIn: 'root' }]
|
|
3529
3448
|
}] });
|
|
@@ -3610,10 +3529,7 @@ class DropdownFieldComponent {
|
|
|
3610
3529
|
* The value this option stores — the option object itself.
|
|
3611
3530
|
*
|
|
3612
3531
|
* Kept as a method rather than binding `option` straight into the template, because it is
|
|
3613
|
-
* the one place that says *why* the two are the same thing.
|
|
3614
|
-
* `getOptionStoredValue`, an exported identity function whose two branches returned their
|
|
3615
|
-
* argument unchanged; naming a transformation that does not happen is worse than naming
|
|
3616
|
-
* nothing, because a reader has to go and check.
|
|
3532
|
+
* the one place that says *why* the two are the same thing.
|
|
3617
3533
|
*/
|
|
3618
3534
|
getOptStoredVal(option) {
|
|
3619
3535
|
return option;
|
|
@@ -3652,8 +3568,8 @@ class DropdownFieldComponent {
|
|
|
3652
3568
|
// dropdown than "This field is required", and both stay independently overridable.
|
|
3653
3569
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
3654
3570
|
}
|
|
3655
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
3656
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
3571
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DropdownFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3572
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: DropdownFieldComponent, isStandalone: true, selector: "ngx-dropdown-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
3657
3573
|
<div
|
|
3658
3574
|
class="ngx-field ngx-field--dropdown"
|
|
3659
3575
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -3723,7 +3639,7 @@ class DropdownFieldComponent {
|
|
|
3723
3639
|
</div>
|
|
3724
3640
|
`, 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 });
|
|
3725
3641
|
}
|
|
3726
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
3642
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DropdownFieldComponent, decorators: [{
|
|
3727
3643
|
type: Component,
|
|
3728
3644
|
args: [{
|
|
3729
3645
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -3875,10 +3791,7 @@ class RadioFieldComponent {
|
|
|
3875
3791
|
* The value this option stores — the option object itself.
|
|
3876
3792
|
*
|
|
3877
3793
|
* Kept as a method rather than binding `option` straight into the template, because it is
|
|
3878
|
-
* the one place that says *why* the two are the same thing.
|
|
3879
|
-
* `getOptionStoredValue`, an exported identity function whose two branches returned their
|
|
3880
|
-
* argument unchanged; naming a transformation that does not happen is worse than naming
|
|
3881
|
-
* nothing, because a reader has to go and check.
|
|
3794
|
+
* the one place that says *why* the two are the same thing.
|
|
3882
3795
|
*/
|
|
3883
3796
|
getOptStoredVal(option) {
|
|
3884
3797
|
return option;
|
|
@@ -3928,12 +3841,6 @@ class RadioFieldComponent {
|
|
|
3928
3841
|
return cached;
|
|
3929
3842
|
return typeof value === 'object' ? resolveLabel(value, this.language) : String(value ?? '—');
|
|
3930
3843
|
}
|
|
3931
|
-
/**
|
|
3932
|
-
* Resolved through `ValidationMessagesService`, so `provideNgxDynamicEntity({
|
|
3933
|
-
* validationMessages })` reaches this field. It used to render a fixed
|
|
3934
|
-
* "This field has an error", which made a documented, configurable feature work on three
|
|
3935
|
-
* of fifteen field types.
|
|
3936
|
-
*/
|
|
3937
3844
|
/** Author help text, shown under the control and named by `aria-describedby`. */
|
|
3938
3845
|
get hint() {
|
|
3939
3846
|
return resolveLabel(this.field?.hint, this.language);
|
|
@@ -3947,8 +3854,8 @@ class RadioFieldComponent {
|
|
|
3947
3854
|
return '';
|
|
3948
3855
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
3949
3856
|
}
|
|
3950
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
3951
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
3857
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RadioFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3858
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: RadioFieldComponent, isStandalone: true, selector: "ngx-radio-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
3952
3859
|
<div
|
|
3953
3860
|
class="ngx-field ngx-field--radio"
|
|
3954
3861
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -4012,7 +3919,7 @@ class RadioFieldComponent {
|
|
|
4012
3919
|
</div>
|
|
4013
3920
|
`, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4014
3921
|
}
|
|
4015
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
3922
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RadioFieldComponent, decorators: [{
|
|
4016
3923
|
type: Component,
|
|
4017
3924
|
args: [{
|
|
4018
3925
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -4141,10 +4048,7 @@ class MultiSelectFieldComponent {
|
|
|
4141
4048
|
* The value this option stores — the option object itself.
|
|
4142
4049
|
*
|
|
4143
4050
|
* Kept as a method rather than binding `option` straight into the template, because it is
|
|
4144
|
-
* the one place that says *why* the two are the same thing.
|
|
4145
|
-
* `getOptionStoredValue`, an exported identity function whose two branches returned their
|
|
4146
|
-
* argument unchanged; naming a transformation that does not happen is worse than naming
|
|
4147
|
-
* nothing, because a reader has to go and check.
|
|
4051
|
+
* the one place that says *why* the two are the same thing.
|
|
4148
4052
|
*/
|
|
4149
4053
|
getOptStoredVal(option) {
|
|
4150
4054
|
return option;
|
|
@@ -4168,12 +4072,6 @@ class MultiSelectFieldComponent {
|
|
|
4168
4072
|
})
|
|
4169
4073
|
.join(', ');
|
|
4170
4074
|
}
|
|
4171
|
-
/**
|
|
4172
|
-
* Resolved through `ValidationMessagesService`, so `provideNgxDynamicEntity({
|
|
4173
|
-
* validationMessages })` reaches this field. It used to render a fixed
|
|
4174
|
-
* "This field has an error", which made a documented, configurable feature work on three
|
|
4175
|
-
* of fifteen field types.
|
|
4176
|
-
*/
|
|
4177
4075
|
/** Author help text, shown under the control and named by `aria-describedby`. */
|
|
4178
4076
|
get hint() {
|
|
4179
4077
|
return resolveLabel(this.field?.hint, this.language);
|
|
@@ -4187,8 +4085,8 @@ class MultiSelectFieldComponent {
|
|
|
4187
4085
|
return '';
|
|
4188
4086
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
4189
4087
|
}
|
|
4190
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
4191
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
4088
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: MultiSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4089
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: MultiSelectFieldComponent, isStandalone: true, selector: "ngx-multi-select-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
4192
4090
|
<div
|
|
4193
4091
|
class="ngx-field ngx-field--multiSelect"
|
|
4194
4092
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -4248,7 +4146,7 @@ class MultiSelectFieldComponent {
|
|
|
4248
4146
|
</div>
|
|
4249
4147
|
`, 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 });
|
|
4250
4148
|
}
|
|
4251
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
4149
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: MultiSelectFieldComponent, decorators: [{
|
|
4252
4150
|
type: Component,
|
|
4253
4151
|
args: [{
|
|
4254
4152
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -4347,10 +4245,10 @@ class EntityRefRegistryService {
|
|
|
4347
4245
|
has(entityKey) {
|
|
4348
4246
|
return this.registry.has(entityKey);
|
|
4349
4247
|
}
|
|
4350
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
4351
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
4248
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4249
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefRegistryService, providedIn: 'root' });
|
|
4352
4250
|
}
|
|
4353
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
4251
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefRegistryService, decorators: [{
|
|
4354
4252
|
type: Injectable,
|
|
4355
4253
|
args: [{ providedIn: 'root' }]
|
|
4356
4254
|
}] });
|
|
@@ -4510,10 +4408,10 @@ class EntityReferenceService {
|
|
|
4510
4408
|
matches(a, b, lang = 'en') {
|
|
4511
4409
|
return valuesMatch(a, b, lang);
|
|
4512
4410
|
}
|
|
4513
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
4514
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
4411
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityReferenceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4412
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityReferenceService, providedIn: 'root' });
|
|
4515
4413
|
}
|
|
4516
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
4414
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityReferenceService, decorators: [{
|
|
4517
4415
|
type: Injectable,
|
|
4518
4416
|
args: [{ providedIn: 'root' }]
|
|
4519
4417
|
}] });
|
|
@@ -4634,10 +4532,10 @@ class CascadeDataService {
|
|
|
4634
4532
|
walkTabs(config?.tabs);
|
|
4635
4533
|
return found;
|
|
4636
4534
|
}
|
|
4637
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
4638
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
4535
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: CascadeDataService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4536
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: CascadeDataService, providedIn: 'root' });
|
|
4639
4537
|
}
|
|
4640
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
4538
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: CascadeDataService, decorators: [{
|
|
4641
4539
|
type: Injectable,
|
|
4642
4540
|
args: [{ providedIn: 'root' }]
|
|
4643
4541
|
}] });
|
|
@@ -4658,10 +4556,10 @@ class EntityRefSelectionService {
|
|
|
4658
4556
|
emit(fieldId, option) {
|
|
4659
4557
|
this.subject.next({ fieldId, option });
|
|
4660
4558
|
}
|
|
4661
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
4662
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
4559
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefSelectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4560
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefSelectionService, providedIn: 'root' });
|
|
4663
4561
|
}
|
|
4664
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
4562
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefSelectionService, decorators: [{
|
|
4665
4563
|
type: Injectable,
|
|
4666
4564
|
args: [{ providedIn: 'root' }]
|
|
4667
4565
|
}] });
|
|
@@ -4776,12 +4674,6 @@ class EntityRefFieldComponent {
|
|
|
4776
4674
|
void this.reload();
|
|
4777
4675
|
});
|
|
4778
4676
|
}
|
|
4779
|
-
/**
|
|
4780
|
-
* Resolved through `ValidationMessagesService`, so `provideNgxDynamicEntity({
|
|
4781
|
-
* validationMessages })` reaches this field. It used to render a fixed
|
|
4782
|
-
* "This field has an error", which made a documented, configurable feature work on three
|
|
4783
|
-
* of fifteen field types.
|
|
4784
|
-
*/
|
|
4785
4677
|
/** Author help text, shown under the control and named by `aria-describedby`. */
|
|
4786
4678
|
get hint() {
|
|
4787
4679
|
return resolveLabel(this.field?.hint, this.language);
|
|
@@ -4795,8 +4687,8 @@ class EntityRefFieldComponent {
|
|
|
4795
4687
|
return '';
|
|
4796
4688
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
4797
4689
|
}
|
|
4798
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
4799
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
4690
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4691
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: EntityRefFieldComponent, isStandalone: true, selector: "ngx-entity-ref-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
4800
4692
|
<div
|
|
4801
4693
|
class="ngx-field ngx-field--entity-ref"
|
|
4802
4694
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -4861,7 +4753,7 @@ class EntityRefFieldComponent {
|
|
|
4861
4753
|
</div>
|
|
4862
4754
|
`, 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 });
|
|
4863
4755
|
}
|
|
4864
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
4756
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityRefFieldComponent, decorators: [{
|
|
4865
4757
|
type: Component,
|
|
4866
4758
|
args: [{
|
|
4867
4759
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -4989,10 +4881,10 @@ class FieldRegistryService {
|
|
|
4989
4881
|
registeredTypes() {
|
|
4990
4882
|
return [...new Set([...this.dynamicRegistry.keys(), ...this.consumerRegistry.keys()])].sort();
|
|
4991
4883
|
}
|
|
4992
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
4993
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
4884
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FieldRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4885
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FieldRegistryService, providedIn: 'root' });
|
|
4994
4886
|
}
|
|
4995
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
4887
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FieldRegistryService, decorators: [{
|
|
4996
4888
|
type: Injectable,
|
|
4997
4889
|
args: [{ providedIn: 'root' }]
|
|
4998
4890
|
}], ctorParameters: () => [] });
|
|
@@ -5031,10 +4923,10 @@ class RbacService {
|
|
|
5031
4923
|
canDelete: this.hasPermission(userRoles, config.permissions?.delete),
|
|
5032
4924
|
};
|
|
5033
4925
|
}
|
|
5034
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5035
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
4926
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RbacService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4927
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RbacService, providedIn: 'root' });
|
|
5036
4928
|
}
|
|
5037
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
4929
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RbacService, decorators: [{
|
|
5038
4930
|
type: Injectable,
|
|
5039
4931
|
args: [{ providedIn: 'root' }]
|
|
5040
4932
|
}] });
|
|
@@ -5127,7 +5019,7 @@ class DynamicFieldComponent {
|
|
|
5127
5019
|
*
|
|
5128
5020
|
* `detectChanges()` rather than `markForCheck()`: a selection that arrives from a native
|
|
5129
5021
|
* `change` event (or Playwright) may not be followed by another Angular tick, so a dirty
|
|
5130
|
-
* flag would never be flushed. Running CD on the hosted component
|
|
5022
|
+
* flag would never be flushed. Running CD on the hosted component immediately is what shows the copied value.
|
|
5131
5023
|
*
|
|
5132
5024
|
* Done here rather than in each of the eighteen field components: the host owns the
|
|
5133
5025
|
* component reference, so one subscription covers every type including custom ones.
|
|
@@ -5156,10 +5048,10 @@ class DynamicFieldComponent {
|
|
|
5156
5048
|
ref.setInput('readonly', this.readonly || !!this.field.readonly);
|
|
5157
5049
|
ref.setInput('masked', masked);
|
|
5158
5050
|
}
|
|
5159
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5160
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.
|
|
5051
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DynamicFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5052
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.24", type: DynamicFieldComponent, isStandalone: true, selector: "ngx-dynamic-field", inputs: { field: "field", control: "control", config: "config", currentTabId: "currentTabId", language: "language", readonly: "readonly", userRoles: "userRoles" }, viewQueries: [{ propertyName: "fieldHost", first: true, predicate: ["fieldHost"], descendants: true, read: ViewContainerRef, static: true }], usesOnChanges: true, ngImport: i0, template: `<ng-container #fieldHost></ng-container>`, isInline: true, styles: [":host{display:block;width:100%;min-width:0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5161
5053
|
}
|
|
5162
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
5054
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DynamicFieldComponent, decorators: [{
|
|
5163
5055
|
type: Component,
|
|
5164
5056
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-dynamic-field', standalone: true, template: `<ng-container #fieldHost></ng-container>`, styles: [":host{display:block;width:100%;min-width:0}\n"] }]
|
|
5165
5057
|
}], propDecorators: { field: [{
|
|
@@ -5203,8 +5095,8 @@ class GroupFieldComponent {
|
|
|
5203
5095
|
const group = this.control;
|
|
5204
5096
|
return group?.get(childId);
|
|
5205
5097
|
}
|
|
5206
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5207
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
5098
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: GroupFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5099
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: GroupFieldComponent, isStandalone: true, selector: "ngx-group-field", inputs: { field: "field", control: "control", config: "config", language: "language", readonly: "readonly", masked: "masked", userRoles: "userRoles" }, ngImport: i0, template: `
|
|
5208
5100
|
<fieldset
|
|
5209
5101
|
class="ngx-field ngx-field--group"
|
|
5210
5102
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -5245,7 +5137,7 @@ class GroupFieldComponent {
|
|
|
5245
5137
|
</fieldset>
|
|
5246
5138
|
`, 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 });
|
|
5247
5139
|
}
|
|
5248
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
5140
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: GroupFieldComponent, decorators: [{
|
|
5249
5141
|
type: Component,
|
|
5250
5142
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-group-field', standalone: true, imports: [ReactiveFormsModule, forwardRef(() => DynamicFieldComponent)], template: `
|
|
5251
5143
|
<fieldset
|
|
@@ -5370,10 +5262,8 @@ class ValidatorRegistryService {
|
|
|
5370
5262
|
}
|
|
5371
5263
|
if (config.email)
|
|
5372
5264
|
fnList.push(Validators.email);
|
|
5373
|
-
// Named validators from the consumer registry
|
|
5374
|
-
//
|
|
5375
|
-
// a validator the consumer had registered could not be named from a typed schema
|
|
5376
|
-
// without casting it to `any`.
|
|
5265
|
+
// Named validators from the consumer registry, so a registered validator can be named
|
|
5266
|
+
// from a typed schema.
|
|
5377
5267
|
for (const key of config.custom ?? []) {
|
|
5378
5268
|
const fn = this.resolve(key);
|
|
5379
5269
|
if (fn)
|
|
@@ -5409,10 +5299,9 @@ class ValidatorRegistryService {
|
|
|
5409
5299
|
/**
|
|
5410
5300
|
* Say when a named validator resolved to nothing.
|
|
5411
5301
|
*
|
|
5412
|
-
*
|
|
5413
|
-
*
|
|
5414
|
-
*
|
|
5415
|
-
* lands in the database.
|
|
5302
|
+
* Silence would be the worst failure mode this library has: the schema says the field is
|
|
5303
|
+
* checked, the field is not checked, the form saves, and nothing says so. For
|
|
5304
|
+
* `customAsync` that is typically a uniqueness check, so the duplicate lands in the database.
|
|
5416
5305
|
*
|
|
5417
5306
|
* Dev only, once per name — this runs while controls are built, which happens on every
|
|
5418
5307
|
* config change. `validateConfig` with `knownValidators` is the build-time version of the
|
|
@@ -5430,10 +5319,10 @@ class ValidatorRegistryService {
|
|
|
5430
5319
|
this.warned.add(id);
|
|
5431
5320
|
console.warn(`[ngx-dynamic-entity] ${message}`);
|
|
5432
5321
|
}
|
|
5433
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5434
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
5322
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ValidatorRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
5323
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ValidatorRegistryService, providedIn: 'root' });
|
|
5435
5324
|
}
|
|
5436
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
5325
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ValidatorRegistryService, decorators: [{
|
|
5437
5326
|
type: Injectable,
|
|
5438
5327
|
args: [{ providedIn: 'root' }]
|
|
5439
5328
|
}] });
|
|
@@ -5486,8 +5375,8 @@ class ArrayFieldComponent {
|
|
|
5486
5375
|
this.formArray.removeAt(index);
|
|
5487
5376
|
this.cdr.markForCheck();
|
|
5488
5377
|
}
|
|
5489
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5490
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
5378
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5379
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: ArrayFieldComponent, isStandalone: true, selector: "ngx-array-field", inputs: { field: "field", control: "control", config: "config", language: "language", readonly: "readonly", masked: "masked", userRoles: "userRoles" }, ngImport: i0, template: `
|
|
5491
5380
|
<div
|
|
5492
5381
|
class="ngx-field ngx-field--array"
|
|
5493
5382
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -5566,7 +5455,7 @@ class ArrayFieldComponent {
|
|
|
5566
5455
|
</div>
|
|
5567
5456
|
`, 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 });
|
|
5568
5457
|
}
|
|
5569
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
5458
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ArrayFieldComponent, decorators: [{
|
|
5570
5459
|
type: Component,
|
|
5571
5460
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-array-field', standalone: true, imports: [ReactiveFormsModule, forwardRef(() => DynamicFieldComponent)], template: `
|
|
5572
5461
|
<div
|
|
@@ -5740,10 +5629,10 @@ class FileUploadService {
|
|
|
5740
5629
|
value !== null &&
|
|
5741
5630
|
typeof value.subscribe === 'function');
|
|
5742
5631
|
}
|
|
5743
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5744
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
5632
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FileUploadService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
5633
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FileUploadService, providedIn: 'root' });
|
|
5745
5634
|
}
|
|
5746
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
5635
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FileUploadService, decorators: [{
|
|
5747
5636
|
type: Injectable,
|
|
5748
5637
|
args: [{ providedIn: 'root' }]
|
|
5749
5638
|
}] });
|
|
@@ -5828,8 +5717,8 @@ class ImageFieldComponent {
|
|
|
5828
5717
|
return '';
|
|
5829
5718
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
5830
5719
|
}
|
|
5831
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5832
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
5720
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImageFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5721
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: ImageFieldComponent, isStandalone: true, selector: "ngx-image-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
5833
5722
|
<div
|
|
5834
5723
|
class="ngx-field ngx-field--image"
|
|
5835
5724
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -5908,7 +5797,7 @@ class ImageFieldComponent {
|
|
|
5908
5797
|
</div>
|
|
5909
5798
|
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5910
5799
|
}
|
|
5911
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
5800
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImageFieldComponent, decorators: [{
|
|
5912
5801
|
type: Component,
|
|
5913
5802
|
args: [{
|
|
5914
5803
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -6078,8 +5967,8 @@ class FileFieldComponent {
|
|
|
6078
5967
|
return '';
|
|
6079
5968
|
return this.messages.resolveForField(this.control.errors, this.language, this.field.type);
|
|
6080
5969
|
}
|
|
6081
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
6082
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
5970
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FileFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5971
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: FileFieldComponent, isStandalone: true, selector: "ngx-file-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
6083
5972
|
<div
|
|
6084
5973
|
class="ngx-field ngx-field--file"
|
|
6085
5974
|
[attr.data-testid]="'field-' + field.id"
|
|
@@ -6161,7 +6050,7 @@ class FileFieldComponent {
|
|
|
6161
6050
|
</div>
|
|
6162
6051
|
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6163
6052
|
}
|
|
6164
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6053
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FileFieldComponent, decorators: [{
|
|
6165
6054
|
type: Component,
|
|
6166
6055
|
args: [{
|
|
6167
6056
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
@@ -6331,10 +6220,10 @@ class HookRegistryService {
|
|
|
6331
6220
|
return data;
|
|
6332
6221
|
return hook(data, context);
|
|
6333
6222
|
}
|
|
6334
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
6335
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
6223
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HookRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
6224
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HookRegistryService, providedIn: 'root' });
|
|
6336
6225
|
}
|
|
6337
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6226
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: HookRegistryService, decorators: [{
|
|
6338
6227
|
type: Injectable,
|
|
6339
6228
|
args: [{ providedIn: 'root' }]
|
|
6340
6229
|
}] });
|
|
@@ -6342,13 +6231,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
6342
6231
|
/**
|
|
6343
6232
|
* RulesEvaluationService — evaluates rules, and owns what a result *means*.
|
|
6344
6233
|
*
|
|
6345
|
-
*
|
|
6346
|
-
*
|
|
6347
|
-
* decided again forty lines further down. They disagreed about what "every field" meant, and
|
|
6348
|
-
* the consequence was a required field inside a `group` that a rule had hidden holding
|
|
6349
|
-
* `form.invalid` true forever with nothing on screen to explain it.
|
|
6350
|
-
*
|
|
6351
|
-
* Precedence lives here now, in one method that both callers go through. Still stateless —
|
|
6234
|
+
* Precedence lives in one method that both the render filter and the validity sync go
|
|
6235
|
+
* through, so what is on screen and what counts toward validity cannot disagree. Stateless —
|
|
6352
6236
|
* every method takes the result it is interpreting, so there is nothing to go stale against a
|
|
6353
6237
|
* rebuilt form and nothing to reset between configs.
|
|
6354
6238
|
*/
|
|
@@ -6448,10 +6332,10 @@ class RulesEvaluationService {
|
|
|
6448
6332
|
return sets;
|
|
6449
6333
|
}
|
|
6450
6334
|
nameSetCache = new WeakMap();
|
|
6451
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
6452
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
6335
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RulesEvaluationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
6336
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RulesEvaluationService, providedIn: 'root' });
|
|
6453
6337
|
}
|
|
6454
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6338
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: RulesEvaluationService, decorators: [{
|
|
6455
6339
|
type: Injectable,
|
|
6456
6340
|
args: [{ providedIn: 'root' }]
|
|
6457
6341
|
}] });
|
|
@@ -6506,10 +6390,10 @@ class ConfigSourceService {
|
|
|
6506
6390
|
this.cache.clear();
|
|
6507
6391
|
}
|
|
6508
6392
|
}
|
|
6509
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
6510
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
6393
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ConfigSourceService, deps: [{ token: CONFIG_SOURCE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
6394
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ConfigSourceService, providedIn: 'root' });
|
|
6511
6395
|
}
|
|
6512
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6396
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ConfigSourceService, decorators: [{
|
|
6513
6397
|
type: Injectable,
|
|
6514
6398
|
args: [{ providedIn: 'root' }]
|
|
6515
6399
|
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
@@ -6524,9 +6408,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
6524
6408
|
*
|
|
6525
6409
|
* The one question this answers is *where a value lives*: which control a field owns, how
|
|
6526
6410
|
* the tab tree nests into groups, how a record is read out of the tree and patched back in.
|
|
6527
|
-
*
|
|
6528
|
-
* evaluation and RBAC — and the cost of that was not length but ownership: the bug Phase 2
|
|
6529
|
-
* fixed was two methods forty lines apart disagreeing about what "every field" meant.
|
|
6411
|
+
* Anything that has to find a field's control goes through here, so there is one answer.
|
|
6530
6412
|
*
|
|
6531
6413
|
* Deliberately stateless. Every method takes the form and the config it is operating on, so
|
|
6532
6414
|
* there is no cached `this.form` to go stale against a rebuilt one, and every method is
|
|
@@ -6618,6 +6500,44 @@ class FormStructureService {
|
|
|
6618
6500
|
}
|
|
6619
6501
|
return curr instanceof FormGroup ? curr : null;
|
|
6620
6502
|
}
|
|
6503
|
+
/**
|
|
6504
|
+
* The control a scope entry addresses, found by its path rather than its bare id.
|
|
6505
|
+
*
|
|
6506
|
+
* The control tree mirrors the scope path exactly — a tab nests by its id and a `group`
|
|
6507
|
+
* under its own — so `form.get(path)` names exactly one control. A bare-id lookup falls
|
|
6508
|
+
* back to a first-match search and, with `address` on two tabs, finds the wrong one.
|
|
6509
|
+
*
|
|
6510
|
+
* A field inside an `array` has no static path: its controls live in `FormArray` rows
|
|
6511
|
+
* built per row. `form.get()` returns null for those, and callers leave them alone.
|
|
6512
|
+
*/
|
|
6513
|
+
controlAt(form, entry) {
|
|
6514
|
+
return form?.get(fieldRefFor(entry.scope, entry.field.id)) ?? null;
|
|
6515
|
+
}
|
|
6516
|
+
/**
|
|
6517
|
+
* The value map rules and `showWhen` are evaluated against.
|
|
6518
|
+
*
|
|
6519
|
+
* Every field appears under two keys: its bare id, and its ref wrapped in brackets. The
|
|
6520
|
+
* bare id is what most configs use, but ids are unique only per scope, so when two scopes
|
|
6521
|
+
* define one the last field walked wins. `[personal.address]` names one field and cannot
|
|
6522
|
+
* be ambiguous, which is why a rule that has to tell them apart uses the ref.
|
|
6523
|
+
*
|
|
6524
|
+
* Both live in one flat map on purpose: `evaluateFormRules` takes a `Record<string,
|
|
6525
|
+
* unknown>` and needs no knowledge of refs at all — the extra keys simply resolve.
|
|
6526
|
+
*/
|
|
6527
|
+
flattenValues(form, entries) {
|
|
6528
|
+
const out = {};
|
|
6529
|
+
for (const entry of entries) {
|
|
6530
|
+
const field = entry.field;
|
|
6531
|
+
if (!field?.id)
|
|
6532
|
+
continue;
|
|
6533
|
+
const ctrl = this.controlAt(form, entry);
|
|
6534
|
+
if (!ctrl)
|
|
6535
|
+
continue;
|
|
6536
|
+
out[field.id] = ctrl.value;
|
|
6537
|
+
out[toRefToken(refOf(field, entry.scope))] = ctrl.value;
|
|
6538
|
+
}
|
|
6539
|
+
return out;
|
|
6540
|
+
}
|
|
6621
6541
|
/**
|
|
6622
6542
|
* Find a control by reference: a bracketed path, or a bare field id.
|
|
6623
6543
|
*
|
|
@@ -6761,10 +6681,10 @@ class FormStructureService {
|
|
|
6761
6681
|
fieldsById.has(key) &&
|
|
6762
6682
|
!patchedFieldIds.has(key));
|
|
6763
6683
|
}
|
|
6764
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
6765
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
6684
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FormStructureService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
6685
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FormStructureService });
|
|
6766
6686
|
}
|
|
6767
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6687
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: FormStructureService, decorators: [{
|
|
6768
6688
|
type: Injectable
|
|
6769
6689
|
}] });
|
|
6770
6690
|
|
|
@@ -6937,9 +6857,8 @@ class DynamicFormComponent {
|
|
|
6937
6857
|
// ─── Computed ─────────────────────────────────────────────────────────────
|
|
6938
6858
|
ruleResult = computed(() => this.rulesEvaluation.evaluate(this.rules, this.formValues(), this.baseline(), {
|
|
6939
6859
|
// The engine reports rather than logs, because it also runs on a server. This is the
|
|
6940
|
-
// client's answer: say it once per distinct problem, in dev only
|
|
6941
|
-
//
|
|
6942
|
-
// silently skipped rule is exactly as hard to diagnose as a thrown one.
|
|
6860
|
+
// client's answer: say it once per distinct problem, in dev only — a silently skipped
|
|
6861
|
+
// rule is as hard to diagnose as a thrown one.
|
|
6943
6862
|
onProblem: message => this.warnOnce(message),
|
|
6944
6863
|
}), ...(ngDevMode ? [{ debugName: "ruleResult" }] : /* istanbul ignore next */ []));
|
|
6945
6864
|
/** Warned-about rule problems, per component instance — see `warnedAmbiguousIds`. */
|
|
@@ -7056,8 +6975,7 @@ class DynamicFormComponent {
|
|
|
7056
6975
|
/**
|
|
7057
6976
|
* Whether the current roles may see this record at all.
|
|
7058
6977
|
*
|
|
7059
|
-
* `permissions.view`
|
|
7060
|
-
* still received the complete form with every value in the DOM. It is honoured now.
|
|
6978
|
+
* A user whose roles fail `permissions.view` gets no form, so no value reaches the DOM.
|
|
7061
6979
|
*
|
|
7062
6980
|
* As with masking, this is presentational: it stops the browser rendering data, it does
|
|
7063
6981
|
* not stop the data reaching the browser. Authorize on the server.
|
|
@@ -7115,13 +7033,11 @@ class DynamicFormComponent {
|
|
|
7115
7033
|
/**
|
|
7116
7034
|
* Whether the Save button is unavailable.
|
|
7117
7035
|
*
|
|
7118
|
-
*
|
|
7119
|
-
*
|
|
7120
|
-
*
|
|
7121
|
-
* user is left comparing a disabled button against a form that looks, to them, filled in.
|
|
7122
|
-
* On a tabbed form the offending field is usually not even on screen.
|
|
7036
|
+
* Deliberately not `submitBlocked`: a greyed button is the worst answer to "why can't I
|
|
7037
|
+
* save?". It cannot be clicked, so the form never gets to say which field is at fault or
|
|
7038
|
+
* which tab it is on — and on a tabbed form that field is usually not on screen.
|
|
7123
7039
|
*
|
|
7124
|
-
* Save
|
|
7040
|
+
* Save stays available while the form is merely invalid. Clicking it still saves
|
|
7125
7041
|
* nothing — `submit()` checks `submitBlocked` and refuses — but the refusal is where the
|
|
7126
7042
|
* error summary, the tab badges and the jump to the first bad field come from, so pressing
|
|
7127
7043
|
* Save produces an explanation instead of silence.
|
|
@@ -7156,10 +7072,9 @@ class DynamicFormComponent {
|
|
|
7156
7072
|
this.buildForm();
|
|
7157
7073
|
// Clear *before* the guard below, not after it.
|
|
7158
7074
|
//
|
|
7159
|
-
//
|
|
7160
|
-
//
|
|
7161
|
-
//
|
|
7162
|
-
// survived into the next one. Clearing is unconditional and cheap, and safe on its own
|
|
7075
|
+
// A swap can arrive while no tab is visible — a config still being applied, or every
|
|
7076
|
+
// tab hidden by a rule at that instant — and the previous record's tab must not survive
|
|
7077
|
+
// into the next one. Clearing is unconditional and cheap, and safe on its own
|
|
7163
7078
|
// because `activeTabConfig` already falls back to the first visible tab when nothing
|
|
7164
7079
|
// is selected. Selecting the tab is what needs tabs to exist; forgetting the old one
|
|
7165
7080
|
// does not.
|
|
@@ -7169,14 +7084,9 @@ class DynamicFormComponent {
|
|
|
7169
7084
|
}
|
|
7170
7085
|
if (this.visibleTabs.length === 0)
|
|
7171
7086
|
return;
|
|
7172
|
-
//
|
|
7173
|
-
//
|
|
7174
|
-
//
|
|
7175
|
-
// by destroying the form between records; a host that keeps it mounted did not.
|
|
7176
|
-
//
|
|
7177
|
-
// Resetting here is consistent with what already happens: `buildForm` assigns a brand
|
|
7178
|
-
// new FormGroup, so a changed `initialData` has already discarded every control and
|
|
7179
|
-
// all validation state. The tab was the one thing pretending nothing had changed.
|
|
7087
|
+
// A form that stays mounted while `initialData` is swapped must open the next record
|
|
7088
|
+
// on its first tab, not the tab the previous one was left on. `buildForm` has already
|
|
7089
|
+
// discarded every control and all validation state; the active tab resets with them.
|
|
7180
7090
|
if (!this.activeTab()) {
|
|
7181
7091
|
// No focus steal: the panel gains focus when a *user* picks a tab, not when a host
|
|
7182
7092
|
// swaps the record underneath them.
|
|
@@ -7610,25 +7520,10 @@ class DynamicFormComponent {
|
|
|
7610
7520
|
/**
|
|
7611
7521
|
* Ids already warned about, per component instance.
|
|
7612
7522
|
*
|
|
7613
|
-
*
|
|
7614
|
-
*
|
|
7615
|
-
* that lived as long as the server did, and the first request to render a config
|
|
7616
|
-
* suppressed the warning for every request after it. Per instance, the warning is once per
|
|
7617
|
-
* form — which is what "warn once" was meant to mean.
|
|
7523
|
+
* Per instance, never `static`: under SSR a module-global set would outlive every request,
|
|
7524
|
+
* and the first render of a config would suppress the warning for every render after it.
|
|
7618
7525
|
*/
|
|
7619
7526
|
warnedAmbiguousIds = new Set();
|
|
7620
|
-
/**
|
|
7621
|
-
* The value map rules and `showWhen` are evaluated against.
|
|
7622
|
-
*
|
|
7623
|
-
* Every field appears under two keys: its bare id, and its `refererField` path wrapped in brackets.
|
|
7624
|
-
* The bare id is what every config written so far uses and is kept exactly as it was — but
|
|
7625
|
-
* ids are unique per scope, so when two scopes define one the last field walked wins and
|
|
7626
|
-
* the rule reads whichever that is. `[personal.address]` names one field and cannot be
|
|
7627
|
-
* ambiguous, which is why a rule that has to distinguish them uses the ref.
|
|
7628
|
-
*
|
|
7629
|
-
* Both live in one flat map on purpose: `evaluateFormRules` takes a `Record<string,
|
|
7630
|
-
* unknown>` and needs no knowledge of refs at all — the extra keys simply resolve.
|
|
7631
|
-
*/
|
|
7632
7527
|
/**
|
|
7633
7528
|
* Every name a rule may address this field by.
|
|
7634
7529
|
*
|
|
@@ -7662,9 +7557,8 @@ class DynamicFormComponent {
|
|
|
7662
7557
|
/**
|
|
7663
7558
|
* Every field in the config with the scope its value is stored under, cached per config.
|
|
7664
7559
|
*
|
|
7665
|
-
* `collectFieldScopes` walks the whole tree
|
|
7666
|
-
*
|
|
7667
|
-
* tab — a quadratic walk on every change-detection pass. Keyed by config identity, so a
|
|
7560
|
+
* `collectFieldScopes` walks the whole tree, and `pathFor` runs once per rendered field on
|
|
7561
|
+
* every change-detection pass — uncached, that is quadratic. Keyed by config identity, so a
|
|
7668
7562
|
* new config invalidates it without an explicit lifecycle hook to forget.
|
|
7669
7563
|
*/
|
|
7670
7564
|
fieldScopes() {
|
|
@@ -7683,35 +7577,13 @@ class DynamicFormComponent {
|
|
|
7683
7577
|
return this.scopeCache?.byField.get(field);
|
|
7684
7578
|
}
|
|
7685
7579
|
scopeCache;
|
|
7686
|
-
/**
|
|
7687
|
-
* The control a scope entry addresses, named by path rather than by bare id.
|
|
7688
|
-
*
|
|
7689
|
-
* `getControl(field.id)` with no tab falls through to a first-match recursive search of the
|
|
7690
|
-
* whole form, so with `address` on two tabs, hiding one disabled the other. The control
|
|
7691
|
-
* tree mirrors the scope path exactly — `buildTabControls` nests by tab id and
|
|
7692
|
-
* `buildFieldControl` nests a `group` under its own id — so a `[path]` ref resolves through
|
|
7693
|
-
* `form.get()` on `getControl`'s first branch and names exactly one control.
|
|
7694
|
-
*
|
|
7695
|
-
* A field inside an `array` has no static path: its controls live in `FormArray` rows built
|
|
7696
|
-
* per row. `form.get()` returns null for those and they are left alone, which is correct —
|
|
7697
|
-
* a row's controls are created and destroyed with the row.
|
|
7698
|
-
*/
|
|
7580
|
+
/** The control a scope entry addresses — see `FormStructureService.controlAt`. */
|
|
7699
7581
|
controlForEntry(entry) {
|
|
7700
|
-
return this.
|
|
7582
|
+
return this.structure.controlAt(this.form, entry);
|
|
7701
7583
|
}
|
|
7584
|
+
/** The value map rules and `showWhen` are evaluated against — see `FormStructureService.flattenValues`. */
|
|
7702
7585
|
flattenFormValues() {
|
|
7703
|
-
|
|
7704
|
-
for (const entry of collectFieldScopes(this.config)) {
|
|
7705
|
-
const field = entry.field;
|
|
7706
|
-
if (!field?.id)
|
|
7707
|
-
continue;
|
|
7708
|
-
const ctrl = this.getControl(field.id, entry.scope.split('.').pop());
|
|
7709
|
-
if (!ctrl)
|
|
7710
|
-
continue;
|
|
7711
|
-
out[field.id] = ctrl.value;
|
|
7712
|
-
out[toRefToken(refOf(field, entry.scope))] = ctrl.value;
|
|
7713
|
-
}
|
|
7714
|
-
return out;
|
|
7586
|
+
return this.structure.flattenValues(this.form, this.fieldScopes());
|
|
7715
7587
|
}
|
|
7716
7588
|
// ─── autoPatch / patchOnTrue ──────────────────────────────────────────────
|
|
7717
7589
|
/**
|
|
@@ -7738,22 +7610,46 @@ class DynamicFormComponent {
|
|
|
7738
7610
|
/**
|
|
7739
7611
|
* `patchOnTrue` — when a boolean/checkbox field transitions to `true`,
|
|
7740
7612
|
* copy `from` → `to` within the current record.
|
|
7613
|
+
*
|
|
7614
|
+
* The trigger is read by its ref, and a bare `from`/`to` resolves to a sibling in the
|
|
7615
|
+
* trigger's own scope before falling back to a form-wide lookup — so two groups that each
|
|
7616
|
+
* carry `home`, `billing` and a "same as" flag patch within themselves, not across.
|
|
7741
7617
|
*/
|
|
7742
7618
|
runPatchOnTrue(values) {
|
|
7743
|
-
for (const
|
|
7744
|
-
const
|
|
7745
|
-
|
|
7619
|
+
for (const entry of this.fieldScopes()) {
|
|
7620
|
+
const field = entry.field;
|
|
7621
|
+
const mappings = field?.patchOnTrue;
|
|
7622
|
+
if (!field?.id || !mappings?.length)
|
|
7746
7623
|
continue;
|
|
7747
|
-
const
|
|
7748
|
-
|
|
7749
|
-
if (!isTrue || wasTrue)
|
|
7624
|
+
const trigger = toRefToken(refOf(field, entry.scope));
|
|
7625
|
+
if (values[trigger] !== true || this.previousValues[trigger] === true)
|
|
7750
7626
|
continue;
|
|
7751
|
-
const
|
|
7752
|
-
|
|
7753
|
-
|
|
7627
|
+
for (const mapping of mappings) {
|
|
7628
|
+
if (!mapping || isUnsafePath(mapping.to))
|
|
7629
|
+
continue;
|
|
7630
|
+
const from = this.resolveInScope(mapping.from, entry.scope);
|
|
7631
|
+
if (!from || !(from.key in values))
|
|
7632
|
+
continue;
|
|
7633
|
+
this.resolveInScope(mapping.to, entry.scope)?.control?.patchValue(values[from.key], { emitEvent: false });
|
|
7754
7634
|
}
|
|
7755
7635
|
}
|
|
7756
7636
|
}
|
|
7637
|
+
/**
|
|
7638
|
+
* A field named by a mapping, preferring a sibling in `scope` when the name is a bare id.
|
|
7639
|
+
*
|
|
7640
|
+
* `key` is the field's entry in the flattened value map; `control` is the control it owns.
|
|
7641
|
+
*/
|
|
7642
|
+
resolveInScope(name, scope) {
|
|
7643
|
+
if (typeof name !== 'string' || !name)
|
|
7644
|
+
return null;
|
|
7645
|
+
const parsed = parseFieldRef(name);
|
|
7646
|
+
if (parsed.kind !== 'ref') {
|
|
7647
|
+
const sibling = this.fieldScopes().find(e => e.scope === scope && e.field?.id === parsed.value);
|
|
7648
|
+
if (sibling)
|
|
7649
|
+
return { key: toRefToken(refOf(sibling.field, sibling.scope)), control: this.controlForEntry(sibling) };
|
|
7650
|
+
}
|
|
7651
|
+
return { key: name, control: this.getControl(name) };
|
|
7652
|
+
}
|
|
7757
7653
|
/**
|
|
7758
7654
|
* Prefer a control declared on the configured target tab; fall back to a top-level control.
|
|
7759
7655
|
*
|
|
@@ -7889,11 +7785,9 @@ class DynamicFormComponent {
|
|
|
7889
7785
|
async submit() {
|
|
7890
7786
|
if (!this.canSubmit || this.submitBlocked) {
|
|
7891
7787
|
/*
|
|
7892
|
-
*
|
|
7893
|
-
*
|
|
7894
|
-
*
|
|
7895
|
-
* whichever tabs hold them, the user is looking at a different one, and nothing on
|
|
7896
|
-
* screen changes. Three things fix it, and all three are needed — the summary says how
|
|
7788
|
+
* Marking every control touched is not enough on its own: on a tabbed form the errors
|
|
7789
|
+
* appear on whichever tabs hold them, the user is looking at a different one, and
|
|
7790
|
+
* nothing on screen changes. Three things are needed — the summary says how
|
|
7897
7791
|
* many and which, the tab badges say where, and this jump takes the user to the first.
|
|
7898
7792
|
*/
|
|
7899
7793
|
this.markAllTouched();
|
|
@@ -7946,10 +7840,10 @@ class DynamicFormComponent {
|
|
|
7946
7840
|
this.submitAttempted.set(false);
|
|
7947
7841
|
this.formReset.emit();
|
|
7948
7842
|
}
|
|
7949
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
7950
|
-
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", layout: "layout" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", activeTabChange: "activeTabChange", saveRejected: "saveRejected" }, host: { listeners: { "window:keydown": "handleKeyboardEvent($event)" } }, providers: [EntityRefSelectionService, FormStructureService], viewQueries: [{ propertyName: "formPanel", first: true, predicate: ["formPanel"], descendants: true }, { propertyName: "fieldHosts", predicate: DynamicFieldComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "@if (canView) {\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\" class=\"ngx-form\" [class.ngx-form--readonly]=\"readonly\" novalidate>\n <!-- Error message banner -->\n @if (error) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" data-testid=\"form-error\">{{ error }}</div>\n }\n\n <!--\n Why the last Save did nothing.\n Only after a refused submit \u2014 see `submitAttempted`. Each entry jumps to its field,\n switching tabs on the way, because the field is usually not the one on screen.\n -->\n @if (submitAttempted() && invalidFields().length > 0) {\n <div class=\"ngx-form__error-summary\" role=\"alert\" data-testid=\"error-summary\" tabindex=\"-1\">\n <!--\n `error-summary-heading`, not `-title`: the links below are `error-summary-<fieldId>`,\n and a field whose id happens to be `title` would then share a test id with this line.\n The `extensions` entity has exactly such a field.\n -->\n <p class=\"ngx-form__error-summary-title\" data-testid=\"error-summary-heading\">\n {{ ui.text('errorSummaryTitle', language, { count: invalidFields().length }) }}\n </p>\n <ul class=\"ngx-form__error-summary-list\">\n @for (entry of invalidFields(); track entry.field.id) {\n <li>\n <!--\n The field, and what is wrong with it \u2014 the same sentence the field renders\n under itself, so the summary is not a second opinion. Naming only the field\n left the user to visit each one to find out what it wanted.\n -->\n <button\n type=\"button\"\n class=\"ngx-form__error-summary-link\"\n [attr.data-testid]=\"'error-summary-' + entry.field.id\"\n (click)=\"jumpToField(entry.field.id)\"\n >\n <span class=\"ngx-form__error-summary-field\">{{ resolveFieldLabel(entry.field) }}</span>\n @if (entry.message) {\n <span class=\"ngx-form__error-summary-reason\">{{ entry.message }}</span>\n }\n </button>\n </li>\n }\n </ul>\n </div>\n }\n\n <!-- Rules Info Banners -->\n @for (infoKey of showInfoBanners ? Object.keys(ruleResult().infoBanners) : []; track infoKey) {\n <div class=\"ngx-form__info-banner\" role=\"status\" [attr.data-testid]=\"'info-banner-' + infoKey\">\n \u2139\uFE0F {{ ruleResult().infoBanners[infoKey] }}\n </div>\n }\n\n <!-- Rules Validation Errors & Warnings -->\n @for (errKey of Object.keys(ruleResult().validationErrors); track errKey) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" [attr.data-testid]=\"'rule-error-' + errKey\">\n \u26A0\uFE0F {{ ruleResult().validationErrors[errKey] }}\n </div>\n }\n @for (warnKey of Object.keys(ruleResult().validationWarnings); track warnKey) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" [attr.data-testid]=\"'rule-warning-' + warnKey\">\n \u26A1 {{ ruleResult().validationWarnings[warnKey] }}\n </div>\n }\n\n <!-- Deferred VALUE_CHANGED notice for criticalField edits (vs session baseline) -->\n @if (changedCriticalFields().length > 0) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" data-testid=\"critical-change-banner\">\n {{ ui.text('criticalFieldChanged', language, { fields: changedCriticalFieldLabels() }) }}\n </div>\n }\n\n <!-- Primary Tab bar \u2014 only if visible tabs defined -->\n @if (visibleTabs.length > 0) {\n <div class=\"ngx-form__tabs\" role=\"tablist\" data-testid=\"tab-strip\">\n @for (tab of visibleTabs; track tab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab\"\n [attr.data-testid]=\"'tab-' + tab.id\"\n [class.ngx-form__tab--active]=\"activeTab() === tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n >\n {{ resolveTabLabel(tab) }}\n <!--\n How many fields on this tab are stopping the save.\n The count is in the accessible name rather than the badge, because \"3\" read out\n after a tab name says nothing about what it counts.\n -->\n @if (tabErrorCount(tab); as errors) {\n <span\n class=\"ngx-form__tab-badge\"\n [attr.data-testid]=\"'tab-errors-' + tab.id\"\n [attr.aria-label]=\"ui.text('tabErrorCount', language, { count: errors })\"\n >{{ errors }}</span\n >\n }\n </button>\n }\n </div>\n }\n\n <!-- Sub-tab bar \u2014 only if active tab has sub-tabs -->\n @if (visibleSubTabs.length > 0) {\n <div\n class=\"ngx-form__tabs ngx-form__tabs--sub\"\n role=\"tablist\"\n data-testid=\"subtab-strip\"\n style=\"margin-top: -12px; margin-bottom: 12px; font-size: 13px\"\n >\n @for (subTab of visibleSubTabs; track subTab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab ngx-form__tab--sub\"\n [attr.data-testid]=\"'subtab-' + subTab.id\"\n [class.ngx-form__tab--active]=\"activeSubTabConfig?.id === subTab.id\"\n (click)=\"setActiveSubTab(subTab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"activeSubTabConfig?.id === subTab.id\"\n >\n {{ resolveTabLabel(subTab) }}\n @if (subTabErrorCount(subTab); as errors) {\n <span\n class=\"ngx-form__tab-badge\"\n [attr.data-testid]=\"'subtab-errors-' + subTab.id\"\n [attr.aria-label]=\"ui.text('tabErrorCount', language, { count: errors })\"\n >{{ errors }}</span\n >\n }\n </button>\n }\n </div>\n }\n\n <!--\n One tabpanel per tab, wrapping both branches. Focus moves here on a tab switch, and a\n module tab is still a tab \u2014 putting the panel semantics on only the fields branch left\n a module tab with nowhere for focus to land.\n -->\n <div role=\"tabpanel\" tabindex=\"-1\" #formPanel class=\"ngx-form__tabpanel\" [attr.aria-label]=\"activeTabLabel\">\n @if (activeTabModuleComponent) {\n <div class=\"ngx-form__module-panel\" data-testid=\"module-panel\">\n <ng-container\n *ngComponentOutlet=\"activeTabModuleComponent; inputs: (activeSubTabConfig || activeTabConfig)?.moduleInputs\"\n ></ng-container>\n </div>\n } @else {\n <!-- Fields for active tab (Responsive 12-column grid) -->\n <div class=\"ngx-form__panel\" data-testid=\"form-panel\">\n @for (field of fieldsForActiveTab; track field.id) {\n <div\n class=\"ngx-form__field-slot\"\n [style.gridColumn]=\"getFieldSpan(field)\"\n [attr.id]=\"'field-container-' + field.id\"\n tabindex=\"-1\"\n >\n <ngx-dynamic-field\n [field]=\"field\"\n [control]=\"getControl(field.id, (activeSubTabConfig || activeTabConfig)?.id)!\"\n [config]=\"config\"\n [currentTabId]=\"(activeSubTabConfig || activeTabConfig)?.id\"\n [language]=\"language\"\n [readonly]=\"isFieldReadonly(field)\"\n [userRoles]=\"userRoles\"\n />\n @if (field.hasDrift) {\n <!--\n Drift was previously visible only in the builder, so a referenced field whose\n source had changed looked completely normal to the person filling the form in.\n role=\"status\" rather than \"alert\": it is information, not an error.\n -->\n <span\n class=\"ngx-field__drift\"\n role=\"status\"\n [attr.data-testid]=\"'field-' + field.id + '-drift'\"\n [title]=\"driftHint(field)\"\n >\u26A0 {{ driftHint(field) }}</span\n >\n }\n @if (field.criticalField && !readonly) {\n <button\n type=\"button\"\n class=\"ngx-form__lock\"\n [attr.data-testid]=\"'lock-' + field.id\"\n [attr.aria-pressed]=\"!isFieldLocked(field)\"\n [attr.aria-label]=\"\n ui.text(isFieldLocked(field) ? 'unlockField' : 'lockField', language, {\n field: resolveFieldLabel(field),\n })\n \"\n [title]=\"ui.text(isFieldLocked(field) ? 'criticalFieldUnlockHint' : 'criticalFieldLockHint', language)\"\n (click)=\"toggleFieldLock(field)\"\n >\n {{ isFieldLocked(field) ? '\uD83D\uDD12' : '\uD83D\uDD13' }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Submit button \u2014 gated by RBAC and isSaving lock -->\n @if (!readonly && permissions.canEdit) {\n <div class=\"ngx-form__actions\" data-testid=\"form-actions\">\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"form-reset\" (click)=\"reset()\" [disabled]=\"isSaving()\">\n {{ ui.text('reset', language) }}\n </button>\n <!--\n Two different \"unavailable\" states, and they are not the same thing.\n\n `disabled` is for what a retry cannot fix \u2014 a save in flight, an async check still\n outstanding. `data-blocked` is for validation: the button stays fully operable,\n because pressing it is how the user gets the error summary, but it should not look\n ready either. The stylesheet mutes it without taking the click away.\n\n Deliberately not `aria-disabled`. That announces a control as present-but-inoperable,\n and this one is operable and useful \u2014 pressing it raises a `role=\"alert\"` naming\n every field at fault, which is more than a disabled button could ever say. Tooling\n agrees: Playwright refuses to click through `aria-disabled`, and it would be right\n to, because the attribute would be claiming something untrue.\n -->\n <button\n type=\"submit\"\n class=\"ngx-form__submit\"\n data-testid=\"form-submit\"\n [disabled]=\"submitDisabled\"\n [attr.data-blocked]=\"submitBlocked ? '' : null\"\n [title]=\"ui.text('saveShortcutHint', language)\"\n >\n @if (isSaving() || loading) {\n {{ ui.text('saving', language) }}\n } @else {\n {{ ui.text('save', language) }}\n }\n </button>\n </div>\n }\n </form>\n} @else {\n <!-- permissions.view denied: render nothing of the record.\n Presentational only \u2014 the server must not send data this user may not see. -->\n <div class=\"ngx-form__denied\" role=\"status\" data-testid=\"form-access-denied\">\n {{ ui.text('accessDenied', language) }}\n </div>\n}\n", styles: [".ngx-form__panel{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:var(--ngx-gap, 18px);align-items:start}.ngx-form__field-slot{display:flex;align-items:flex-start;gap:6px;min-width:0}.ngx-form__field-slot ngx-dynamic-field{flex:1;min-width:0}.ngx-form__lock{flex:0 0 auto;margin-top:22px;background:none;border:1px solid transparent;border-radius:var(--ngx-radius-sm, 6px);cursor:pointer;font-size:14px;line-height:1;padding:4px 6px}.ngx-form__lock:hover{border-color:var(--ngx-color-border, #d1d5db);background:var(--ngx-color-surface-alt, #f9fafb)}@media(max-width:640px){.ngx-form__panel{grid-template-columns:minmax(0,1fr)}.ngx-form__field-slot{grid-column:1 / -1!important}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicFieldComponent, selector: "ngx-dynamic-field", inputs: ["field", "control", "config", "currentTabId", "language", "readonly", "userRoles"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7843
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DynamicFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7844
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: DynamicFormComponent, isStandalone: true, selector: "ngx-dynamic-form", inputs: { config: "config", rules: "rules", initialData: "initialData", originalData: "originalData", userRoles: "userRoles", language: "language", readonly: "readonly", readOnlyFields: "readOnlyFields", showInfoBanners: "showInfoBanners", loading: "loading", error: "error", changeDebounceMs: "changeDebounceMs", preview: "preview", layout: "layout" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", activeTabChange: "activeTabChange", saveRejected: "saveRejected" }, host: { listeners: { "window:keydown": "handleKeyboardEvent($event)" } }, providers: [EntityRefSelectionService, FormStructureService], viewQueries: [{ propertyName: "formPanel", first: true, predicate: ["formPanel"], descendants: true }, { propertyName: "fieldHosts", predicate: DynamicFieldComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "@if (canView) {\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\" class=\"ngx-form\" [class.ngx-form--readonly]=\"readonly\" novalidate>\n <!-- Error message banner -->\n @if (error) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" data-testid=\"form-error\">{{ error }}</div>\n }\n\n <!--\n Why the last Save did nothing.\n Only after a refused submit \u2014 see `submitAttempted`. Each entry jumps to its field,\n switching tabs on the way, because the field is usually not the one on screen.\n -->\n @if (submitAttempted() && invalidFields().length > 0) {\n <div class=\"ngx-form__error-summary\" role=\"alert\" data-testid=\"error-summary\" tabindex=\"-1\">\n <!--\n `error-summary-heading`, not `-title`: the links below are `error-summary-<fieldId>`,\n and a field whose id happens to be `title` would then share a test id with this line.\n The `extensions` entity has exactly such a field.\n -->\n <p class=\"ngx-form__error-summary-title\" data-testid=\"error-summary-heading\">\n {{ ui.text('errorSummaryTitle', language, { count: invalidFields().length }) }}\n </p>\n <ul class=\"ngx-form__error-summary-list\">\n @for (entry of invalidFields(); track entry.field.id) {\n <li>\n <!--\n The field, and what is wrong with it \u2014 the same sentence the field renders\n under itself, so the summary is not a second opinion. Naming only the field\n left the user to visit each one to find out what it wanted.\n -->\n <button\n type=\"button\"\n class=\"ngx-form__error-summary-link\"\n [attr.data-testid]=\"'error-summary-' + entry.field.id\"\n (click)=\"jumpToField(entry.field.id)\"\n >\n <span class=\"ngx-form__error-summary-field\">{{ resolveFieldLabel(entry.field) }}</span>\n @if (entry.message) {\n <span class=\"ngx-form__error-summary-reason\">{{ entry.message }}</span>\n }\n </button>\n </li>\n }\n </ul>\n </div>\n }\n\n <!-- Rules Info Banners -->\n @for (infoKey of showInfoBanners ? Object.keys(ruleResult().infoBanners) : []; track infoKey) {\n <div class=\"ngx-form__info-banner\" role=\"status\" [attr.data-testid]=\"'info-banner-' + infoKey\">\n \u2139\uFE0F {{ ruleResult().infoBanners[infoKey] }}\n </div>\n }\n\n <!-- Rules Validation Errors & Warnings -->\n @for (errKey of Object.keys(ruleResult().validationErrors); track errKey) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" [attr.data-testid]=\"'rule-error-' + errKey\">\n \u26A0\uFE0F {{ ruleResult().validationErrors[errKey] }}\n </div>\n }\n @for (warnKey of Object.keys(ruleResult().validationWarnings); track warnKey) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" [attr.data-testid]=\"'rule-warning-' + warnKey\">\n \u26A1 {{ ruleResult().validationWarnings[warnKey] }}\n </div>\n }\n\n <!-- Deferred VALUE_CHANGED notice for criticalField edits (vs session baseline) -->\n @if (changedCriticalFields().length > 0) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" data-testid=\"critical-change-banner\">\n {{ ui.text('criticalFieldChanged', language, { fields: changedCriticalFieldLabels() }) }}\n </div>\n }\n\n <!-- Primary Tab bar \u2014 only if visible tabs defined -->\n @if (visibleTabs.length > 0) {\n <div class=\"ngx-form__tabs\" role=\"tablist\" data-testid=\"tab-strip\">\n @for (tab of visibleTabs; track tab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab\"\n [attr.data-testid]=\"'tab-' + tab.id\"\n [class.ngx-form__tab--active]=\"activeTab() === tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n >\n {{ resolveTabLabel(tab) }}\n <!--\n How many fields on this tab are stopping the save.\n The count is in the accessible name rather than the badge, because \"3\" read out\n after a tab name says nothing about what it counts.\n -->\n @if (tabErrorCount(tab); as errors) {\n <span\n class=\"ngx-form__tab-badge\"\n [attr.data-testid]=\"'tab-errors-' + tab.id\"\n [attr.aria-label]=\"ui.text('tabErrorCount', language, { count: errors })\"\n >{{ errors }}</span\n >\n }\n </button>\n }\n </div>\n }\n\n <!-- Sub-tab bar \u2014 only if active tab has sub-tabs -->\n @if (visibleSubTabs.length > 0) {\n <div\n class=\"ngx-form__tabs ngx-form__tabs--sub\"\n role=\"tablist\"\n data-testid=\"subtab-strip\"\n style=\"margin-top: -12px; margin-bottom: 12px; font-size: 13px\"\n >\n @for (subTab of visibleSubTabs; track subTab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab ngx-form__tab--sub\"\n [attr.data-testid]=\"'subtab-' + subTab.id\"\n [class.ngx-form__tab--active]=\"activeSubTabConfig?.id === subTab.id\"\n (click)=\"setActiveSubTab(subTab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"activeSubTabConfig?.id === subTab.id\"\n >\n {{ resolveTabLabel(subTab) }}\n @if (subTabErrorCount(subTab); as errors) {\n <span\n class=\"ngx-form__tab-badge\"\n [attr.data-testid]=\"'subtab-errors-' + subTab.id\"\n [attr.aria-label]=\"ui.text('tabErrorCount', language, { count: errors })\"\n >{{ errors }}</span\n >\n }\n </button>\n }\n </div>\n }\n\n <!--\n One tabpanel per tab, wrapping both branches. Focus moves here on a tab switch, and a\n module tab is still a tab \u2014 putting the panel semantics on only the fields branch left\n a module tab with nowhere for focus to land.\n -->\n <div role=\"tabpanel\" tabindex=\"-1\" #formPanel class=\"ngx-form__tabpanel\" [attr.aria-label]=\"activeTabLabel\">\n @if (activeTabModuleComponent) {\n <div class=\"ngx-form__module-panel\" data-testid=\"module-panel\">\n <ng-container\n *ngComponentOutlet=\"activeTabModuleComponent; inputs: (activeSubTabConfig || activeTabConfig)?.moduleInputs\"\n ></ng-container>\n </div>\n } @else {\n <!-- Fields for active tab (Responsive 12-column grid) -->\n <div class=\"ngx-form__panel\" data-testid=\"form-panel\">\n @for (field of fieldsForActiveTab; track field.id) {\n <div\n class=\"ngx-form__field-slot\"\n [style.gridColumn]=\"getFieldSpan(field)\"\n [attr.id]=\"'field-container-' + field.id\"\n tabindex=\"-1\"\n >\n <ngx-dynamic-field\n [field]=\"field\"\n [control]=\"getControl(field.id, (activeSubTabConfig || activeTabConfig)?.id)!\"\n [config]=\"config\"\n [currentTabId]=\"(activeSubTabConfig || activeTabConfig)?.id\"\n [language]=\"language\"\n [readonly]=\"isFieldReadonly(field)\"\n [userRoles]=\"userRoles\"\n />\n @if (field.hasDrift) {\n <!--\n Drift was previously visible only in the builder, so a referenced field whose\n source had changed looked completely normal to the person filling the form in.\n role=\"status\" rather than \"alert\": it is information, not an error.\n -->\n <span\n class=\"ngx-field__drift\"\n role=\"status\"\n [attr.data-testid]=\"'field-' + field.id + '-drift'\"\n [title]=\"driftHint(field)\"\n >\u26A0 {{ driftHint(field) }}</span\n >\n }\n @if (field.criticalField && !readonly) {\n <button\n type=\"button\"\n class=\"ngx-form__lock\"\n [attr.data-testid]=\"'lock-' + field.id\"\n [attr.aria-pressed]=\"!isFieldLocked(field)\"\n [attr.aria-label]=\"\n ui.text(isFieldLocked(field) ? 'unlockField' : 'lockField', language, {\n field: resolveFieldLabel(field),\n })\n \"\n [title]=\"ui.text(isFieldLocked(field) ? 'criticalFieldUnlockHint' : 'criticalFieldLockHint', language)\"\n (click)=\"toggleFieldLock(field)\"\n >\n {{ isFieldLocked(field) ? '\uD83D\uDD12' : '\uD83D\uDD13' }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Submit button \u2014 gated by RBAC and isSaving lock -->\n @if (!readonly && permissions.canEdit) {\n <div class=\"ngx-form__actions\" data-testid=\"form-actions\">\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"form-reset\" (click)=\"reset()\" [disabled]=\"isSaving()\">\n {{ ui.text('reset', language) }}\n </button>\n <!--\n Two different \"unavailable\" states, and they are not the same thing.\n\n `disabled` is for what a retry cannot fix \u2014 a save in flight, an async check still\n outstanding. `data-blocked` is for validation: the button stays fully operable,\n because pressing it is how the user gets the error summary, but it should not look\n ready either. The stylesheet mutes it without taking the click away.\n\n Deliberately not `aria-disabled`. That announces a control as present-but-inoperable,\n and this one is operable and useful \u2014 pressing it raises a `role=\"alert\"` naming\n every field at fault, which is more than a disabled button could ever say. Tooling\n agrees: Playwright refuses to click through `aria-disabled`, and it would be right\n to, because the attribute would be claiming something untrue.\n -->\n <button\n type=\"submit\"\n class=\"ngx-form__submit\"\n data-testid=\"form-submit\"\n [disabled]=\"submitDisabled\"\n [attr.data-blocked]=\"submitBlocked ? '' : null\"\n [title]=\"ui.text('saveShortcutHint', language)\"\n >\n @if (isSaving() || loading) {\n {{ ui.text('saving', language) }}\n } @else {\n {{ ui.text('save', language) }}\n }\n </button>\n </div>\n }\n </form>\n} @else {\n <!-- permissions.view denied: render nothing of the record.\n Presentational only \u2014 the server must not send data this user may not see. -->\n <div class=\"ngx-form__denied\" role=\"status\" data-testid=\"form-access-denied\">\n {{ ui.text('accessDenied', language) }}\n </div>\n}\n", styles: [".ngx-form__panel{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:var(--ngx-gap, 18px);align-items:start}.ngx-form__field-slot{display:flex;align-items:flex-start;gap:6px;min-width:0}.ngx-form__field-slot ngx-dynamic-field{flex:1;min-width:0}.ngx-form__lock{flex:0 0 auto;margin-top:22px;background:none;border:1px solid transparent;border-radius:var(--ngx-radius-sm, 6px);cursor:pointer;font-size:14px;line-height:1;padding:4px 6px}.ngx-form__lock:hover{border-color:var(--ngx-color-border, #d1d5db);background:var(--ngx-color-surface-alt, #f9fafb)}@media(max-width:640px){.ngx-form__panel{grid-template-columns:minmax(0,1fr)}.ngx-form__field-slot{grid-column:1 / -1!important}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicFieldComponent, selector: "ngx-dynamic-field", inputs: ["field", "control", "config", "currentTabId", "language", "readonly", "userRoles"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7951
7845
|
}
|
|
7952
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
7846
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DynamicFormComponent, decorators: [{
|
|
7953
7847
|
type: Component,
|
|
7954
7848
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-dynamic-form', standalone: true, imports: [ReactiveFormsModule, DynamicFieldComponent, NgComponentOutlet], providers: [EntityRefSelectionService, FormStructureService], template: "@if (canView) {\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\" class=\"ngx-form\" [class.ngx-form--readonly]=\"readonly\" novalidate>\n <!-- Error message banner -->\n @if (error) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" data-testid=\"form-error\">{{ error }}</div>\n }\n\n <!--\n Why the last Save did nothing.\n Only after a refused submit \u2014 see `submitAttempted`. Each entry jumps to its field,\n switching tabs on the way, because the field is usually not the one on screen.\n -->\n @if (submitAttempted() && invalidFields().length > 0) {\n <div class=\"ngx-form__error-summary\" role=\"alert\" data-testid=\"error-summary\" tabindex=\"-1\">\n <!--\n `error-summary-heading`, not `-title`: the links below are `error-summary-<fieldId>`,\n and a field whose id happens to be `title` would then share a test id with this line.\n The `extensions` entity has exactly such a field.\n -->\n <p class=\"ngx-form__error-summary-title\" data-testid=\"error-summary-heading\">\n {{ ui.text('errorSummaryTitle', language, { count: invalidFields().length }) }}\n </p>\n <ul class=\"ngx-form__error-summary-list\">\n @for (entry of invalidFields(); track entry.field.id) {\n <li>\n <!--\n The field, and what is wrong with it \u2014 the same sentence the field renders\n under itself, so the summary is not a second opinion. Naming only the field\n left the user to visit each one to find out what it wanted.\n -->\n <button\n type=\"button\"\n class=\"ngx-form__error-summary-link\"\n [attr.data-testid]=\"'error-summary-' + entry.field.id\"\n (click)=\"jumpToField(entry.field.id)\"\n >\n <span class=\"ngx-form__error-summary-field\">{{ resolveFieldLabel(entry.field) }}</span>\n @if (entry.message) {\n <span class=\"ngx-form__error-summary-reason\">{{ entry.message }}</span>\n }\n </button>\n </li>\n }\n </ul>\n </div>\n }\n\n <!-- Rules Info Banners -->\n @for (infoKey of showInfoBanners ? Object.keys(ruleResult().infoBanners) : []; track infoKey) {\n <div class=\"ngx-form__info-banner\" role=\"status\" [attr.data-testid]=\"'info-banner-' + infoKey\">\n \u2139\uFE0F {{ ruleResult().infoBanners[infoKey] }}\n </div>\n }\n\n <!-- Rules Validation Errors & Warnings -->\n @for (errKey of Object.keys(ruleResult().validationErrors); track errKey) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" [attr.data-testid]=\"'rule-error-' + errKey\">\n \u26A0\uFE0F {{ ruleResult().validationErrors[errKey] }}\n </div>\n }\n @for (warnKey of Object.keys(ruleResult().validationWarnings); track warnKey) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" [attr.data-testid]=\"'rule-warning-' + warnKey\">\n \u26A1 {{ ruleResult().validationWarnings[warnKey] }}\n </div>\n }\n\n <!-- Deferred VALUE_CHANGED notice for criticalField edits (vs session baseline) -->\n @if (changedCriticalFields().length > 0) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" data-testid=\"critical-change-banner\">\n {{ ui.text('criticalFieldChanged', language, { fields: changedCriticalFieldLabels() }) }}\n </div>\n }\n\n <!-- Primary Tab bar \u2014 only if visible tabs defined -->\n @if (visibleTabs.length > 0) {\n <div class=\"ngx-form__tabs\" role=\"tablist\" data-testid=\"tab-strip\">\n @for (tab of visibleTabs; track tab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab\"\n [attr.data-testid]=\"'tab-' + tab.id\"\n [class.ngx-form__tab--active]=\"activeTab() === tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n >\n {{ resolveTabLabel(tab) }}\n <!--\n How many fields on this tab are stopping the save.\n The count is in the accessible name rather than the badge, because \"3\" read out\n after a tab name says nothing about what it counts.\n -->\n @if (tabErrorCount(tab); as errors) {\n <span\n class=\"ngx-form__tab-badge\"\n [attr.data-testid]=\"'tab-errors-' + tab.id\"\n [attr.aria-label]=\"ui.text('tabErrorCount', language, { count: errors })\"\n >{{ errors }}</span\n >\n }\n </button>\n }\n </div>\n }\n\n <!-- Sub-tab bar \u2014 only if active tab has sub-tabs -->\n @if (visibleSubTabs.length > 0) {\n <div\n class=\"ngx-form__tabs ngx-form__tabs--sub\"\n role=\"tablist\"\n data-testid=\"subtab-strip\"\n style=\"margin-top: -12px; margin-bottom: 12px; font-size: 13px\"\n >\n @for (subTab of visibleSubTabs; track subTab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab ngx-form__tab--sub\"\n [attr.data-testid]=\"'subtab-' + subTab.id\"\n [class.ngx-form__tab--active]=\"activeSubTabConfig?.id === subTab.id\"\n (click)=\"setActiveSubTab(subTab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"activeSubTabConfig?.id === subTab.id\"\n >\n {{ resolveTabLabel(subTab) }}\n @if (subTabErrorCount(subTab); as errors) {\n <span\n class=\"ngx-form__tab-badge\"\n [attr.data-testid]=\"'subtab-errors-' + subTab.id\"\n [attr.aria-label]=\"ui.text('tabErrorCount', language, { count: errors })\"\n >{{ errors }}</span\n >\n }\n </button>\n }\n </div>\n }\n\n <!--\n One tabpanel per tab, wrapping both branches. Focus moves here on a tab switch, and a\n module tab is still a tab \u2014 putting the panel semantics on only the fields branch left\n a module tab with nowhere for focus to land.\n -->\n <div role=\"tabpanel\" tabindex=\"-1\" #formPanel class=\"ngx-form__tabpanel\" [attr.aria-label]=\"activeTabLabel\">\n @if (activeTabModuleComponent) {\n <div class=\"ngx-form__module-panel\" data-testid=\"module-panel\">\n <ng-container\n *ngComponentOutlet=\"activeTabModuleComponent; inputs: (activeSubTabConfig || activeTabConfig)?.moduleInputs\"\n ></ng-container>\n </div>\n } @else {\n <!-- Fields for active tab (Responsive 12-column grid) -->\n <div class=\"ngx-form__panel\" data-testid=\"form-panel\">\n @for (field of fieldsForActiveTab; track field.id) {\n <div\n class=\"ngx-form__field-slot\"\n [style.gridColumn]=\"getFieldSpan(field)\"\n [attr.id]=\"'field-container-' + field.id\"\n tabindex=\"-1\"\n >\n <ngx-dynamic-field\n [field]=\"field\"\n [control]=\"getControl(field.id, (activeSubTabConfig || activeTabConfig)?.id)!\"\n [config]=\"config\"\n [currentTabId]=\"(activeSubTabConfig || activeTabConfig)?.id\"\n [language]=\"language\"\n [readonly]=\"isFieldReadonly(field)\"\n [userRoles]=\"userRoles\"\n />\n @if (field.hasDrift) {\n <!--\n Drift was previously visible only in the builder, so a referenced field whose\n source had changed looked completely normal to the person filling the form in.\n role=\"status\" rather than \"alert\": it is information, not an error.\n -->\n <span\n class=\"ngx-field__drift\"\n role=\"status\"\n [attr.data-testid]=\"'field-' + field.id + '-drift'\"\n [title]=\"driftHint(field)\"\n >\u26A0 {{ driftHint(field) }}</span\n >\n }\n @if (field.criticalField && !readonly) {\n <button\n type=\"button\"\n class=\"ngx-form__lock\"\n [attr.data-testid]=\"'lock-' + field.id\"\n [attr.aria-pressed]=\"!isFieldLocked(field)\"\n [attr.aria-label]=\"\n ui.text(isFieldLocked(field) ? 'unlockField' : 'lockField', language, {\n field: resolveFieldLabel(field),\n })\n \"\n [title]=\"ui.text(isFieldLocked(field) ? 'criticalFieldUnlockHint' : 'criticalFieldLockHint', language)\"\n (click)=\"toggleFieldLock(field)\"\n >\n {{ isFieldLocked(field) ? '\uD83D\uDD12' : '\uD83D\uDD13' }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Submit button \u2014 gated by RBAC and isSaving lock -->\n @if (!readonly && permissions.canEdit) {\n <div class=\"ngx-form__actions\" data-testid=\"form-actions\">\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"form-reset\" (click)=\"reset()\" [disabled]=\"isSaving()\">\n {{ ui.text('reset', language) }}\n </button>\n <!--\n Two different \"unavailable\" states, and they are not the same thing.\n\n `disabled` is for what a retry cannot fix \u2014 a save in flight, an async check still\n outstanding. `data-blocked` is for validation: the button stays fully operable,\n because pressing it is how the user gets the error summary, but it should not look\n ready either. The stylesheet mutes it without taking the click away.\n\n Deliberately not `aria-disabled`. That announces a control as present-but-inoperable,\n and this one is operable and useful \u2014 pressing it raises a `role=\"alert\"` naming\n every field at fault, which is more than a disabled button could ever say. Tooling\n agrees: Playwright refuses to click through `aria-disabled`, and it would be right\n to, because the attribute would be claiming something untrue.\n -->\n <button\n type=\"submit\"\n class=\"ngx-form__submit\"\n data-testid=\"form-submit\"\n [disabled]=\"submitDisabled\"\n [attr.data-blocked]=\"submitBlocked ? '' : null\"\n [title]=\"ui.text('saveShortcutHint', language)\"\n >\n @if (isSaving() || loading) {\n {{ ui.text('saving', language) }}\n } @else {\n {{ ui.text('save', language) }}\n }\n </button>\n </div>\n }\n </form>\n} @else {\n <!-- permissions.view denied: render nothing of the record.\n Presentational only \u2014 the server must not send data this user may not see. -->\n <div class=\"ngx-form__denied\" role=\"status\" data-testid=\"form-access-denied\">\n {{ ui.text('accessDenied', language) }}\n </div>\n}\n", styles: [".ngx-form__panel{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:var(--ngx-gap, 18px);align-items:start}.ngx-form__field-slot{display:flex;align-items:flex-start;gap:6px;min-width:0}.ngx-form__field-slot ngx-dynamic-field{flex:1;min-width:0}.ngx-form__lock{flex:0 0 auto;margin-top:22px;background:none;border:1px solid transparent;border-radius:var(--ngx-radius-sm, 6px);cursor:pointer;font-size:14px;line-height:1;padding:4px 6px}.ngx-form__lock:hover{border-color:var(--ngx-color-border, #d1d5db);background:var(--ngx-color-surface-alt, #f9fafb)}@media(max-width:640px){.ngx-form__panel{grid-template-columns:minmax(0,1fr)}.ngx-form__field-slot{grid-column:1 / -1!important}}\n"] }]
|
|
7955
7849
|
}], propDecorators: { config: [{
|
|
@@ -8367,10 +8261,8 @@ class DynamicRecordFormComponent {
|
|
|
8367
8261
|
/**
|
|
8368
8262
|
* What this record is called, as opposed to what kind of thing it is.
|
|
8369
8263
|
*
|
|
8370
|
-
*
|
|
8371
|
-
*
|
|
8372
|
-
* looking at, was the one thing it did not say. The entity keeps the subtitle; this takes
|
|
8373
|
-
* the heading.
|
|
8264
|
+
* A record header exists to answer *which* record am I looking at, so this takes the
|
|
8265
|
+
* heading and the entity name keeps the subtitle.
|
|
8374
8266
|
*
|
|
8375
8267
|
* The order is deliberate. `showOnMinimize` is the author saying "this is what identifies a
|
|
8376
8268
|
* record" — it is what the summary panel and the collapsed view already show — so it is
|
|
@@ -8552,11 +8444,6 @@ class DynamicRecordFormComponent {
|
|
|
8552
8444
|
formatFieldValue(field) {
|
|
8553
8445
|
return formatDisplayValue(field.type, field.options, this.fieldValue(field.id), this.language);
|
|
8554
8446
|
}
|
|
8555
|
-
/**
|
|
8556
|
-
* Finds which tab — and, where the field lives one level down, which sub-tab — owns a
|
|
8557
|
-
* field. The walk used to check top-level `fields` only, so every field in a sub-tab was
|
|
8558
|
-
* simply not found and the jump did nothing at all.
|
|
8559
|
-
*/
|
|
8560
8447
|
/**
|
|
8561
8448
|
* Switches to the tab holding `fieldId`, then scrolls it into view and moves focus to it.
|
|
8562
8449
|
*
|
|
@@ -8569,10 +8456,10 @@ class DynamicRecordFormComponent {
|
|
|
8569
8456
|
jumpToField(fieldId) {
|
|
8570
8457
|
this.dynamicFormComp?.jumpToField(fieldId);
|
|
8571
8458
|
}
|
|
8572
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
8573
|
-
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", layout: "layout", viewMode: "viewMode" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", sectionSave: "sectionSave", saveRejected: "saveRejected" }, viewQueries: [{ propertyName: "dynamicFormComp", first: true, predicate: DynamicFormComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"ngx-record-editor\">\n <!-- Record Header -->\n <div class=\"ngx-record-editor__header\">\n <div class=\"ngx-record-editor__title-group\">\n @if (profileImageUrl) {\n <img\n class=\"ngx-record-editor__avatar\"\n data-testid=\"header-profile-image\"\n [src]=\"profileImageUrl\"\n [alt]=\"recordHeading\"\n />\n } @else {\n <div class=\"ngx-record-editor__avatar\" data-testid=\"header-avatar-initial\">\n {{ avatarLetter }}\n </div>\n }\n <div>\n <!--\n The record's own name, with the entity underneath. These were the other way round \u2014\n both showing the entity \u2014 so the header never said which record was open.\n -->\n <h2 class=\"ngx-record-editor__title\" data-testid=\"record-title\">\n {{ recordHeading }}\n </h2>\n <span class=\"ngx-record-editor__subtitle\">{{ ui.text('entityLabel', language, { entity: recordTitle }) }}</span>\n </div>\n </div>\n\n <!-- Record-level status switch, outside section editing -->\n @if (headerToggleField; as toggleField) {\n <label class=\"ngx-record-editor__header-toggle\">\n <span>{{ formatFieldLabel(toggleField) }}</span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n data-testid=\"header-toggle\"\n [checked]=\"headerToggleValue\"\n [disabled]=\"recordReadOnly\"\n [attr.aria-checked]=\"headerToggleValue\"\n (change)=\"toggleHeaderStatus()\"\n />\n <span class=\"ngx-record-editor__header-toggle-text\">{{ ui.text(headerToggleValue ? 'yes' : 'no', language) }}</span>\n </label>\n }\n </div>\n\n <!-- Dismissible info banners (rules `info` action) -->\n @for (banner of infoBanners; track banner.fieldId) {\n <div\n class=\"ngx-record-editor__banner ngx-record-editor__banner--info\"\n role=\"status\"\n [attr.data-testid]=\"'info-banner-' + banner.fieldId\"\n >\n <span>\u2139\uFE0F {{ banner.message }}</span>\n <button\n type=\"button\"\n class=\"ngx-record-editor__banner-dismiss\"\n [attr.data-testid]=\"'dismiss-' + banner.fieldId\"\n [attr.aria-label]=\"ui.text('dismissBanner', language, { message: banner.message })\"\n (click)=\"dismissInfoBanner(banner.fieldId)\"\n >\n \u2715\n </button>\n </div>\n }\n\n <!-- Modification baseline banner -->\n @if (isModified()) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--warning\">\n {{ ui.text('recordModified', language) }}\n </div>\n }\n\n <!-- Summary Panel (showOnMinimize fields with interactive jump) -->\n @if (summaryFields().length > 0) {\n <div class=\"ngx-record-editor__summary-panel\" data-testid=\"summary-panel\">\n @for (sf of summaryFields(); track sf.id) {\n <div\n class=\"ngx-record-editor__summary-item\"\n [attr.data-testid]=\"'summary-' + sf.id\"\n (click)=\"jumpToField(sf.id)\"\n [title]=\"ui.text('jumpToField', language, { field: formatFieldLabel(sf) })\"\n >\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(sf) }}</span>\n <span class=\"ngx-record-editor__summary-val\">{{ formatFieldValue(sf) }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Section edit bar: one tab is edited at a time, and validated on its own -->\n @if (viewMode && !recordReadOnly) {\n <div class=\"ngx-record-editor__section-bar\">\n @if (isEditingActiveTab) {\n <span class=\"ngx-record-editor__section-hint\">{{ ui.text('editingSection', language) }}</span>\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-section\" (click)=\"cancelSection()\">\n {{ ui.text('cancel', language) }}\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-section\" (click)=\"saveSection()\">\n {{ ui.text('saveSection', language) }}\n </button>\n } @else {\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"edit-section\" (click)=\"editSection()\">\n {{ ui.text('editSection', language) }}\n </button>\n }\n </div>\n }\n\n @if (Object.keys(sectionErrors()).length > 0) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--error\" role=\"alert\" data-testid=\"section-errors\">\n @for (key of Object.keys(sectionErrors()); track key) {\n <div>\u26A0\uFE0F {{ sectionErrors()[key] }}</div>\n }\n </div>\n }\n\n <!-- Array rows: listed here, edited in the drawer below -->\n @for (arrayField of arrayFieldsForActiveTab; track arrayField.id) {\n <div class=\"ngx-record-editor__rows\" [attr.data-testid]=\"'rows-' + arrayField.id\">\n <div class=\"ngx-record-editor__rows-head\">\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(arrayField) }}</span>\n @if (!sectionReadOnly) {\n <button\n type=\"button\"\n class=\"ngx-form__reset\"\n [attr.data-testid]=\"'add-row-' + arrayField.id\"\n (click)=\"openAddRow(arrayField)\"\n >\n {{ ui.text('addRow', language) }}\n </button>\n }\n </div>\n\n @for (row of rowsOf(arrayField); track $index) {\n <div class=\"ngx-record-editor__row\" [attr.data-testid]=\"'row-' + arrayField.id + '-' + $index\">\n <span class=\"ngx-record-editor__row-text\">{{ rowSummary(arrayField, row) }}</span>\n @if (!sectionReadOnly) {\n <button\n type=\"button\"\n class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'edit-row-' + arrayField.id + '-' + $index\"\n (click)=\"openEditRow(arrayField, $index)\"\n >\n {{ ui.text('edit', language) }}\n </button>\n <button\n type=\"button\"\n class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'delete-row-' + arrayField.id + '-' + $index\"\n (click)=\"deleteRow(arrayField, $index)\"\n >\n {{ ui.text('delete', language) }}\n </button>\n }\n </div>\n } @empty {\n <p class=\"ngx-record-editor__row-empty\">{{ ui.text('noRows', language) }}</p>\n }\n </div>\n }\n\n <!--\n Row drawer. Deliberately outside the tab panel: building a row's FormGroup inside the\n tab view races the tab's own initialisation (the reference hit this with FormArray).\n -->\n @if (inlineRowField(); as rowField) {\n <div class=\"ngx-record-editor__drawer\" data-testid=\"row-drawer\" [formGroup]=\"inlineRowForm!\">\n <div class=\"ngx-record-editor__rows-head\">\n <strong>{{ rowDrawerTitle(rowField) }}</strong>\n </div>\n\n <div class=\"ngx-form__panel\">\n @for (child of rowField.children ?? []; track child.id) {\n <ngx-dynamic-field\n [field]=\"child\"\n [control]=\"inlineRowForm!.get(child.id)!\"\n [config]=\"config\"\n [language]=\"language\"\n [readonly]=\"false\"\n [userRoles]=\"userRoles\"\n [style.gridColumn]=\"'span 12'\"\n />\n }\n </div>\n\n <div class=\"ngx-record-editor__section-bar\">\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-row\" (click)=\"cancelRow()\">\n {{ ui.text('cancel', language) }}\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-row\" (click)=\"saveRow()\">\n {{ ui.text('saveRow', language) }}\n </button>\n </div>\n </div>\n }\n\n <!-- Main Dynamic Form -->\n <ngx-dynamic-form\n [config]=\"config\"\n [rules]=\"rules\"\n [initialData]=\"initialData\"\n [originalData]=\"originalBaseline()\"\n [userRoles]=\"userRoles\"\n [language]=\"language\"\n [readonly]=\"sectionReadOnly\"\n [readOnlyFields]=\"readOnlyFields\"\n [layout]=\"layout\"\n [showInfoBanners]=\"false\"\n [loading]=\"loading\"\n [error]=\"error\"\n (formChange)=\"onFormChange($event)\"\n (formSubmit)=\"onFormSubmit($event)\"\n (saveRejected)=\"saveRejected.emit($event)\"\n (formReset)=\"onFormReset()\"\n (activeTabChange)=\"onActiveTabChange($event)\"\n />\n</div>\n", styles: [".ngx-record-editor{display:flex;flex-direction:column;gap:var(--ngx-gap, 16px);background:var(--ngx-color-surface, #ffffff);border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius, 12px);padding:20px}.ngx-record-editor__header{display:flex;align-items:center;justify-content:space-between;gap:14px;padding-bottom:var(--ngx-gap-sm, 16px);border-bottom:1px solid var(--ngx-color-border, #f3f4f6)}.ngx-record-editor__title-group{display:flex;align-items:center;gap:12px;min-width:0}.ngx-record-editor__avatar{width:44px;height:44px;border-radius:50%;object-fit:cover;background:var(--ngx-color-accent-soft, #e0e7ff);color:var(--ngx-color-accent, #4f46e5);border:1px solid var(--ngx-color-border, transparent);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:17px;flex:none}.ngx-record-editor__title{font-size:19px;font-weight:700;color:var(--ngx-color-text, #111827);margin:0;letter-spacing:-.01em}.ngx-record-editor__subtitle{font-size:var(--ngx-hint-size, 13px);color:var(--ngx-color-muted, #6b7280)}.ngx-record-editor__header-toggle{display:flex;align-items:center;gap:8px;font-size:var(--ngx-label-size, 13px);font-weight:var(--ngx-label-weight, 600);color:var(--ngx-color-text, #374151)}.ngx-record-editor__header-toggle-text{font-weight:600}.ngx-record-editor__banner{padding:11px 14px;border-radius:var(--ngx-radius-sm, 8px);font-size:var(--ngx-font-size, 13px);border:1px solid transparent;border-left-width:3px}.ngx-record-editor__banner--info{background:var(--ngx-color-info-soft, #eff6ff);border-color:var(--ngx-color-border, #dbeafe);border-left-color:var(--ngx-color-info, #1d4ed8);color:var(--ngx-color-info, #1d4ed8);display:flex;align-items:center;justify-content:space-between;gap:12px}.ngx-record-editor__banner--error{background:var(--ngx-color-error-soft, #fef2f2);border-color:var(--ngx-color-border, #fecaca);border-left-color:var(--ngx-color-error, #b91c1c);color:var(--ngx-color-error, #b91c1c)}.ngx-record-editor__banner--warning{background:var(--ngx-color-warning-soft, #fffbeb);border-color:var(--ngx-color-border, #fef3c7);border-left-color:var(--ngx-color-warning, #b45309);color:var(--ngx-color-warning, #b45309)}.ngx-record-editor__banner-dismiss{background:none;border:none;cursor:pointer;color:inherit;font-size:14px;line-height:1;padding:2px 6px;border-radius:4px}.ngx-record-editor__banner-dismiss:hover{background:#0000000f}.ngx-record-editor__section-bar{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:8px}.ngx-record-editor__section-hint{margin-right:auto;font-size:var(--ngx-hint-size, 12px);color:var(--ngx-color-muted, #6b7280)}.ngx-record-editor__rows{border:1px solid var(--ngx-color-border, #f3f4f6);border-radius:var(--ngx-radius-sm, 8px);padding:12px 14px;display:flex;flex-direction:column;gap:6px}.ngx-record-editor__rows-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.ngx-record-editor__row{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface-alt, #f9fafb)}.ngx-record-editor__row-text{flex:1;font-size:var(--ngx-font-size, 13px);overflow-wrap:anywhere}.ngx-record-editor__row-btn{background:var(--ngx-color-surface, none);border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px);cursor:pointer;font:inherit;font-size:var(--ngx-hint-size, 12px);font-weight:var(--ngx-label-weight, 600);color:inherit;padding:4px 10px}.ngx-record-editor__row-btn:hover{border-color:var(--ngx-color-accent, #e5e7eb);color:var(--ngx-color-accent, inherit)}.ngx-record-editor__row-empty{font-size:var(--ngx-hint-size, 12px);color:var(--ngx-color-muted, #6b7280);margin:0}.ngx-record-editor__drawer{border:1px solid var(--ngx-color-border, #dbeafe);background:var(--ngx-color-surface-alt, #f8fafc);border-radius:var(--ngx-radius, 8px);padding:16px;display:flex;flex-direction:column;gap:12px}.ngx-record-editor__summary-panel{background:var(--ngx-color-surface-alt, #f9fafb);border:1px solid var(--ngx-color-border, #f3f4f6);border-radius:var(--ngx-radius, 8px);padding:12px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:4px var(--ngx-gap, 12px)}.ngx-record-editor__summary-item{display:flex;flex-direction:column;cursor:pointer;padding:6px 10px;border-radius:var(--ngx-radius-sm, 6px);min-width:0;transition:background .15s}.ngx-record-editor__summary-item:hover{background:var(--ngx-color-accent-soft, #eff6ff)}.ngx-record-editor__summary-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--ngx-color-muted, #6b7280);font-weight:600}.ngx-record-editor__summary-val{font-size:var(--ngx-font-size, 14px);font-weight:600;color:var(--ngx-color-text, #1f2937);overflow-wrap:anywhere}@media(prefers-reduced-motion:reduce){.ngx-record-editor__summary-item{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicFormComponent, selector: "ngx-dynamic-form", inputs: ["config", "rules", "initialData", "originalData", "userRoles", "language", "readonly", "readOnlyFields", "showInfoBanners", "loading", "error", "changeDebounceMs", "preview", "layout"], outputs: ["formSubmit", "formChange", "formReset", "activeTabChange", "saveRejected"] }, { kind: "component", type: DynamicFieldComponent, selector: "ngx-dynamic-field", inputs: ["field", "control", "config", "currentTabId", "language", "readonly", "userRoles"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8459
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DynamicRecordFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8460
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: DynamicRecordFormComponent, isStandalone: true, selector: "ngx-dynamic-record-form", inputs: { config: "config", rules: "rules", initialData: "initialData", userRoles: "userRoles", language: "language", readonly: "readonly", loading: "loading", error: "error", isReadOnly: "isReadOnly", readOnlyFields: "readOnlyFields", layout: "layout", viewMode: "viewMode" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", sectionSave: "sectionSave", saveRejected: "saveRejected" }, viewQueries: [{ propertyName: "dynamicFormComp", first: true, predicate: DynamicFormComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"ngx-record-editor\">\n <!-- Record Header -->\n <div class=\"ngx-record-editor__header\">\n <div class=\"ngx-record-editor__title-group\">\n @if (profileImageUrl) {\n <img\n class=\"ngx-record-editor__avatar\"\n data-testid=\"header-profile-image\"\n [src]=\"profileImageUrl\"\n [alt]=\"recordHeading\"\n />\n } @else {\n <div class=\"ngx-record-editor__avatar\" data-testid=\"header-avatar-initial\">\n {{ avatarLetter }}\n </div>\n }\n <div>\n <!--\n The record's own name, with the entity underneath. These were the other way round \u2014\n both showing the entity \u2014 so the header never said which record was open.\n -->\n <h2 class=\"ngx-record-editor__title\" data-testid=\"record-title\">\n {{ recordHeading }}\n </h2>\n <span class=\"ngx-record-editor__subtitle\">{{ ui.text('entityLabel', language, { entity: recordTitle }) }}</span>\n </div>\n </div>\n\n <!-- Record-level status switch, outside section editing -->\n @if (headerToggleField; as toggleField) {\n <label class=\"ngx-record-editor__header-toggle\">\n <span>{{ formatFieldLabel(toggleField) }}</span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n data-testid=\"header-toggle\"\n [checked]=\"headerToggleValue\"\n [disabled]=\"recordReadOnly\"\n [attr.aria-checked]=\"headerToggleValue\"\n (change)=\"toggleHeaderStatus()\"\n />\n <span class=\"ngx-record-editor__header-toggle-text\">{{ ui.text(headerToggleValue ? 'yes' : 'no', language) }}</span>\n </label>\n }\n </div>\n\n <!-- Dismissible info banners (rules `info` action) -->\n @for (banner of infoBanners; track banner.fieldId) {\n <div\n class=\"ngx-record-editor__banner ngx-record-editor__banner--info\"\n role=\"status\"\n [attr.data-testid]=\"'info-banner-' + banner.fieldId\"\n >\n <span>\u2139\uFE0F {{ banner.message }}</span>\n <button\n type=\"button\"\n class=\"ngx-record-editor__banner-dismiss\"\n [attr.data-testid]=\"'dismiss-' + banner.fieldId\"\n [attr.aria-label]=\"ui.text('dismissBanner', language, { message: banner.message })\"\n (click)=\"dismissInfoBanner(banner.fieldId)\"\n >\n \u2715\n </button>\n </div>\n }\n\n <!-- Modification baseline banner -->\n @if (isModified()) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--warning\">\n {{ ui.text('recordModified', language) }}\n </div>\n }\n\n <!-- Summary Panel (showOnMinimize fields with interactive jump) -->\n @if (summaryFields().length > 0) {\n <div class=\"ngx-record-editor__summary-panel\" data-testid=\"summary-panel\">\n @for (sf of summaryFields(); track sf.id) {\n <div\n class=\"ngx-record-editor__summary-item\"\n [attr.data-testid]=\"'summary-' + sf.id\"\n (click)=\"jumpToField(sf.id)\"\n [title]=\"ui.text('jumpToField', language, { field: formatFieldLabel(sf) })\"\n >\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(sf) }}</span>\n <span class=\"ngx-record-editor__summary-val\">{{ formatFieldValue(sf) }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Section edit bar: one tab is edited at a time, and validated on its own -->\n @if (viewMode && !recordReadOnly) {\n <div class=\"ngx-record-editor__section-bar\">\n @if (isEditingActiveTab) {\n <span class=\"ngx-record-editor__section-hint\">{{ ui.text('editingSection', language) }}</span>\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-section\" (click)=\"cancelSection()\">\n {{ ui.text('cancel', language) }}\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-section\" (click)=\"saveSection()\">\n {{ ui.text('saveSection', language) }}\n </button>\n } @else {\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"edit-section\" (click)=\"editSection()\">\n {{ ui.text('editSection', language) }}\n </button>\n }\n </div>\n }\n\n @if (Object.keys(sectionErrors()).length > 0) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--error\" role=\"alert\" data-testid=\"section-errors\">\n @for (key of Object.keys(sectionErrors()); track key) {\n <div>\u26A0\uFE0F {{ sectionErrors()[key] }}</div>\n }\n </div>\n }\n\n <!-- Array rows: listed here, edited in the drawer below -->\n @for (arrayField of arrayFieldsForActiveTab; track arrayField.id) {\n <div class=\"ngx-record-editor__rows\" [attr.data-testid]=\"'rows-' + arrayField.id\">\n <div class=\"ngx-record-editor__rows-head\">\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(arrayField) }}</span>\n @if (!sectionReadOnly) {\n <button\n type=\"button\"\n class=\"ngx-form__reset\"\n [attr.data-testid]=\"'add-row-' + arrayField.id\"\n (click)=\"openAddRow(arrayField)\"\n >\n {{ ui.text('addRow', language) }}\n </button>\n }\n </div>\n\n @for (row of rowsOf(arrayField); track $index) {\n <div class=\"ngx-record-editor__row\" [attr.data-testid]=\"'row-' + arrayField.id + '-' + $index\">\n <span class=\"ngx-record-editor__row-text\">{{ rowSummary(arrayField, row) }}</span>\n @if (!sectionReadOnly) {\n <button\n type=\"button\"\n class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'edit-row-' + arrayField.id + '-' + $index\"\n (click)=\"openEditRow(arrayField, $index)\"\n >\n {{ ui.text('edit', language) }}\n </button>\n <button\n type=\"button\"\n class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'delete-row-' + arrayField.id + '-' + $index\"\n (click)=\"deleteRow(arrayField, $index)\"\n >\n {{ ui.text('delete', language) }}\n </button>\n }\n </div>\n } @empty {\n <p class=\"ngx-record-editor__row-empty\">{{ ui.text('noRows', language) }}</p>\n }\n </div>\n }\n\n <!--\n Row drawer. Deliberately outside the tab panel: building a row's FormGroup inside the\n tab view races the tab's own initialisation (the reference hit this with FormArray).\n -->\n @if (inlineRowField(); as rowField) {\n <div class=\"ngx-record-editor__drawer\" data-testid=\"row-drawer\" [formGroup]=\"inlineRowForm!\">\n <div class=\"ngx-record-editor__rows-head\">\n <strong>{{ rowDrawerTitle(rowField) }}</strong>\n </div>\n\n <div class=\"ngx-form__panel\">\n @for (child of rowField.children ?? []; track child.id) {\n <ngx-dynamic-field\n [field]=\"child\"\n [control]=\"inlineRowForm!.get(child.id)!\"\n [config]=\"config\"\n [language]=\"language\"\n [readonly]=\"false\"\n [userRoles]=\"userRoles\"\n [style.gridColumn]=\"'span 12'\"\n />\n }\n </div>\n\n <div class=\"ngx-record-editor__section-bar\">\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-row\" (click)=\"cancelRow()\">\n {{ ui.text('cancel', language) }}\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-row\" (click)=\"saveRow()\">\n {{ ui.text('saveRow', language) }}\n </button>\n </div>\n </div>\n }\n\n <!-- Main Dynamic Form -->\n <ngx-dynamic-form\n [config]=\"config\"\n [rules]=\"rules\"\n [initialData]=\"initialData\"\n [originalData]=\"originalBaseline()\"\n [userRoles]=\"userRoles\"\n [language]=\"language\"\n [readonly]=\"sectionReadOnly\"\n [readOnlyFields]=\"readOnlyFields\"\n [layout]=\"layout\"\n [showInfoBanners]=\"false\"\n [loading]=\"loading\"\n [error]=\"error\"\n (formChange)=\"onFormChange($event)\"\n (formSubmit)=\"onFormSubmit($event)\"\n (saveRejected)=\"saveRejected.emit($event)\"\n (formReset)=\"onFormReset()\"\n (activeTabChange)=\"onActiveTabChange($event)\"\n />\n</div>\n", styles: [".ngx-record-editor{display:flex;flex-direction:column;gap:var(--ngx-gap, 16px);background:var(--ngx-color-surface, #ffffff);border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius, 12px);padding:20px}.ngx-record-editor__header{display:flex;align-items:center;justify-content:space-between;gap:14px;padding-bottom:var(--ngx-gap-sm, 16px);border-bottom:1px solid var(--ngx-color-border, #f3f4f6)}.ngx-record-editor__title-group{display:flex;align-items:center;gap:12px;min-width:0}.ngx-record-editor__avatar{width:44px;height:44px;border-radius:50%;object-fit:cover;background:var(--ngx-color-accent-soft, #e0e7ff);color:var(--ngx-color-accent, #4f46e5);border:1px solid var(--ngx-color-border, transparent);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:17px;flex:none}.ngx-record-editor__title{font-size:19px;font-weight:700;color:var(--ngx-color-text, #111827);margin:0;letter-spacing:-.01em}.ngx-record-editor__subtitle{font-size:var(--ngx-hint-size, 13px);color:var(--ngx-color-muted, #6b7280)}.ngx-record-editor__header-toggle{display:flex;align-items:center;gap:8px;font-size:var(--ngx-label-size, 13px);font-weight:var(--ngx-label-weight, 600);color:var(--ngx-color-text, #374151)}.ngx-record-editor__header-toggle-text{font-weight:600}.ngx-record-editor__banner{padding:11px 14px;border-radius:var(--ngx-radius-sm, 8px);font-size:var(--ngx-font-size, 13px);border:1px solid transparent;border-left-width:3px}.ngx-record-editor__banner--info{background:var(--ngx-color-info-soft, #eff6ff);border-color:var(--ngx-color-border, #dbeafe);border-left-color:var(--ngx-color-info, #1d4ed8);color:var(--ngx-color-info, #1d4ed8);display:flex;align-items:center;justify-content:space-between;gap:12px}.ngx-record-editor__banner--error{background:var(--ngx-color-error-soft, #fef2f2);border-color:var(--ngx-color-border, #fecaca);border-left-color:var(--ngx-color-error, #b91c1c);color:var(--ngx-color-error, #b91c1c)}.ngx-record-editor__banner--warning{background:var(--ngx-color-warning-soft, #fffbeb);border-color:var(--ngx-color-border, #fef3c7);border-left-color:var(--ngx-color-warning, #b45309);color:var(--ngx-color-warning, #b45309)}.ngx-record-editor__banner-dismiss{background:none;border:none;cursor:pointer;color:inherit;font-size:14px;line-height:1;padding:2px 6px;border-radius:4px}.ngx-record-editor__banner-dismiss:hover{background:#0000000f}.ngx-record-editor__section-bar{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:8px}.ngx-record-editor__section-hint{margin-right:auto;font-size:var(--ngx-hint-size, 12px);color:var(--ngx-color-muted, #6b7280)}.ngx-record-editor__rows{border:1px solid var(--ngx-color-border, #f3f4f6);border-radius:var(--ngx-radius-sm, 8px);padding:12px 14px;display:flex;flex-direction:column;gap:6px}.ngx-record-editor__rows-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.ngx-record-editor__row{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface-alt, #f9fafb)}.ngx-record-editor__row-text{flex:1;font-size:var(--ngx-font-size, 13px);overflow-wrap:anywhere}.ngx-record-editor__row-btn{background:var(--ngx-color-surface, none);border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px);cursor:pointer;font:inherit;font-size:var(--ngx-hint-size, 12px);font-weight:var(--ngx-label-weight, 600);color:inherit;padding:4px 10px}.ngx-record-editor__row-btn:hover{border-color:var(--ngx-color-accent, #e5e7eb);color:var(--ngx-color-accent, inherit)}.ngx-record-editor__row-empty{font-size:var(--ngx-hint-size, 12px);color:var(--ngx-color-muted, #6b7280);margin:0}.ngx-record-editor__drawer{border:1px solid var(--ngx-color-border, #dbeafe);background:var(--ngx-color-surface-alt, #f8fafc);border-radius:var(--ngx-radius, 8px);padding:16px;display:flex;flex-direction:column;gap:12px}.ngx-record-editor__summary-panel{background:var(--ngx-color-surface-alt, #f9fafb);border:1px solid var(--ngx-color-border, #f3f4f6);border-radius:var(--ngx-radius, 8px);padding:12px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:4px var(--ngx-gap, 12px)}.ngx-record-editor__summary-item{display:flex;flex-direction:column;cursor:pointer;padding:6px 10px;border-radius:var(--ngx-radius-sm, 6px);min-width:0;transition:background .15s}.ngx-record-editor__summary-item:hover{background:var(--ngx-color-accent-soft, #eff6ff)}.ngx-record-editor__summary-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--ngx-color-muted, #6b7280);font-weight:600}.ngx-record-editor__summary-val{font-size:var(--ngx-font-size, 14px);font-weight:600;color:var(--ngx-color-text, #1f2937);overflow-wrap:anywhere}@media(prefers-reduced-motion:reduce){.ngx-record-editor__summary-item{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicFormComponent, selector: "ngx-dynamic-form", inputs: ["config", "rules", "initialData", "originalData", "userRoles", "language", "readonly", "readOnlyFields", "showInfoBanners", "loading", "error", "changeDebounceMs", "preview", "layout"], outputs: ["formSubmit", "formChange", "formReset", "activeTabChange", "saveRejected"] }, { kind: "component", type: DynamicFieldComponent, selector: "ngx-dynamic-field", inputs: ["field", "control", "config", "currentTabId", "language", "readonly", "userRoles"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8574
8461
|
}
|
|
8575
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
8462
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: DynamicRecordFormComponent, decorators: [{
|
|
8576
8463
|
type: Component,
|
|
8577
8464
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-dynamic-record-form', standalone: true, imports: [CommonModule, ReactiveFormsModule, DynamicFormComponent, DynamicFieldComponent], template: "<div class=\"ngx-record-editor\">\n <!-- Record Header -->\n <div class=\"ngx-record-editor__header\">\n <div class=\"ngx-record-editor__title-group\">\n @if (profileImageUrl) {\n <img\n class=\"ngx-record-editor__avatar\"\n data-testid=\"header-profile-image\"\n [src]=\"profileImageUrl\"\n [alt]=\"recordHeading\"\n />\n } @else {\n <div class=\"ngx-record-editor__avatar\" data-testid=\"header-avatar-initial\">\n {{ avatarLetter }}\n </div>\n }\n <div>\n <!--\n The record's own name, with the entity underneath. These were the other way round \u2014\n both showing the entity \u2014 so the header never said which record was open.\n -->\n <h2 class=\"ngx-record-editor__title\" data-testid=\"record-title\">\n {{ recordHeading }}\n </h2>\n <span class=\"ngx-record-editor__subtitle\">{{ ui.text('entityLabel', language, { entity: recordTitle }) }}</span>\n </div>\n </div>\n\n <!-- Record-level status switch, outside section editing -->\n @if (headerToggleField; as toggleField) {\n <label class=\"ngx-record-editor__header-toggle\">\n <span>{{ formatFieldLabel(toggleField) }}</span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n data-testid=\"header-toggle\"\n [checked]=\"headerToggleValue\"\n [disabled]=\"recordReadOnly\"\n [attr.aria-checked]=\"headerToggleValue\"\n (change)=\"toggleHeaderStatus()\"\n />\n <span class=\"ngx-record-editor__header-toggle-text\">{{ ui.text(headerToggleValue ? 'yes' : 'no', language) }}</span>\n </label>\n }\n </div>\n\n <!-- Dismissible info banners (rules `info` action) -->\n @for (banner of infoBanners; track banner.fieldId) {\n <div\n class=\"ngx-record-editor__banner ngx-record-editor__banner--info\"\n role=\"status\"\n [attr.data-testid]=\"'info-banner-' + banner.fieldId\"\n >\n <span>\u2139\uFE0F {{ banner.message }}</span>\n <button\n type=\"button\"\n class=\"ngx-record-editor__banner-dismiss\"\n [attr.data-testid]=\"'dismiss-' + banner.fieldId\"\n [attr.aria-label]=\"ui.text('dismissBanner', language, { message: banner.message })\"\n (click)=\"dismissInfoBanner(banner.fieldId)\"\n >\n \u2715\n </button>\n </div>\n }\n\n <!-- Modification baseline banner -->\n @if (isModified()) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--warning\">\n {{ ui.text('recordModified', language) }}\n </div>\n }\n\n <!-- Summary Panel (showOnMinimize fields with interactive jump) -->\n @if (summaryFields().length > 0) {\n <div class=\"ngx-record-editor__summary-panel\" data-testid=\"summary-panel\">\n @for (sf of summaryFields(); track sf.id) {\n <div\n class=\"ngx-record-editor__summary-item\"\n [attr.data-testid]=\"'summary-' + sf.id\"\n (click)=\"jumpToField(sf.id)\"\n [title]=\"ui.text('jumpToField', language, { field: formatFieldLabel(sf) })\"\n >\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(sf) }}</span>\n <span class=\"ngx-record-editor__summary-val\">{{ formatFieldValue(sf) }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Section edit bar: one tab is edited at a time, and validated on its own -->\n @if (viewMode && !recordReadOnly) {\n <div class=\"ngx-record-editor__section-bar\">\n @if (isEditingActiveTab) {\n <span class=\"ngx-record-editor__section-hint\">{{ ui.text('editingSection', language) }}</span>\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-section\" (click)=\"cancelSection()\">\n {{ ui.text('cancel', language) }}\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-section\" (click)=\"saveSection()\">\n {{ ui.text('saveSection', language) }}\n </button>\n } @else {\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"edit-section\" (click)=\"editSection()\">\n {{ ui.text('editSection', language) }}\n </button>\n }\n </div>\n }\n\n @if (Object.keys(sectionErrors()).length > 0) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--error\" role=\"alert\" data-testid=\"section-errors\">\n @for (key of Object.keys(sectionErrors()); track key) {\n <div>\u26A0\uFE0F {{ sectionErrors()[key] }}</div>\n }\n </div>\n }\n\n <!-- Array rows: listed here, edited in the drawer below -->\n @for (arrayField of arrayFieldsForActiveTab; track arrayField.id) {\n <div class=\"ngx-record-editor__rows\" [attr.data-testid]=\"'rows-' + arrayField.id\">\n <div class=\"ngx-record-editor__rows-head\">\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(arrayField) }}</span>\n @if (!sectionReadOnly) {\n <button\n type=\"button\"\n class=\"ngx-form__reset\"\n [attr.data-testid]=\"'add-row-' + arrayField.id\"\n (click)=\"openAddRow(arrayField)\"\n >\n {{ ui.text('addRow', language) }}\n </button>\n }\n </div>\n\n @for (row of rowsOf(arrayField); track $index) {\n <div class=\"ngx-record-editor__row\" [attr.data-testid]=\"'row-' + arrayField.id + '-' + $index\">\n <span class=\"ngx-record-editor__row-text\">{{ rowSummary(arrayField, row) }}</span>\n @if (!sectionReadOnly) {\n <button\n type=\"button\"\n class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'edit-row-' + arrayField.id + '-' + $index\"\n (click)=\"openEditRow(arrayField, $index)\"\n >\n {{ ui.text('edit', language) }}\n </button>\n <button\n type=\"button\"\n class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'delete-row-' + arrayField.id + '-' + $index\"\n (click)=\"deleteRow(arrayField, $index)\"\n >\n {{ ui.text('delete', language) }}\n </button>\n }\n </div>\n } @empty {\n <p class=\"ngx-record-editor__row-empty\">{{ ui.text('noRows', language) }}</p>\n }\n </div>\n }\n\n <!--\n Row drawer. Deliberately outside the tab panel: building a row's FormGroup inside the\n tab view races the tab's own initialisation (the reference hit this with FormArray).\n -->\n @if (inlineRowField(); as rowField) {\n <div class=\"ngx-record-editor__drawer\" data-testid=\"row-drawer\" [formGroup]=\"inlineRowForm!\">\n <div class=\"ngx-record-editor__rows-head\">\n <strong>{{ rowDrawerTitle(rowField) }}</strong>\n </div>\n\n <div class=\"ngx-form__panel\">\n @for (child of rowField.children ?? []; track child.id) {\n <ngx-dynamic-field\n [field]=\"child\"\n [control]=\"inlineRowForm!.get(child.id)!\"\n [config]=\"config\"\n [language]=\"language\"\n [readonly]=\"false\"\n [userRoles]=\"userRoles\"\n [style.gridColumn]=\"'span 12'\"\n />\n }\n </div>\n\n <div class=\"ngx-record-editor__section-bar\">\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-row\" (click)=\"cancelRow()\">\n {{ ui.text('cancel', language) }}\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-row\" (click)=\"saveRow()\">\n {{ ui.text('saveRow', language) }}\n </button>\n </div>\n </div>\n }\n\n <!-- Main Dynamic Form -->\n <ngx-dynamic-form\n [config]=\"config\"\n [rules]=\"rules\"\n [initialData]=\"initialData\"\n [originalData]=\"originalBaseline()\"\n [userRoles]=\"userRoles\"\n [language]=\"language\"\n [readonly]=\"sectionReadOnly\"\n [readOnlyFields]=\"readOnlyFields\"\n [layout]=\"layout\"\n [showInfoBanners]=\"false\"\n [loading]=\"loading\"\n [error]=\"error\"\n (formChange)=\"onFormChange($event)\"\n (formSubmit)=\"onFormSubmit($event)\"\n (saveRejected)=\"saveRejected.emit($event)\"\n (formReset)=\"onFormReset()\"\n (activeTabChange)=\"onActiveTabChange($event)\"\n />\n</div>\n", styles: [".ngx-record-editor{display:flex;flex-direction:column;gap:var(--ngx-gap, 16px);background:var(--ngx-color-surface, #ffffff);border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius, 12px);padding:20px}.ngx-record-editor__header{display:flex;align-items:center;justify-content:space-between;gap:14px;padding-bottom:var(--ngx-gap-sm, 16px);border-bottom:1px solid var(--ngx-color-border, #f3f4f6)}.ngx-record-editor__title-group{display:flex;align-items:center;gap:12px;min-width:0}.ngx-record-editor__avatar{width:44px;height:44px;border-radius:50%;object-fit:cover;background:var(--ngx-color-accent-soft, #e0e7ff);color:var(--ngx-color-accent, #4f46e5);border:1px solid var(--ngx-color-border, transparent);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:17px;flex:none}.ngx-record-editor__title{font-size:19px;font-weight:700;color:var(--ngx-color-text, #111827);margin:0;letter-spacing:-.01em}.ngx-record-editor__subtitle{font-size:var(--ngx-hint-size, 13px);color:var(--ngx-color-muted, #6b7280)}.ngx-record-editor__header-toggle{display:flex;align-items:center;gap:8px;font-size:var(--ngx-label-size, 13px);font-weight:var(--ngx-label-weight, 600);color:var(--ngx-color-text, #374151)}.ngx-record-editor__header-toggle-text{font-weight:600}.ngx-record-editor__banner{padding:11px 14px;border-radius:var(--ngx-radius-sm, 8px);font-size:var(--ngx-font-size, 13px);border:1px solid transparent;border-left-width:3px}.ngx-record-editor__banner--info{background:var(--ngx-color-info-soft, #eff6ff);border-color:var(--ngx-color-border, #dbeafe);border-left-color:var(--ngx-color-info, #1d4ed8);color:var(--ngx-color-info, #1d4ed8);display:flex;align-items:center;justify-content:space-between;gap:12px}.ngx-record-editor__banner--error{background:var(--ngx-color-error-soft, #fef2f2);border-color:var(--ngx-color-border, #fecaca);border-left-color:var(--ngx-color-error, #b91c1c);color:var(--ngx-color-error, #b91c1c)}.ngx-record-editor__banner--warning{background:var(--ngx-color-warning-soft, #fffbeb);border-color:var(--ngx-color-border, #fef3c7);border-left-color:var(--ngx-color-warning, #b45309);color:var(--ngx-color-warning, #b45309)}.ngx-record-editor__banner-dismiss{background:none;border:none;cursor:pointer;color:inherit;font-size:14px;line-height:1;padding:2px 6px;border-radius:4px}.ngx-record-editor__banner-dismiss:hover{background:#0000000f}.ngx-record-editor__section-bar{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:8px}.ngx-record-editor__section-hint{margin-right:auto;font-size:var(--ngx-hint-size, 12px);color:var(--ngx-color-muted, #6b7280)}.ngx-record-editor__rows{border:1px solid var(--ngx-color-border, #f3f4f6);border-radius:var(--ngx-radius-sm, 8px);padding:12px 14px;display:flex;flex-direction:column;gap:6px}.ngx-record-editor__rows-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.ngx-record-editor__row{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface-alt, #f9fafb)}.ngx-record-editor__row-text{flex:1;font-size:var(--ngx-font-size, 13px);overflow-wrap:anywhere}.ngx-record-editor__row-btn{background:var(--ngx-color-surface, none);border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px);cursor:pointer;font:inherit;font-size:var(--ngx-hint-size, 12px);font-weight:var(--ngx-label-weight, 600);color:inherit;padding:4px 10px}.ngx-record-editor__row-btn:hover{border-color:var(--ngx-color-accent, #e5e7eb);color:var(--ngx-color-accent, inherit)}.ngx-record-editor__row-empty{font-size:var(--ngx-hint-size, 12px);color:var(--ngx-color-muted, #6b7280);margin:0}.ngx-record-editor__drawer{border:1px solid var(--ngx-color-border, #dbeafe);background:var(--ngx-color-surface-alt, #f8fafc);border-radius:var(--ngx-radius, 8px);padding:16px;display:flex;flex-direction:column;gap:12px}.ngx-record-editor__summary-panel{background:var(--ngx-color-surface-alt, #f9fafb);border:1px solid var(--ngx-color-border, #f3f4f6);border-radius:var(--ngx-radius, 8px);padding:12px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:4px var(--ngx-gap, 12px)}.ngx-record-editor__summary-item{display:flex;flex-direction:column;cursor:pointer;padding:6px 10px;border-radius:var(--ngx-radius-sm, 6px);min-width:0;transition:background .15s}.ngx-record-editor__summary-item:hover{background:var(--ngx-color-accent-soft, #eff6ff)}.ngx-record-editor__summary-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--ngx-color-muted, #6b7280);font-weight:600}.ngx-record-editor__summary-val{font-size:var(--ngx-font-size, 14px);font-weight:600;color:var(--ngx-color-text, #1f2937);overflow-wrap:anywhere}@media(prefers-reduced-motion:reduce){.ngx-record-editor__summary-item{transition:none}}\n"] }]
|
|
8578
8465
|
}], propDecorators: { config: [{
|
|
@@ -8738,10 +8625,10 @@ class LocalImportTransport {
|
|
|
8738
8625
|
const csv = toCsv(spec.columns.map(column => column.header));
|
|
8739
8626
|
return new Blob([csv], { type: 'text/csv;charset=utf-8' });
|
|
8740
8627
|
}
|
|
8741
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
8742
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
8628
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LocalImportTransport, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
8629
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LocalImportTransport, providedIn: 'root' });
|
|
8743
8630
|
}
|
|
8744
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
8631
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: LocalImportTransport, decorators: [{
|
|
8745
8632
|
type: Injectable,
|
|
8746
8633
|
args: [{ providedIn: 'root' }]
|
|
8747
8634
|
}] });
|
|
@@ -8965,8 +8852,8 @@ class ImportErrorsComponent {
|
|
|
8965
8852
|
.sort((a, b) => a[0] - b[0])
|
|
8966
8853
|
.map(([row, problems]) => ({ row, problems }));
|
|
8967
8854
|
}
|
|
8968
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
8969
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
8855
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportErrorsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8856
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: ImportErrorsComponent, isStandalone: true, selector: "ngx-import-errors", inputs: { errors: "errors", language: "language" }, ngImport: i0, template: `
|
|
8970
8857
|
@if (errors.length) {
|
|
8971
8858
|
<section class="ngx-import-errors" data-testid="import-errors">
|
|
8972
8859
|
<h4 class="ngx-import-errors__heading">{{ ui.text('importErrorsHeading', language) }}</h4>
|
|
@@ -8998,7 +8885,7 @@ class ImportErrorsComponent {
|
|
|
8998
8885
|
}
|
|
8999
8886
|
`, isInline: true, styles: [".ngx-import-errors__heading{margin:0 0 8px;font-size:var(--ngx-font-size, 14px);color:var(--ngx-color-error, #b91c1c)}.ngx-import-errors__table{width:100%;border-collapse:collapse}.ngx-import-errors__table th,.ngx-import-errors__table td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--ngx-color-border, #e5e7eb);vertical-align:top}.ngx-import-errors__table thead th{color:var(--ngx-color-muted, #6b7280)}.ngx-import-errors__table tbody th{white-space:nowrap;color:var(--ngx-color-error, #b91c1c)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9000
8887
|
}
|
|
9001
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
8888
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportErrorsComponent, decorators: [{
|
|
9002
8889
|
type: Component,
|
|
9003
8890
|
args: [{ selector: 'ngx-import-errors', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [], template: `
|
|
9004
8891
|
@if (errors.length) {
|
|
@@ -9109,8 +8996,8 @@ class ImportMapperComponent {
|
|
|
9109
8996
|
entries,
|
|
9110
8997
|
};
|
|
9111
8998
|
}
|
|
9112
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
9113
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
8999
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportMapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9000
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: ImportMapperComponent, isStandalone: true, selector: "ngx-import-mapper", inputs: { config: "config", headers: "headers", plan: "plan", language: "language" }, outputs: { planChange: "planChange" }, usesOnChanges: true, ngImport: i0, template: `
|
|
9114
9001
|
<section class="ngx-import-mapper" data-testid="import-mapper">
|
|
9115
9002
|
<p class="ngx-import-mapper__summary" data-testid="import-mapped-count">
|
|
9116
9003
|
{{ ui.text('importMappedCount', language, { mapped: mappedCount(), total: rows().length }) }}
|
|
@@ -9168,7 +9055,7 @@ class ImportMapperComponent {
|
|
|
9168
9055
|
</section>
|
|
9169
9056
|
`, isInline: true, styles: [".ngx-import-mapper__summary,.ngx-import-mapper__warning,.ngx-import-mapper__note{margin:0 0 10px}.ngx-import-mapper__warning{color:var(--ngx-color-warning, #b45309);background:var(--ngx-color-warning-soft, #fffbeb);border:1px solid var(--ngx-color-border, #fef3c7);border-radius:var(--ngx-radius-sm, 6px);padding:8px 12px}.ngx-import-mapper__note{color:var(--ngx-color-muted, #6b7280)}.ngx-import-mapper__table{width:100%;border-collapse:collapse}.ngx-import-mapper__table th,.ngx-import-mapper__table td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--ngx-color-border, #e5e7eb);vertical-align:middle}.ngx-import-mapper__table thead th{color:var(--ngx-color-muted, #6b7280);font-size:var(--ngx-font-size, 13px);text-transform:uppercase;letter-spacing:.04em}.ngx-import-mapper__required{color:var(--ngx-color-error, #b91c1c);margin-left:2px}.ngx-import-mapper__select{min-width:220px;max-width:100%;padding:6px 8px;border:1px solid var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff);color:inherit;font:inherit}.ngx-import-mapper__guess{margin-left:8px;padding:2px 8px;border-radius:999px;font-size:12px;color:var(--ngx-color-info, #1d4ed8);background:var(--ngx-color-info-soft, #eff6ff)}@media(max-width:640px){.ngx-import-mapper__select{min-width:0;width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9170
9057
|
}
|
|
9171
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
9058
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportMapperComponent, decorators: [{
|
|
9172
9059
|
type: Component,
|
|
9173
9060
|
args: [{ selector: 'ngx-import-mapper', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [FormsModule], template: `
|
|
9174
9061
|
<section class="ngx-import-mapper" data-testid="import-mapper">
|
|
@@ -9310,8 +9197,8 @@ class ImportPreviewComponent {
|
|
|
9310
9197
|
}),
|
|
9311
9198
|
})));
|
|
9312
9199
|
}
|
|
9313
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
9314
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
9200
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportPreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9201
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: ImportPreviewComponent, isStandalone: true, selector: "ngx-import-preview", inputs: { config: "config", plan: "plan", rows: "rows", lookups: "lookups", rules: "rules", language: "language" }, usesOnChanges: true, ngImport: i0, template: `
|
|
9315
9202
|
<section class="ngx-import-preview" data-testid="import-preview">
|
|
9316
9203
|
<h4 class="ngx-import-preview__heading">
|
|
9317
9204
|
{{ ui.text('importPreviewHeading', language, { count: rows.length }) }}
|
|
@@ -9349,7 +9236,7 @@ class ImportPreviewComponent {
|
|
|
9349
9236
|
</section>
|
|
9350
9237
|
`, isInline: true, styles: [".ngx-import-preview__heading{margin:0 0 8px;font-size:var(--ngx-font-size, 14px)}.ngx-import-preview__scroll{overflow-x:auto;border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px)}.ngx-import-preview__table{width:100%;border-collapse:collapse;white-space:nowrap}.ngx-import-preview__table th,.ngx-import-preview__table td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--ngx-color-border, #f3f4f6)}.ngx-import-preview__table thead th{background:var(--ngx-color-surface-alt, #f9fafb);color:var(--ngx-color-muted, #6b7280)}.ngx-import-preview__cell--error{color:var(--ngx-color-error, #b91c1c);background:var(--ngx-color-error-soft, #fef2f2)}.ngx-import-preview__row--rejected td{opacity:.65}.ngx-import-preview__reason td{white-space:normal;color:var(--ngx-color-error, #b91c1c);background:var(--ngx-color-error-soft, #fef2f2)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9351
9238
|
}
|
|
9352
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
9239
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportPreviewComponent, decorators: [{
|
|
9353
9240
|
type: Component,
|
|
9354
9241
|
args: [{ selector: 'ngx-import-preview', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [], template: `
|
|
9355
9242
|
<section class="ngx-import-preview" data-testid="import-preview">
|
|
@@ -9458,8 +9345,8 @@ class ImportTemplateComponent {
|
|
|
9458
9345
|
selectAll(selected) {
|
|
9459
9346
|
this.choices.update(choices => choices.map(choice => ({ ...choice, selected })));
|
|
9460
9347
|
}
|
|
9461
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
9462
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
9348
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportTemplateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9349
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: ImportTemplateComponent, isStandalone: true, selector: "ngx-import-template", inputs: { config: "config", language: "language" }, outputs: { download: "download" }, usesOnChanges: true, ngImport: i0, template: `
|
|
9463
9350
|
<section class="ngx-import-template" data-testid="import-template">
|
|
9464
9351
|
<h4 class="ngx-import-template__heading">{{ ui.text('importTemplateHeading', language) }}</h4>
|
|
9465
9352
|
<p class="ngx-import-template__explain">{{ ui.text('importTemplateExplain', language) }}</p>
|
|
@@ -9510,7 +9397,7 @@ class ImportTemplateComponent {
|
|
|
9510
9397
|
</section>
|
|
9511
9398
|
`, isInline: true, styles: [".ngx-import-template{padding:14px;border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface-alt, #f9fafb)}.ngx-import-template__heading{margin:0 0 4px;font-size:var(--ngx-font-size, 14px)}.ngx-import-template__explain{margin:0 0 10px;color:var(--ngx-color-muted, #6b7280)}.ngx-import-template__bulk{display:flex;gap:8px;margin-bottom:10px}.ngx-import-template__list{list-style:none;margin:0 0 10px;padding:0;max-height:240px;overflow-y:auto;display:grid;gap:4px}.ngx-import-template__item{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px}.ngx-import-template__item label{display:flex;align-items:center;gap:6px;cursor:pointer}.ngx-import-template__note{color:var(--ngx-color-muted, #6b7280);font-size:12px}.ngx-import-template__unsupported{margin:0 0 10px;color:var(--ngx-color-muted, #6b7280)}.ngx-import-template button{padding:6px 12px;border:1px solid var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff);color:inherit;font:inherit;cursor:pointer}.ngx-import-template button:disabled{opacity:.55;cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9512
9399
|
}
|
|
9513
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
9400
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: ImportTemplateComponent, decorators: [{
|
|
9514
9401
|
type: Component,
|
|
9515
9402
|
args: [{ selector: 'ngx-import-template', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [], template: `
|
|
9516
9403
|
<section class="ngx-import-template" data-testid="import-template">
|
|
@@ -9796,8 +9683,8 @@ class EntityImportComponent {
|
|
|
9796
9683
|
this.problem.set(null);
|
|
9797
9684
|
this.step.set('upload');
|
|
9798
9685
|
}
|
|
9799
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
9800
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
9686
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityImportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9687
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.24", type: EntityImportComponent, isStandalone: true, selector: "ngx-entity-import", inputs: { config: "config", rules: "rules", lookups: "lookups", language: "language", templateFormat: "templateFormat" }, outputs: { importComplete: "importComplete", templateReady: "templateReady" }, ngImport: i0, template: `
|
|
9801
9688
|
<div class="ngx-import" data-testid="entity-import">
|
|
9802
9689
|
<ol class="ngx-import__steps" data-testid="import-steps">
|
|
9803
9690
|
@for (name of steps; track name) {
|
|
@@ -9915,7 +9802,7 @@ class EntityImportComponent {
|
|
|
9915
9802
|
</div>
|
|
9916
9803
|
`, isInline: true, styles: [".ngx-import{display:flex;flex-direction:column;gap:var(--ngx-gap, 16px);color:var(--ngx-color-text, #1f2937);font-size:var(--ngx-font-size, 14px)}.ngx-import__steps{display:flex;flex-wrap:wrap;gap:8px;list-style:none;margin:0;padding:0}.ngx-import__step{padding:4px 12px;border:1px solid var(--ngx-color-border, #e5e7eb);border-radius:var(--ngx-radius-sm, 6px);color:var(--ngx-color-muted, #6b7280);background:var(--ngx-color-surface-alt, #f9fafb)}.ngx-import__step--current{color:var(--ngx-color-accent, #4f46e5);border-color:var(--ngx-color-accent, #4f46e5);background:var(--ngx-color-accent-soft, #e0e7ff);font-weight:600}.ngx-import__problem{margin:0;padding:10px 12px;border:1px solid var(--ngx-color-border, #fecaca);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-error-soft, #fef2f2);color:var(--ngx-color-error, #b91c1c);overflow-wrap:anywhere}.ngx-import__file{display:flex;flex-direction:column;gap:6px;padding:14px;border:1px dashed var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff)}.ngx-import button{align-self:flex-start;padding:8px 16px;border:1px solid var(--ngx-color-border, #d1d5db);border-radius:var(--ngx-radius-sm, 6px);background:var(--ngx-color-surface, #ffffff);color:inherit;font:inherit;cursor:pointer}.ngx-import button:hover:not(:disabled){border-color:var(--ngx-color-accent, #4f46e5);background:var(--ngx-color-accent-soft, #e0e7ff)}.ngx-import button:disabled{opacity:.55;cursor:not-allowed}\n"], dependencies: [{ kind: "component", type: ImportTemplateComponent, selector: "ngx-import-template", inputs: ["config", "language"], outputs: ["download"] }, { kind: "component", type: ImportMapperComponent, selector: "ngx-import-mapper", inputs: ["config", "headers", "plan", "language"], outputs: ["planChange"] }, { kind: "component", type: ImportPreviewComponent, selector: "ngx-import-preview", inputs: ["config", "plan", "rows", "lookups", "rules", "language"] }, { kind: "component", type: ImportErrorsComponent, selector: "ngx-import-errors", inputs: ["errors", "language"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9917
9804
|
}
|
|
9918
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
9805
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.24", ngImport: i0, type: EntityImportComponent, decorators: [{
|
|
9919
9806
|
type: Component,
|
|
9920
9807
|
args: [{ selector: 'ngx-entity-import', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
9921
9808
|
ImportTemplateComponent,
|