ngx-dynamic-entity 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -139
- package/esm2022/lib/field-types/array-field.component.mjs +101 -101
- package/esm2022/lib/field-types/boolean-field.component.mjs +59 -59
- package/esm2022/lib/field-types/checkbox-field.component.mjs +45 -45
- package/esm2022/lib/field-types/currency-field.component.mjs +51 -51
- package/esm2022/lib/field-types/date-field.component.mjs +43 -43
- package/esm2022/lib/field-types/dropdown-field.component.mjs +81 -81
- package/esm2022/lib/field-types/email-field.component.mjs +61 -61
- package/esm2022/lib/field-types/entity-ref-field.component.mjs +67 -67
- package/esm2022/lib/field-types/file-field.component.mjs +85 -85
- package/esm2022/lib/field-types/group-field.component.mjs +47 -47
- package/esm2022/lib/field-types/image-field.component.mjs +89 -89
- package/esm2022/lib/field-types/month-year-field.component.mjs +77 -77
- package/esm2022/lib/field-types/multi-select-field.component.mjs +63 -63
- package/esm2022/lib/field-types/number-field.component.mjs +65 -65
- package/esm2022/lib/field-types/password-field.component.mjs +65 -65
- package/esm2022/lib/field-types/radio-field.component.mjs +63 -63
- package/esm2022/lib/field-types/text-field.component.mjs +59 -59
- package/esm2022/lib/field-types/textarea-field.component.mjs +45 -45
- package/esm2022/lib/form/dynamic-field/dynamic-field.component.mjs +1 -1
- package/esm2022/lib/form/dynamic-form.component.mjs +183 -10
- package/esm2022/lib/form/dynamic-record-form.component.mjs +36 -11
- package/esm2022/lib/providers/provide-field-types.mjs +1 -1
- package/esm2022/lib/providers/provide-ngx-dynamic-entity.mjs +3 -2
- package/esm2022/lib/services/cascade-data.service.mjs +1 -1
- package/esm2022/lib/services/entity-ref-cache.mjs +1 -1
- package/esm2022/lib/services/entity-ref-registry.service.mjs +1 -1
- package/esm2022/lib/services/entity-ref-selection.service.mjs +1 -1
- package/esm2022/lib/services/entity-reference.service.mjs +1 -1
- package/esm2022/lib/services/field-registry.service.mjs +1 -1
- package/esm2022/lib/services/file-upload.service.mjs +1 -1
- package/esm2022/lib/services/rbac.service.mjs +1 -1
- package/esm2022/lib/services/validator-registry.service.mjs +12 -1
- package/esm2022/lib/tokens/injection-tokens.mjs +17 -1
- package/esm2022/public-api.mjs +18 -2
- package/fesm2022/ngx-dynamic-entity.mjs +1392 -1166
- package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
- package/lib/field-types/dropdown-field.component.d.ts +1 -1
- package/lib/field-types/entity-ref-field.component.d.ts +1 -1
- package/lib/field-types/multi-select-field.component.d.ts +1 -1
- package/lib/field-types/radio-field.component.d.ts +1 -1
- package/lib/form/dynamic-form.component.d.ts +93 -6
- package/lib/form/dynamic-record-form.component.d.ts +13 -2
- package/lib/providers/provide-ngx-dynamic-entity.d.ts +8 -2
- package/lib/tokens/injection-tokens.d.ts +24 -2
- package/package.json +17 -18
- package/public-api.d.ts +10 -0
|
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { InjectionToken, makeEnvironmentProviders, Input, Component, signal, inject, Injectable, DestroyRef, isDevMode, ViewContainerRef, ViewChild, forwardRef, Optional, Inject, EventEmitter, computed, HostListener, Output } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { ReactiveFormsModule, Validators, FormBuilder, FormArray, FormGroup } from '@angular/forms';
|
|
5
|
-
import { resolveLabel, normalizeLookupValues, lookupValuesToOptions, valuesMatch, getOptionStoredValue, resolveOptionLabel, resolveOptionValue, buildReferenceCacheKey, normalizeReferenceOptions, entityKeyFromCacheKey, applyCascadeFilter, resolveEffectiveMask, shouldMaskField, findTab, fileRefName, evaluateFormRules, filterRulesForTab, evaluateFieldVisibility, normalizeConfigOptions, getTabData, getValueByPath, setTabData, setValueByPath, normalizeArrayStructures, getTabPath, applyAutoPatch, applyPatchOnTrue, formatDisplayValue } from '@dynamic-entity/core';
|
|
5
|
+
import { resolveLabel, normalizeLookupValues, lookupValuesToOptions, valuesMatch, getOptionStoredValue, resolveOptionLabel, resolveOptionValue, buildReferenceCacheKey, normalizeReferenceOptions, entityKeyFromCacheKey, applyCascadeFilter, resolveEffectiveMask, shouldMaskField, findTab, fileRefName, evaluateFormRules, filterRulesForTab, evaluateFieldVisibility, normalizeConfigOptions, migrateRecord, getTabData, getValueByPath, setTabData, setValueByPath, normalizeArrayStructures, getTabPath, applyAutoPatch, applyPatchOnTrue, formatDisplayValue } 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';
|
|
@@ -70,6 +70,22 @@ const ENTITY_REF_REGISTRY = new InjectionToken('ENTITY_REF_REGISTRY');
|
|
|
70
70
|
* })
|
|
71
71
|
*/
|
|
72
72
|
const LOOKUP_REGISTRY = new InjectionToken('LOOKUP_REGISTRY');
|
|
73
|
+
/**
|
|
74
|
+
* Ordered steps that move a saved record forward when a config's `version` increases.
|
|
75
|
+
*
|
|
76
|
+
* Without these, `EntityFormConfig.version` and `VersionedRecord._configVersion` are
|
|
77
|
+
* declarations nothing acts on: a schema can be edited freely while records keep their old
|
|
78
|
+
* shape, and nothing reconciles the two. Registering migrations makes the version numbers
|
|
79
|
+
* mean something.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* provideNgxDynamicEntity({
|
|
83
|
+
* migrations: [
|
|
84
|
+
* { from: 1, to: 2, description: 'split name', migrate: r => ({ ...r, firstName: r['name'] }) },
|
|
85
|
+
* ],
|
|
86
|
+
* })
|
|
87
|
+
*/
|
|
88
|
+
const RECORD_MIGRATIONS = new InjectionToken('RECORD_MIGRATIONS');
|
|
73
89
|
/** Registry: validator key → ValidatorFn */
|
|
74
90
|
const VALIDATOR_REGISTRY = new InjectionToken('VALIDATOR_REGISTRY');
|
|
75
91
|
/** Registry: hook key → async (data, context) => data */
|
|
@@ -141,6 +157,7 @@ const provideNgxDynamicEntity = (config = {}) => makeEnvironmentProviders([
|
|
|
141
157
|
{ provide: LOOKUP_REGISTRY, useValue: new Map(Object.entries(config.lookups ?? {})) },
|
|
142
158
|
{ provide: VALIDATOR_REGISTRY, useValue: new Map(Object.entries(config.validators ?? {})) },
|
|
143
159
|
{ provide: HOOK_REGISTRY, useValue: new Map(Object.entries(config.hooks ?? {})) },
|
|
160
|
+
{ provide: RECORD_MIGRATIONS, useValue: config.migrations ?? [] },
|
|
144
161
|
]);
|
|
145
162
|
|
|
146
163
|
class TextFieldComponent {
|
|
@@ -172,35 +189,35 @@ class TextFieldComponent {
|
|
|
172
189
|
return 'Invalid value.';
|
|
173
190
|
}
|
|
174
191
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TextFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
175
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: TextFieldComponent, isStandalone: true, selector: "ngx-text-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
176
|
-
<div
|
|
177
|
-
class="ngx-field ngx-field--text"
|
|
178
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
|
|
179
|
-
[class.ngx-field--readonly]="readonly"
|
|
180
|
-
[class.ngx-field--masked]="masked"
|
|
181
|
-
[class.ngx-field--invalid]="control && control.invalid && control.touched"
|
|
182
|
-
>
|
|
183
|
-
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}@if (field.validators?.required) { <span class="ngx-field__req">*</span> }</label>
|
|
184
|
-
@if (masked) {
|
|
185
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
186
|
-
} @else if (readonly) {
|
|
187
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ?? '—' }}</span>
|
|
188
|
-
} @else {
|
|
189
|
-
<input
|
|
190
|
-
[id]="field.id"
|
|
191
|
-
type="text"
|
|
192
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
193
|
-
[formControl]="$any(control)"
|
|
194
|
-
[placeholder]="placeholder"
|
|
195
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
196
|
-
[attr.aria-invalid]="control.invalid && control.touched"
|
|
197
|
-
[attr.aria-describedby]="errorMessage ? field.id + '-error' : null"
|
|
198
|
-
/>
|
|
199
|
-
@if (errorMessage) {
|
|
200
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'" [id]="field.id + '-error'" role="alert">{{ errorMessage }}</span>
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
</div>
|
|
192
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: TextFieldComponent, isStandalone: true, selector: "ngx-text-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
193
|
+
<div
|
|
194
|
+
class="ngx-field ngx-field--text"
|
|
195
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
|
|
196
|
+
[class.ngx-field--readonly]="readonly"
|
|
197
|
+
[class.ngx-field--masked]="masked"
|
|
198
|
+
[class.ngx-field--invalid]="control && control.invalid && control.touched"
|
|
199
|
+
>
|
|
200
|
+
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}@if (field.validators?.required) { <span class="ngx-field__req">*</span> }</label>
|
|
201
|
+
@if (masked) {
|
|
202
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
203
|
+
} @else if (readonly) {
|
|
204
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ?? '—' }}</span>
|
|
205
|
+
} @else {
|
|
206
|
+
<input
|
|
207
|
+
[id]="field.id"
|
|
208
|
+
type="text"
|
|
209
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
210
|
+
[formControl]="$any(control)"
|
|
211
|
+
[placeholder]="placeholder"
|
|
212
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
213
|
+
[attr.aria-invalid]="control.invalid && control.touched"
|
|
214
|
+
[attr.aria-describedby]="errorMessage ? field.id + '-error' : null"
|
|
215
|
+
/>
|
|
216
|
+
@if (errorMessage) {
|
|
217
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'" [id]="field.id + '-error'" role="alert">{{ errorMessage }}</span>
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
</div>
|
|
204
221
|
`, 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"] }] });
|
|
205
222
|
}
|
|
206
223
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TextFieldComponent, decorators: [{
|
|
@@ -209,35 +226,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
209
226
|
selector: 'ngx-text-field',
|
|
210
227
|
standalone: true,
|
|
211
228
|
imports: [ReactiveFormsModule],
|
|
212
|
-
template: `
|
|
213
|
-
<div
|
|
214
|
-
class="ngx-field ngx-field--text"
|
|
215
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
|
|
216
|
-
[class.ngx-field--readonly]="readonly"
|
|
217
|
-
[class.ngx-field--masked]="masked"
|
|
218
|
-
[class.ngx-field--invalid]="control && control.invalid && control.touched"
|
|
219
|
-
>
|
|
220
|
-
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}@if (field.validators?.required) { <span class="ngx-field__req">*</span> }</label>
|
|
221
|
-
@if (masked) {
|
|
222
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
223
|
-
} @else if (readonly) {
|
|
224
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ?? '—' }}</span>
|
|
225
|
-
} @else {
|
|
226
|
-
<input
|
|
227
|
-
[id]="field.id"
|
|
228
|
-
type="text"
|
|
229
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
230
|
-
[formControl]="$any(control)"
|
|
231
|
-
[placeholder]="placeholder"
|
|
232
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
233
|
-
[attr.aria-invalid]="control.invalid && control.touched"
|
|
234
|
-
[attr.aria-describedby]="errorMessage ? field.id + '-error' : null"
|
|
235
|
-
/>
|
|
236
|
-
@if (errorMessage) {
|
|
237
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'" [id]="field.id + '-error'" role="alert">{{ errorMessage }}</span>
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
</div>
|
|
229
|
+
template: `
|
|
230
|
+
<div
|
|
231
|
+
class="ngx-field ngx-field--text"
|
|
232
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
|
|
233
|
+
[class.ngx-field--readonly]="readonly"
|
|
234
|
+
[class.ngx-field--masked]="masked"
|
|
235
|
+
[class.ngx-field--invalid]="control && control.invalid && control.touched"
|
|
236
|
+
>
|
|
237
|
+
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}@if (field.validators?.required) { <span class="ngx-field__req">*</span> }</label>
|
|
238
|
+
@if (masked) {
|
|
239
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
240
|
+
} @else if (readonly) {
|
|
241
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ?? '—' }}</span>
|
|
242
|
+
} @else {
|
|
243
|
+
<input
|
|
244
|
+
[id]="field.id"
|
|
245
|
+
type="text"
|
|
246
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
247
|
+
[formControl]="$any(control)"
|
|
248
|
+
[placeholder]="placeholder"
|
|
249
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
250
|
+
[attr.aria-invalid]="control.invalid && control.touched"
|
|
251
|
+
[attr.aria-describedby]="errorMessage ? field.id + '-error' : null"
|
|
252
|
+
/>
|
|
253
|
+
@if (errorMessage) {
|
|
254
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'" [id]="field.id + '-error'" role="alert">{{ errorMessage }}</span>
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
</div>
|
|
241
258
|
`,
|
|
242
259
|
}]
|
|
243
260
|
}], propDecorators: { field: [{
|
|
@@ -265,28 +282,28 @@ class TextareaFieldComponent {
|
|
|
265
282
|
return resolveLabel(this.field?.placeholder, this.language);
|
|
266
283
|
}
|
|
267
284
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TextareaFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
268
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: TextareaFieldComponent, isStandalone: true, selector: "ngx-textarea-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
269
|
-
<div class="ngx-field ngx-field--textarea"
|
|
270
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
271
|
-
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
|
|
272
|
-
@if (masked) {
|
|
273
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
274
|
-
} @else if (readonly) {
|
|
275
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value }}</span>
|
|
276
|
-
} @else {
|
|
277
|
-
<textarea
|
|
278
|
-
[id]="field.id"
|
|
279
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
280
|
-
[formControl]="$any(control)"
|
|
281
|
-
[placeholder]="placeholder"
|
|
282
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
283
|
-
rows="3"
|
|
284
|
-
></textarea>
|
|
285
|
-
@if (control.invalid && control.touched) {
|
|
286
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
</div>
|
|
285
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: TextareaFieldComponent, isStandalone: true, selector: "ngx-textarea-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
286
|
+
<div class="ngx-field ngx-field--textarea"
|
|
287
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
288
|
+
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
|
|
289
|
+
@if (masked) {
|
|
290
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
291
|
+
} @else if (readonly) {
|
|
292
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value }}</span>
|
|
293
|
+
} @else {
|
|
294
|
+
<textarea
|
|
295
|
+
[id]="field.id"
|
|
296
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
297
|
+
[formControl]="$any(control)"
|
|
298
|
+
[placeholder]="placeholder"
|
|
299
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
300
|
+
rows="3"
|
|
301
|
+
></textarea>
|
|
302
|
+
@if (control.invalid && control.touched) {
|
|
303
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
</div>
|
|
290
307
|
`, 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"] }] });
|
|
291
308
|
}
|
|
292
309
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: TextareaFieldComponent, decorators: [{
|
|
@@ -295,28 +312,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
295
312
|
selector: 'ngx-textarea-field',
|
|
296
313
|
standalone: true,
|
|
297
314
|
imports: [ReactiveFormsModule],
|
|
298
|
-
template: `
|
|
299
|
-
<div class="ngx-field ngx-field--textarea"
|
|
300
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
301
|
-
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
|
|
302
|
-
@if (masked) {
|
|
303
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
304
|
-
} @else if (readonly) {
|
|
305
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value }}</span>
|
|
306
|
-
} @else {
|
|
307
|
-
<textarea
|
|
308
|
-
[id]="field.id"
|
|
309
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
310
|
-
[formControl]="$any(control)"
|
|
311
|
-
[placeholder]="placeholder"
|
|
312
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
313
|
-
rows="3"
|
|
314
|
-
></textarea>
|
|
315
|
-
@if (control.invalid && control.touched) {
|
|
316
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
</div>
|
|
315
|
+
template: `
|
|
316
|
+
<div class="ngx-field ngx-field--textarea"
|
|
317
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
318
|
+
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
|
|
319
|
+
@if (masked) {
|
|
320
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
321
|
+
} @else if (readonly) {
|
|
322
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value }}</span>
|
|
323
|
+
} @else {
|
|
324
|
+
<textarea
|
|
325
|
+
[id]="field.id"
|
|
326
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
327
|
+
[formControl]="$any(control)"
|
|
328
|
+
[placeholder]="placeholder"
|
|
329
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
330
|
+
rows="3"
|
|
331
|
+
></textarea>
|
|
332
|
+
@if (control.invalid && control.touched) {
|
|
333
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
</div>
|
|
320
337
|
`,
|
|
321
338
|
}]
|
|
322
339
|
}], propDecorators: { field: [{
|
|
@@ -356,38 +373,38 @@ class NumberFieldComponent {
|
|
|
356
373
|
return 'Invalid number.';
|
|
357
374
|
}
|
|
358
375
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NumberFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
359
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: NumberFieldComponent, isStandalone: true, selector: "ngx-number-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
360
|
-
<div
|
|
361
|
-
class="ngx-field ngx-field--number"
|
|
362
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
|
|
363
|
-
[class.ngx-field--readonly]="readonly"
|
|
364
|
-
[class.ngx-field--masked]="masked"
|
|
365
|
-
[class.ngx-field--invalid]="control && control.invalid && control.touched"
|
|
366
|
-
>
|
|
367
|
-
<label class="ngx-field__label" [attr.for]="field.id">
|
|
368
|
-
{{ label }}
|
|
369
|
-
@if (field.validators?.required) { <span class="ngx-field__req">*</span> }
|
|
370
|
-
</label>
|
|
371
|
-
@if (masked) {
|
|
372
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
373
|
-
} @else if (readonly) {
|
|
374
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ?? '—' }}</span>
|
|
375
|
-
} @else {
|
|
376
|
-
<input
|
|
377
|
-
[id]="field.id"
|
|
378
|
-
type="number"
|
|
379
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
380
|
-
[formControl]="$any(control)"
|
|
381
|
-
[placeholder]="placeholder"
|
|
382
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
383
|
-
[attr.aria-invalid]="control.invalid && control.touched"
|
|
384
|
-
[attr.aria-describedby]="errorMessage ? field.id + '-error' : null"
|
|
385
|
-
/>
|
|
386
|
-
@if (errorMessage) {
|
|
387
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'" [id]="field.id + '-error'" role="alert">{{ errorMessage }}</span>
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
</div>
|
|
376
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: NumberFieldComponent, isStandalone: true, selector: "ngx-number-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
377
|
+
<div
|
|
378
|
+
class="ngx-field ngx-field--number"
|
|
379
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
|
|
380
|
+
[class.ngx-field--readonly]="readonly"
|
|
381
|
+
[class.ngx-field--masked]="masked"
|
|
382
|
+
[class.ngx-field--invalid]="control && control.invalid && control.touched"
|
|
383
|
+
>
|
|
384
|
+
<label class="ngx-field__label" [attr.for]="field.id">
|
|
385
|
+
{{ label }}
|
|
386
|
+
@if (field.validators?.required) { <span class="ngx-field__req">*</span> }
|
|
387
|
+
</label>
|
|
388
|
+
@if (masked) {
|
|
389
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
390
|
+
} @else if (readonly) {
|
|
391
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ?? '—' }}</span>
|
|
392
|
+
} @else {
|
|
393
|
+
<input
|
|
394
|
+
[id]="field.id"
|
|
395
|
+
type="number"
|
|
396
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
397
|
+
[formControl]="$any(control)"
|
|
398
|
+
[placeholder]="placeholder"
|
|
399
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
400
|
+
[attr.aria-invalid]="control.invalid && control.touched"
|
|
401
|
+
[attr.aria-describedby]="errorMessage ? field.id + '-error' : null"
|
|
402
|
+
/>
|
|
403
|
+
@if (errorMessage) {
|
|
404
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'" [id]="field.id + '-error'" role="alert">{{ errorMessage }}</span>
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
</div>
|
|
391
408
|
`, 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"] }] });
|
|
392
409
|
}
|
|
393
410
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: NumberFieldComponent, decorators: [{
|
|
@@ -396,38 +413,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
396
413
|
selector: 'ngx-number-field',
|
|
397
414
|
standalone: true,
|
|
398
415
|
imports: [ReactiveFormsModule],
|
|
399
|
-
template: `
|
|
400
|
-
<div
|
|
401
|
-
class="ngx-field ngx-field--number"
|
|
402
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
|
|
403
|
-
[class.ngx-field--readonly]="readonly"
|
|
404
|
-
[class.ngx-field--masked]="masked"
|
|
405
|
-
[class.ngx-field--invalid]="control && control.invalid && control.touched"
|
|
406
|
-
>
|
|
407
|
-
<label class="ngx-field__label" [attr.for]="field.id">
|
|
408
|
-
{{ label }}
|
|
409
|
-
@if (field.validators?.required) { <span class="ngx-field__req">*</span> }
|
|
410
|
-
</label>
|
|
411
|
-
@if (masked) {
|
|
412
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
413
|
-
} @else if (readonly) {
|
|
414
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ?? '—' }}</span>
|
|
415
|
-
} @else {
|
|
416
|
-
<input
|
|
417
|
-
[id]="field.id"
|
|
418
|
-
type="number"
|
|
419
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
420
|
-
[formControl]="$any(control)"
|
|
421
|
-
[placeholder]="placeholder"
|
|
422
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
423
|
-
[attr.aria-invalid]="control.invalid && control.touched"
|
|
424
|
-
[attr.aria-describedby]="errorMessage ? field.id + '-error' : null"
|
|
425
|
-
/>
|
|
426
|
-
@if (errorMessage) {
|
|
427
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'" [id]="field.id + '-error'" role="alert">{{ errorMessage }}</span>
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
</div>
|
|
416
|
+
template: `
|
|
417
|
+
<div
|
|
418
|
+
class="ngx-field ngx-field--number"
|
|
419
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
|
|
420
|
+
[class.ngx-field--readonly]="readonly"
|
|
421
|
+
[class.ngx-field--masked]="masked"
|
|
422
|
+
[class.ngx-field--invalid]="control && control.invalid && control.touched"
|
|
423
|
+
>
|
|
424
|
+
<label class="ngx-field__label" [attr.for]="field.id">
|
|
425
|
+
{{ label }}
|
|
426
|
+
@if (field.validators?.required) { <span class="ngx-field__req">*</span> }
|
|
427
|
+
</label>
|
|
428
|
+
@if (masked) {
|
|
429
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
430
|
+
} @else if (readonly) {
|
|
431
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ?? '—' }}</span>
|
|
432
|
+
} @else {
|
|
433
|
+
<input
|
|
434
|
+
[id]="field.id"
|
|
435
|
+
type="number"
|
|
436
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
437
|
+
[formControl]="$any(control)"
|
|
438
|
+
[placeholder]="placeholder"
|
|
439
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
440
|
+
[attr.aria-invalid]="control.invalid && control.touched"
|
|
441
|
+
[attr.aria-describedby]="errorMessage ? field.id + '-error' : null"
|
|
442
|
+
/>
|
|
443
|
+
@if (errorMessage) {
|
|
444
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'" [id]="field.id + '-error'" role="alert">{{ errorMessage }}</span>
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
</div>
|
|
431
448
|
`,
|
|
432
449
|
}]
|
|
433
450
|
}], propDecorators: { field: [{
|
|
@@ -466,31 +483,31 @@ class CurrencyFieldComponent {
|
|
|
466
483
|
}
|
|
467
484
|
}
|
|
468
485
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CurrencyFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
469
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: CurrencyFieldComponent, isStandalone: true, selector: "ngx-currency-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
470
|
-
<div class="ngx-field ngx-field--currency"
|
|
471
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
472
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
473
|
-
@if (masked) {
|
|
474
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
475
|
-
} @else if (readonly) {
|
|
476
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ symbol }}{{ control.value }}</span>
|
|
477
|
-
} @else {
|
|
478
|
-
<div class="ngx-field__currency-wrap">
|
|
479
|
-
<span class="ngx-field__currency-symbol">{{ symbol }}</span>
|
|
480
|
-
<input
|
|
481
|
-
class="ngx-field__input ngx-field__input--currency" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
482
|
-
type="number"
|
|
483
|
-
step="0.01"
|
|
484
|
-
[formControl]="$any(control)"
|
|
485
|
-
[placeholder]="placeholder || '0.00'"
|
|
486
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
487
|
-
/>
|
|
488
|
-
</div>
|
|
489
|
-
@if (control.invalid && control.touched) {
|
|
490
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
</div>
|
|
486
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: CurrencyFieldComponent, isStandalone: true, selector: "ngx-currency-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
487
|
+
<div class="ngx-field ngx-field--currency"
|
|
488
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
489
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
490
|
+
@if (masked) {
|
|
491
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
492
|
+
} @else if (readonly) {
|
|
493
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ symbol }}{{ control.value }}</span>
|
|
494
|
+
} @else {
|
|
495
|
+
<div class="ngx-field__currency-wrap">
|
|
496
|
+
<span class="ngx-field__currency-symbol">{{ symbol }}</span>
|
|
497
|
+
<input
|
|
498
|
+
class="ngx-field__input ngx-field__input--currency" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
499
|
+
type="number"
|
|
500
|
+
step="0.01"
|
|
501
|
+
[formControl]="$any(control)"
|
|
502
|
+
[placeholder]="placeholder || '0.00'"
|
|
503
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
504
|
+
/>
|
|
505
|
+
</div>
|
|
506
|
+
@if (control.invalid && control.touched) {
|
|
507
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
</div>
|
|
494
511
|
`, 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"] }] });
|
|
495
512
|
}
|
|
496
513
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CurrencyFieldComponent, decorators: [{
|
|
@@ -499,31 +516,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
499
516
|
selector: 'ngx-currency-field',
|
|
500
517
|
standalone: true,
|
|
501
518
|
imports: [ReactiveFormsModule],
|
|
502
|
-
template: `
|
|
503
|
-
<div class="ngx-field ngx-field--currency"
|
|
504
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
505
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
506
|
-
@if (masked) {
|
|
507
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
508
|
-
} @else if (readonly) {
|
|
509
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ symbol }}{{ control.value }}</span>
|
|
510
|
-
} @else {
|
|
511
|
-
<div class="ngx-field__currency-wrap">
|
|
512
|
-
<span class="ngx-field__currency-symbol">{{ symbol }}</span>
|
|
513
|
-
<input
|
|
514
|
-
class="ngx-field__input ngx-field__input--currency" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
515
|
-
type="number"
|
|
516
|
-
step="0.01"
|
|
517
|
-
[formControl]="$any(control)"
|
|
518
|
-
[placeholder]="placeholder || '0.00'"
|
|
519
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
520
|
-
/>
|
|
521
|
-
</div>
|
|
522
|
-
@if (control.invalid && control.touched) {
|
|
523
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
</div>
|
|
519
|
+
template: `
|
|
520
|
+
<div class="ngx-field ngx-field--currency"
|
|
521
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
522
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
523
|
+
@if (masked) {
|
|
524
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
525
|
+
} @else if (readonly) {
|
|
526
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ symbol }}{{ control.value }}</span>
|
|
527
|
+
} @else {
|
|
528
|
+
<div class="ngx-field__currency-wrap">
|
|
529
|
+
<span class="ngx-field__currency-symbol">{{ symbol }}</span>
|
|
530
|
+
<input
|
|
531
|
+
class="ngx-field__input ngx-field__input--currency" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
532
|
+
type="number"
|
|
533
|
+
step="0.01"
|
|
534
|
+
[formControl]="$any(control)"
|
|
535
|
+
[placeholder]="placeholder || '0.00'"
|
|
536
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
537
|
+
/>
|
|
538
|
+
</div>
|
|
539
|
+
@if (control.invalid && control.touched) {
|
|
540
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
</div>
|
|
527
544
|
`,
|
|
528
545
|
}]
|
|
529
546
|
}], propDecorators: { field: [{
|
|
@@ -552,36 +569,36 @@ class EmailFieldComponent {
|
|
|
552
569
|
return resolveLabel(this.field?.placeholder, this.language);
|
|
553
570
|
}
|
|
554
571
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EmailFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
555
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EmailFieldComponent, isStandalone: true, selector: "ngx-email-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
556
|
-
<div class="ngx-field ngx-field--email"
|
|
557
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
558
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
559
|
-
@if (masked) {
|
|
560
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
561
|
-
} @else if (readonly) {
|
|
562
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">
|
|
563
|
-
<a [href]="'mailto:' + control.value" class="ngx-field__email-link">{{ control.value || '—' }}</a>
|
|
564
|
-
</span>
|
|
565
|
-
} @else {
|
|
566
|
-
<input
|
|
567
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
568
|
-
type="email"
|
|
569
|
-
autocomplete="email"
|
|
570
|
-
[formControl]="$any(control)"
|
|
571
|
-
[placeholder]="placeholder || 'you@example.com'"
|
|
572
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
573
|
-
/>
|
|
574
|
-
@if (control.invalid && control.touched) {
|
|
575
|
-
@if (control.errors?.['required']) {
|
|
576
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">Email is required</span>
|
|
577
|
-
} @else if (control.errors?.['email']) {
|
|
578
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">Enter a valid email address</span>
|
|
579
|
-
} @else {
|
|
580
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
</div>
|
|
572
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EmailFieldComponent, isStandalone: true, selector: "ngx-email-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
573
|
+
<div class="ngx-field ngx-field--email"
|
|
574
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
575
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
576
|
+
@if (masked) {
|
|
577
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
578
|
+
} @else if (readonly) {
|
|
579
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">
|
|
580
|
+
<a [href]="'mailto:' + control.value" class="ngx-field__email-link">{{ control.value || '—' }}</a>
|
|
581
|
+
</span>
|
|
582
|
+
} @else {
|
|
583
|
+
<input
|
|
584
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
585
|
+
type="email"
|
|
586
|
+
autocomplete="email"
|
|
587
|
+
[formControl]="$any(control)"
|
|
588
|
+
[placeholder]="placeholder || 'you@example.com'"
|
|
589
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
590
|
+
/>
|
|
591
|
+
@if (control.invalid && control.touched) {
|
|
592
|
+
@if (control.errors?.['required']) {
|
|
593
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">Email is required</span>
|
|
594
|
+
} @else if (control.errors?.['email']) {
|
|
595
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">Enter a valid email address</span>
|
|
596
|
+
} @else {
|
|
597
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
</div>
|
|
585
602
|
`, 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"] }] });
|
|
586
603
|
}
|
|
587
604
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EmailFieldComponent, decorators: [{
|
|
@@ -590,36 +607,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
590
607
|
selector: 'ngx-email-field',
|
|
591
608
|
standalone: true,
|
|
592
609
|
imports: [ReactiveFormsModule],
|
|
593
|
-
template: `
|
|
594
|
-
<div class="ngx-field ngx-field--email"
|
|
595
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
596
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
597
|
-
@if (masked) {
|
|
598
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
599
|
-
} @else if (readonly) {
|
|
600
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">
|
|
601
|
-
<a [href]="'mailto:' + control.value" class="ngx-field__email-link">{{ control.value || '—' }}</a>
|
|
602
|
-
</span>
|
|
603
|
-
} @else {
|
|
604
|
-
<input
|
|
605
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
606
|
-
type="email"
|
|
607
|
-
autocomplete="email"
|
|
608
|
-
[formControl]="$any(control)"
|
|
609
|
-
[placeholder]="placeholder || 'you@example.com'"
|
|
610
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
611
|
-
/>
|
|
612
|
-
@if (control.invalid && control.touched) {
|
|
613
|
-
@if (control.errors?.['required']) {
|
|
614
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">Email is required</span>
|
|
615
|
-
} @else if (control.errors?.['email']) {
|
|
616
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">Enter a valid email address</span>
|
|
617
|
-
} @else {
|
|
618
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
</div>
|
|
610
|
+
template: `
|
|
611
|
+
<div class="ngx-field ngx-field--email"
|
|
612
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
613
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
614
|
+
@if (masked) {
|
|
615
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
616
|
+
} @else if (readonly) {
|
|
617
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">
|
|
618
|
+
<a [href]="'mailto:' + control.value" class="ngx-field__email-link">{{ control.value || '—' }}</a>
|
|
619
|
+
</span>
|
|
620
|
+
} @else {
|
|
621
|
+
<input
|
|
622
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
623
|
+
type="email"
|
|
624
|
+
autocomplete="email"
|
|
625
|
+
[formControl]="$any(control)"
|
|
626
|
+
[placeholder]="placeholder || 'you@example.com'"
|
|
627
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
628
|
+
/>
|
|
629
|
+
@if (control.invalid && control.touched) {
|
|
630
|
+
@if (control.errors?.['required']) {
|
|
631
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">Email is required</span>
|
|
632
|
+
} @else if (control.errors?.['email']) {
|
|
633
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">Enter a valid email address</span>
|
|
634
|
+
} @else {
|
|
635
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
</div>
|
|
623
640
|
`,
|
|
624
641
|
}]
|
|
625
642
|
}], propDecorators: { field: [{
|
|
@@ -652,38 +669,38 @@ class PasswordFieldComponent {
|
|
|
652
669
|
this.visible.update(v => !v);
|
|
653
670
|
}
|
|
654
671
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PasswordFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
655
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: PasswordFieldComponent, isStandalone: true, selector: "ngx-password-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
656
|
-
<div class="ngx-field ngx-field--password"
|
|
657
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
658
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
659
|
-
@if (masked) {
|
|
660
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
661
|
-
} @else if (readonly) {
|
|
662
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">••••••••</span>
|
|
663
|
-
} @else {
|
|
664
|
-
<div class="ngx-field__password-wrap">
|
|
665
|
-
<input
|
|
666
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
667
|
-
[type]="visible() ? 'text' : 'password'"
|
|
668
|
-
autocomplete="current-password"
|
|
669
|
-
[formControl]="$any(control)"
|
|
670
|
-
[placeholder]="placeholder || '••••••••'"
|
|
671
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
672
|
-
/>
|
|
673
|
-
<button
|
|
674
|
-
type="button"
|
|
675
|
-
class="ngx-field__eye-btn"
|
|
676
|
-
(click)="toggleVisibility()"
|
|
677
|
-
[attr.aria-label]="visible() ? 'Hide password' : 'Show password'"
|
|
678
|
-
>
|
|
679
|
-
{{ visible() ? '🙈' : '👁' }}
|
|
680
|
-
</button>
|
|
681
|
-
</div>
|
|
682
|
-
@if (control.invalid && control.touched) {
|
|
683
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
</div>
|
|
672
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: PasswordFieldComponent, isStandalone: true, selector: "ngx-password-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
673
|
+
<div class="ngx-field ngx-field--password"
|
|
674
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
675
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
676
|
+
@if (masked) {
|
|
677
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
678
|
+
} @else if (readonly) {
|
|
679
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">••••••••</span>
|
|
680
|
+
} @else {
|
|
681
|
+
<div class="ngx-field__password-wrap">
|
|
682
|
+
<input
|
|
683
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
684
|
+
[type]="visible() ? 'text' : 'password'"
|
|
685
|
+
autocomplete="current-password"
|
|
686
|
+
[formControl]="$any(control)"
|
|
687
|
+
[placeholder]="placeholder || '••••••••'"
|
|
688
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
689
|
+
/>
|
|
690
|
+
<button
|
|
691
|
+
type="button"
|
|
692
|
+
class="ngx-field__eye-btn"
|
|
693
|
+
(click)="toggleVisibility()"
|
|
694
|
+
[attr.aria-label]="visible() ? 'Hide password' : 'Show password'"
|
|
695
|
+
>
|
|
696
|
+
{{ visible() ? '🙈' : '👁' }}
|
|
697
|
+
</button>
|
|
698
|
+
</div>
|
|
699
|
+
@if (control.invalid && control.touched) {
|
|
700
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
</div>
|
|
687
704
|
`, 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"] }] });
|
|
688
705
|
}
|
|
689
706
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PasswordFieldComponent, decorators: [{
|
|
@@ -692,38 +709,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
692
709
|
selector: 'ngx-password-field',
|
|
693
710
|
standalone: true,
|
|
694
711
|
imports: [ReactiveFormsModule],
|
|
695
|
-
template: `
|
|
696
|
-
<div class="ngx-field ngx-field--password"
|
|
697
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
698
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
699
|
-
@if (masked) {
|
|
700
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
701
|
-
} @else if (readonly) {
|
|
702
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">••••••••</span>
|
|
703
|
-
} @else {
|
|
704
|
-
<div class="ngx-field__password-wrap">
|
|
705
|
-
<input
|
|
706
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
707
|
-
[type]="visible() ? 'text' : 'password'"
|
|
708
|
-
autocomplete="current-password"
|
|
709
|
-
[formControl]="$any(control)"
|
|
710
|
-
[placeholder]="placeholder || '••••••••'"
|
|
711
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
712
|
-
/>
|
|
713
|
-
<button
|
|
714
|
-
type="button"
|
|
715
|
-
class="ngx-field__eye-btn"
|
|
716
|
-
(click)="toggleVisibility()"
|
|
717
|
-
[attr.aria-label]="visible() ? 'Hide password' : 'Show password'"
|
|
718
|
-
>
|
|
719
|
-
{{ visible() ? '🙈' : '👁' }}
|
|
720
|
-
</button>
|
|
721
|
-
</div>
|
|
722
|
-
@if (control.invalid && control.touched) {
|
|
723
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
</div>
|
|
712
|
+
template: `
|
|
713
|
+
<div class="ngx-field ngx-field--password"
|
|
714
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
715
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
716
|
+
@if (masked) {
|
|
717
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
718
|
+
} @else if (readonly) {
|
|
719
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">••••••••</span>
|
|
720
|
+
} @else {
|
|
721
|
+
<div class="ngx-field__password-wrap">
|
|
722
|
+
<input
|
|
723
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
724
|
+
[type]="visible() ? 'text' : 'password'"
|
|
725
|
+
autocomplete="current-password"
|
|
726
|
+
[formControl]="$any(control)"
|
|
727
|
+
[placeholder]="placeholder || '••••••••'"
|
|
728
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
729
|
+
/>
|
|
730
|
+
<button
|
|
731
|
+
type="button"
|
|
732
|
+
class="ngx-field__eye-btn"
|
|
733
|
+
(click)="toggleVisibility()"
|
|
734
|
+
[attr.aria-label]="visible() ? 'Hide password' : 'Show password'"
|
|
735
|
+
>
|
|
736
|
+
{{ visible() ? '🙈' : '👁' }}
|
|
737
|
+
</button>
|
|
738
|
+
</div>
|
|
739
|
+
@if (control.invalid && control.touched) {
|
|
740
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
</div>
|
|
727
744
|
`,
|
|
728
745
|
}]
|
|
729
746
|
}], propDecorators: { field: [{
|
|
@@ -748,28 +765,28 @@ class CheckboxFieldComponent {
|
|
|
748
765
|
return resolveLabel(this.field?.label, this.language);
|
|
749
766
|
}
|
|
750
767
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CheckboxFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
751
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: CheckboxFieldComponent, isStandalone: true, selector: "ngx-checkbox-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
752
|
-
<div class="ngx-field ngx-field--checkbox"
|
|
753
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
754
|
-
@if (masked) {
|
|
755
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
756
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
757
|
-
} @else if (readonly) {
|
|
758
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
759
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ? 'Yes' : 'No' }}</span>
|
|
760
|
-
} @else {
|
|
761
|
-
<label class="ngx-field__label" [attr.for]="field.id">
|
|
762
|
-
<input
|
|
763
|
-
[id]="field.id"
|
|
764
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
765
|
-
type="checkbox"
|
|
766
|
-
[formControl]="$any(control)"
|
|
767
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
768
|
-
/>
|
|
769
|
-
{{ label }}
|
|
770
|
-
</label>
|
|
771
|
-
}
|
|
772
|
-
</div>
|
|
768
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: CheckboxFieldComponent, isStandalone: true, selector: "ngx-checkbox-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
769
|
+
<div class="ngx-field ngx-field--checkbox"
|
|
770
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
771
|
+
@if (masked) {
|
|
772
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
773
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
774
|
+
} @else if (readonly) {
|
|
775
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
776
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ? 'Yes' : 'No' }}</span>
|
|
777
|
+
} @else {
|
|
778
|
+
<label class="ngx-field__label" [attr.for]="field.id">
|
|
779
|
+
<input
|
|
780
|
+
[id]="field.id"
|
|
781
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
782
|
+
type="checkbox"
|
|
783
|
+
[formControl]="$any(control)"
|
|
784
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
785
|
+
/>
|
|
786
|
+
{{ label }}
|
|
787
|
+
</label>
|
|
788
|
+
}
|
|
789
|
+
</div>
|
|
773
790
|
`, 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"] }] });
|
|
774
791
|
}
|
|
775
792
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: CheckboxFieldComponent, decorators: [{
|
|
@@ -778,28 +795,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
778
795
|
selector: 'ngx-checkbox-field',
|
|
779
796
|
standalone: true,
|
|
780
797
|
imports: [ReactiveFormsModule],
|
|
781
|
-
template: `
|
|
782
|
-
<div class="ngx-field ngx-field--checkbox"
|
|
783
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
784
|
-
@if (masked) {
|
|
785
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
786
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
787
|
-
} @else if (readonly) {
|
|
788
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
789
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ? 'Yes' : 'No' }}</span>
|
|
790
|
-
} @else {
|
|
791
|
-
<label class="ngx-field__label" [attr.for]="field.id">
|
|
792
|
-
<input
|
|
793
|
-
[id]="field.id"
|
|
794
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
795
|
-
type="checkbox"
|
|
796
|
-
[formControl]="$any(control)"
|
|
797
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
798
|
-
/>
|
|
799
|
-
{{ label }}
|
|
800
|
-
</label>
|
|
801
|
-
}
|
|
802
|
-
</div>
|
|
798
|
+
template: `
|
|
799
|
+
<div class="ngx-field ngx-field--checkbox"
|
|
800
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
801
|
+
@if (masked) {
|
|
802
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
803
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
804
|
+
} @else if (readonly) {
|
|
805
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
806
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ? 'Yes' : 'No' }}</span>
|
|
807
|
+
} @else {
|
|
808
|
+
<label class="ngx-field__label" [attr.for]="field.id">
|
|
809
|
+
<input
|
|
810
|
+
[id]="field.id"
|
|
811
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
812
|
+
type="checkbox"
|
|
813
|
+
[formControl]="$any(control)"
|
|
814
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
815
|
+
/>
|
|
816
|
+
{{ label }}
|
|
817
|
+
</label>
|
|
818
|
+
}
|
|
819
|
+
</div>
|
|
803
820
|
`,
|
|
804
821
|
}]
|
|
805
822
|
}], propDecorators: { field: [{
|
|
@@ -829,35 +846,35 @@ class BooleanFieldComponent {
|
|
|
829
846
|
return resolveLabel(this.field?.label, this.language);
|
|
830
847
|
}
|
|
831
848
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BooleanFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
832
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: BooleanFieldComponent, isStandalone: true, selector: "ngx-boolean-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
833
|
-
<div class="ngx-field ngx-field--boolean"
|
|
834
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
835
|
-
@if (masked) {
|
|
836
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
837
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
838
|
-
} @else if (readonly) {
|
|
839
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
840
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ? 'Yes' : 'No' }}</span>
|
|
841
|
-
} @else {
|
|
842
|
-
<div class="ngx-field__toggle-wrap">
|
|
843
|
-
<label class="ngx-field__toggle-label">{{ label }}</label>
|
|
844
|
-
<label class="ngx-field__toggle-switch" [class.ngx-field__toggle-switch--checked]="control.value">
|
|
845
|
-
<input
|
|
846
|
-
class="ngx-field__toggle-input"
|
|
847
|
-
type="checkbox"
|
|
848
|
-
role="switch"
|
|
849
|
-
[formControl]="$any(control)"
|
|
850
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
851
|
-
[attr.aria-checked]="control.value"
|
|
852
|
-
/>
|
|
853
|
-
<span class="ngx-field__toggle-track">
|
|
854
|
-
<span class="ngx-field__toggle-thumb"></span>
|
|
855
|
-
</span>
|
|
856
|
-
<span class="ngx-field__toggle-text">{{ control.value ? 'Yes' : 'No' }}</span>
|
|
857
|
-
</label>
|
|
858
|
-
</div>
|
|
859
|
-
}
|
|
860
|
-
</div>
|
|
849
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: BooleanFieldComponent, isStandalone: true, selector: "ngx-boolean-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
850
|
+
<div class="ngx-field ngx-field--boolean"
|
|
851
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
852
|
+
@if (masked) {
|
|
853
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
854
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
855
|
+
} @else if (readonly) {
|
|
856
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
857
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ? 'Yes' : 'No' }}</span>
|
|
858
|
+
} @else {
|
|
859
|
+
<div class="ngx-field__toggle-wrap">
|
|
860
|
+
<label class="ngx-field__toggle-label">{{ label }}</label>
|
|
861
|
+
<label class="ngx-field__toggle-switch" [class.ngx-field__toggle-switch--checked]="control.value">
|
|
862
|
+
<input
|
|
863
|
+
class="ngx-field__toggle-input"
|
|
864
|
+
type="checkbox"
|
|
865
|
+
role="switch"
|
|
866
|
+
[formControl]="$any(control)"
|
|
867
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
868
|
+
[attr.aria-checked]="control.value"
|
|
869
|
+
/>
|
|
870
|
+
<span class="ngx-field__toggle-track">
|
|
871
|
+
<span class="ngx-field__toggle-thumb"></span>
|
|
872
|
+
</span>
|
|
873
|
+
<span class="ngx-field__toggle-text">{{ control.value ? 'Yes' : 'No' }}</span>
|
|
874
|
+
</label>
|
|
875
|
+
</div>
|
|
876
|
+
}
|
|
877
|
+
</div>
|
|
861
878
|
`, 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"] }] });
|
|
862
879
|
}
|
|
863
880
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: BooleanFieldComponent, decorators: [{
|
|
@@ -866,35 +883,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
866
883
|
selector: 'ngx-boolean-field',
|
|
867
884
|
standalone: true,
|
|
868
885
|
imports: [ReactiveFormsModule],
|
|
869
|
-
template: `
|
|
870
|
-
<div class="ngx-field ngx-field--boolean"
|
|
871
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
872
|
-
@if (masked) {
|
|
873
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
874
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
875
|
-
} @else if (readonly) {
|
|
876
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
877
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ? 'Yes' : 'No' }}</span>
|
|
878
|
-
} @else {
|
|
879
|
-
<div class="ngx-field__toggle-wrap">
|
|
880
|
-
<label class="ngx-field__toggle-label">{{ label }}</label>
|
|
881
|
-
<label class="ngx-field__toggle-switch" [class.ngx-field__toggle-switch--checked]="control.value">
|
|
882
|
-
<input
|
|
883
|
-
class="ngx-field__toggle-input"
|
|
884
|
-
type="checkbox"
|
|
885
|
-
role="switch"
|
|
886
|
-
[formControl]="$any(control)"
|
|
887
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
888
|
-
[attr.aria-checked]="control.value"
|
|
889
|
-
/>
|
|
890
|
-
<span class="ngx-field__toggle-track">
|
|
891
|
-
<span class="ngx-field__toggle-thumb"></span>
|
|
892
|
-
</span>
|
|
893
|
-
<span class="ngx-field__toggle-text">{{ control.value ? 'Yes' : 'No' }}</span>
|
|
894
|
-
</label>
|
|
895
|
-
</div>
|
|
896
|
-
}
|
|
897
|
-
</div>
|
|
886
|
+
template: `
|
|
887
|
+
<div class="ngx-field ngx-field--boolean"
|
|
888
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
889
|
+
@if (masked) {
|
|
890
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
891
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
892
|
+
} @else if (readonly) {
|
|
893
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
894
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value ? 'Yes' : 'No' }}</span>
|
|
895
|
+
} @else {
|
|
896
|
+
<div class="ngx-field__toggle-wrap">
|
|
897
|
+
<label class="ngx-field__toggle-label">{{ label }}</label>
|
|
898
|
+
<label class="ngx-field__toggle-switch" [class.ngx-field__toggle-switch--checked]="control.value">
|
|
899
|
+
<input
|
|
900
|
+
class="ngx-field__toggle-input"
|
|
901
|
+
type="checkbox"
|
|
902
|
+
role="switch"
|
|
903
|
+
[formControl]="$any(control)"
|
|
904
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
905
|
+
[attr.aria-checked]="control.value"
|
|
906
|
+
/>
|
|
907
|
+
<span class="ngx-field__toggle-track">
|
|
908
|
+
<span class="ngx-field__toggle-thumb"></span>
|
|
909
|
+
</span>
|
|
910
|
+
<span class="ngx-field__toggle-text">{{ control.value ? 'Yes' : 'No' }}</span>
|
|
911
|
+
</label>
|
|
912
|
+
</div>
|
|
913
|
+
}
|
|
914
|
+
</div>
|
|
898
915
|
`,
|
|
899
916
|
}]
|
|
900
917
|
}], propDecorators: { field: [{
|
|
@@ -930,27 +947,27 @@ class DateFieldComponent {
|
|
|
930
947
|
}
|
|
931
948
|
}
|
|
932
949
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DateFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
933
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: DateFieldComponent, isStandalone: true, selector: "ngx-date-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
934
|
-
<div class="ngx-field ngx-field--date"
|
|
935
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
936
|
-
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
|
|
937
|
-
@if (masked) {
|
|
938
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
939
|
-
} @else if (readonly) {
|
|
940
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ formatDate(control.value) }}</span>
|
|
941
|
-
} @else {
|
|
942
|
-
<input
|
|
943
|
-
[id]="field.id"
|
|
944
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
945
|
-
type="date"
|
|
946
|
-
[formControl]="$any(control)"
|
|
947
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
948
|
-
/>
|
|
949
|
-
@if (control.invalid && control.touched) {
|
|
950
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
</div>
|
|
950
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: DateFieldComponent, isStandalone: true, selector: "ngx-date-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
951
|
+
<div class="ngx-field ngx-field--date"
|
|
952
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
953
|
+
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
|
|
954
|
+
@if (masked) {
|
|
955
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
956
|
+
} @else if (readonly) {
|
|
957
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ formatDate(control.value) }}</span>
|
|
958
|
+
} @else {
|
|
959
|
+
<input
|
|
960
|
+
[id]="field.id"
|
|
961
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
962
|
+
type="date"
|
|
963
|
+
[formControl]="$any(control)"
|
|
964
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
965
|
+
/>
|
|
966
|
+
@if (control.invalid && control.touched) {
|
|
967
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
</div>
|
|
954
971
|
`, 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"] }] });
|
|
955
972
|
}
|
|
956
973
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DateFieldComponent, decorators: [{
|
|
@@ -959,27 +976,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
959
976
|
selector: 'ngx-date-field',
|
|
960
977
|
standalone: true,
|
|
961
978
|
imports: [ReactiveFormsModule],
|
|
962
|
-
template: `
|
|
963
|
-
<div class="ngx-field ngx-field--date"
|
|
964
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
965
|
-
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
|
|
966
|
-
@if (masked) {
|
|
967
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
968
|
-
} @else if (readonly) {
|
|
969
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ formatDate(control.value) }}</span>
|
|
970
|
-
} @else {
|
|
971
|
-
<input
|
|
972
|
-
[id]="field.id"
|
|
973
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
974
|
-
type="date"
|
|
975
|
-
[formControl]="$any(control)"
|
|
976
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
977
|
-
/>
|
|
978
|
-
@if (control.invalid && control.touched) {
|
|
979
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
</div>
|
|
979
|
+
template: `
|
|
980
|
+
<div class="ngx-field ngx-field--date"
|
|
981
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
982
|
+
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
|
|
983
|
+
@if (masked) {
|
|
984
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
985
|
+
} @else if (readonly) {
|
|
986
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ formatDate(control.value) }}</span>
|
|
987
|
+
} @else {
|
|
988
|
+
<input
|
|
989
|
+
[id]="field.id"
|
|
990
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
991
|
+
type="date"
|
|
992
|
+
[formControl]="$any(control)"
|
|
993
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
994
|
+
/>
|
|
995
|
+
@if (control.invalid && control.touched) {
|
|
996
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
</div>
|
|
983
1000
|
`,
|
|
984
1001
|
}]
|
|
985
1002
|
}], propDecorators: { field: [{
|
|
@@ -1042,44 +1059,44 @@ class MonthYearFieldComponent {
|
|
|
1042
1059
|
return monthName && year ? `${monthName} ${year}` : String(value);
|
|
1043
1060
|
}
|
|
1044
1061
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MonthYearFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1045
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: MonthYearFieldComponent, isStandalone: true, selector: "ngx-month-year-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
1046
|
-
<div class="ngx-field ngx-field--month-year"
|
|
1047
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1048
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
1049
|
-
@if (masked) {
|
|
1050
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1051
|
-
} @else if (readonly) {
|
|
1052
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ formatValue(control.value) }}</span>
|
|
1053
|
-
} @else {
|
|
1054
|
-
<div class="ngx-field__month-year-wrap">
|
|
1055
|
-
<select
|
|
1056
|
-
class="ngx-field__input ngx-field__input--month" [attr.data-testid]="'field-' + field.id + '-month'"
|
|
1057
|
-
[value]="selectedMonth"
|
|
1058
|
-
(change)="onMonthChange($any($event.target).value)"
|
|
1059
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
1060
|
-
>
|
|
1061
|
-
<option value="">Month</option>
|
|
1062
|
-
@for (m of months; track m.value) {
|
|
1063
|
-
<option [value]="m.value">{{ m.label }}</option>
|
|
1064
|
-
}
|
|
1065
|
-
</select>
|
|
1066
|
-
<select
|
|
1067
|
-
class="ngx-field__input ngx-field__input--year" [attr.data-testid]="'field-' + field.id + '-year'"
|
|
1068
|
-
[value]="selectedYear"
|
|
1069
|
-
(change)="onYearChange($any($event.target).value)"
|
|
1070
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
1071
|
-
>
|
|
1072
|
-
<option value="">Year</option>
|
|
1073
|
-
@for (y of years; track y) {
|
|
1074
|
-
<option [value]="y">{{ y }}</option>
|
|
1075
|
-
}
|
|
1076
|
-
</select>
|
|
1077
|
-
</div>
|
|
1078
|
-
@if (control.invalid && control.touched) {
|
|
1079
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
</div>
|
|
1062
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: MonthYearFieldComponent, isStandalone: true, selector: "ngx-month-year-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
1063
|
+
<div class="ngx-field ngx-field--month-year"
|
|
1064
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1065
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
1066
|
+
@if (masked) {
|
|
1067
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1068
|
+
} @else if (readonly) {
|
|
1069
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ formatValue(control.value) }}</span>
|
|
1070
|
+
} @else {
|
|
1071
|
+
<div class="ngx-field__month-year-wrap">
|
|
1072
|
+
<select
|
|
1073
|
+
class="ngx-field__input ngx-field__input--month" [attr.data-testid]="'field-' + field.id + '-month'"
|
|
1074
|
+
[value]="selectedMonth"
|
|
1075
|
+
(change)="onMonthChange($any($event.target).value)"
|
|
1076
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
1077
|
+
>
|
|
1078
|
+
<option value="">Month</option>
|
|
1079
|
+
@for (m of months; track m.value) {
|
|
1080
|
+
<option [value]="m.value">{{ m.label }}</option>
|
|
1081
|
+
}
|
|
1082
|
+
</select>
|
|
1083
|
+
<select
|
|
1084
|
+
class="ngx-field__input ngx-field__input--year" [attr.data-testid]="'field-' + field.id + '-year'"
|
|
1085
|
+
[value]="selectedYear"
|
|
1086
|
+
(change)="onYearChange($any($event.target).value)"
|
|
1087
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
1088
|
+
>
|
|
1089
|
+
<option value="">Year</option>
|
|
1090
|
+
@for (y of years; track y) {
|
|
1091
|
+
<option [value]="y">{{ y }}</option>
|
|
1092
|
+
}
|
|
1093
|
+
</select>
|
|
1094
|
+
</div>
|
|
1095
|
+
@if (control.invalid && control.touched) {
|
|
1096
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
</div>
|
|
1083
1100
|
`, 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"] }] });
|
|
1084
1101
|
}
|
|
1085
1102
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MonthYearFieldComponent, decorators: [{
|
|
@@ -1088,44 +1105,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1088
1105
|
selector: 'ngx-month-year-field',
|
|
1089
1106
|
standalone: true,
|
|
1090
1107
|
imports: [ReactiveFormsModule],
|
|
1091
|
-
template: `
|
|
1092
|
-
<div class="ngx-field ngx-field--month-year"
|
|
1093
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1094
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
1095
|
-
@if (masked) {
|
|
1096
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1097
|
-
} @else if (readonly) {
|
|
1098
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ formatValue(control.value) }}</span>
|
|
1099
|
-
} @else {
|
|
1100
|
-
<div class="ngx-field__month-year-wrap">
|
|
1101
|
-
<select
|
|
1102
|
-
class="ngx-field__input ngx-field__input--month" [attr.data-testid]="'field-' + field.id + '-month'"
|
|
1103
|
-
[value]="selectedMonth"
|
|
1104
|
-
(change)="onMonthChange($any($event.target).value)"
|
|
1105
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
1106
|
-
>
|
|
1107
|
-
<option value="">Month</option>
|
|
1108
|
-
@for (m of months; track m.value) {
|
|
1109
|
-
<option [value]="m.value">{{ m.label }}</option>
|
|
1110
|
-
}
|
|
1111
|
-
</select>
|
|
1112
|
-
<select
|
|
1113
|
-
class="ngx-field__input ngx-field__input--year" [attr.data-testid]="'field-' + field.id + '-year'"
|
|
1114
|
-
[value]="selectedYear"
|
|
1115
|
-
(change)="onYearChange($any($event.target).value)"
|
|
1116
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
1117
|
-
>
|
|
1118
|
-
<option value="">Year</option>
|
|
1119
|
-
@for (y of years; track y) {
|
|
1120
|
-
<option [value]="y">{{ y }}</option>
|
|
1121
|
-
}
|
|
1122
|
-
</select>
|
|
1123
|
-
</div>
|
|
1124
|
-
@if (control.invalid && control.touched) {
|
|
1125
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
</div>
|
|
1108
|
+
template: `
|
|
1109
|
+
<div class="ngx-field ngx-field--month-year"
|
|
1110
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1111
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
1112
|
+
@if (masked) {
|
|
1113
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1114
|
+
} @else if (readonly) {
|
|
1115
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ formatValue(control.value) }}</span>
|
|
1116
|
+
} @else {
|
|
1117
|
+
<div class="ngx-field__month-year-wrap">
|
|
1118
|
+
<select
|
|
1119
|
+
class="ngx-field__input ngx-field__input--month" [attr.data-testid]="'field-' + field.id + '-month'"
|
|
1120
|
+
[value]="selectedMonth"
|
|
1121
|
+
(change)="onMonthChange($any($event.target).value)"
|
|
1122
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
1123
|
+
>
|
|
1124
|
+
<option value="">Month</option>
|
|
1125
|
+
@for (m of months; track m.value) {
|
|
1126
|
+
<option [value]="m.value">{{ m.label }}</option>
|
|
1127
|
+
}
|
|
1128
|
+
</select>
|
|
1129
|
+
<select
|
|
1130
|
+
class="ngx-field__input ngx-field__input--year" [attr.data-testid]="'field-' + field.id + '-year'"
|
|
1131
|
+
[value]="selectedYear"
|
|
1132
|
+
(change)="onYearChange($any($event.target).value)"
|
|
1133
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
1134
|
+
>
|
|
1135
|
+
<option value="">Year</option>
|
|
1136
|
+
@for (y of years; track y) {
|
|
1137
|
+
<option [value]="y">{{ y }}</option>
|
|
1138
|
+
}
|
|
1139
|
+
</select>
|
|
1140
|
+
</div>
|
|
1141
|
+
@if (control.invalid && control.touched) {
|
|
1142
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
</div>
|
|
1129
1146
|
`,
|
|
1130
1147
|
}]
|
|
1131
1148
|
}], propDecorators: { field: [{
|
|
@@ -1448,46 +1465,46 @@ class DropdownFieldComponent {
|
|
|
1448
1465
|
return 'Invalid selection.';
|
|
1449
1466
|
}
|
|
1450
1467
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DropdownFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1451
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: DropdownFieldComponent, isStandalone: true, selector: "ngx-dropdown-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
1452
|
-
<div
|
|
1453
|
-
class="ngx-field ngx-field--dropdown"
|
|
1454
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
|
|
1455
|
-
[class.ngx-field--readonly]="readonly"
|
|
1456
|
-
[class.ngx-field--masked]="masked"
|
|
1457
|
-
[class.ngx-field--invalid]="control && control.invalid && control.touched"
|
|
1458
|
-
>
|
|
1459
|
-
<label class="ngx-field__label" [attr.for]="field.id">
|
|
1460
|
-
{{ label }}
|
|
1461
|
-
@if (field.validators?.required) { <span class="ngx-field__req">*</span> }
|
|
1462
|
-
</label>
|
|
1463
|
-
@if (masked) {
|
|
1464
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1465
|
-
} @else if (readonly) {
|
|
1466
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getLabel(control.value) }}</span>
|
|
1467
|
-
} @else {
|
|
1468
|
-
<select
|
|
1469
|
-
[id]="field.id"
|
|
1470
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
1471
|
-
[formControl]="$any(control)"
|
|
1472
|
-
[compareWith]="compareFn"
|
|
1473
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
1474
|
-
[attr.aria-invalid]="control.invalid && control.touched"
|
|
1475
|
-
[attr.aria-describedby]="errorMessage ? field.id + '-error' : null"
|
|
1476
|
-
>
|
|
1477
|
-
<option [value]="''">{{ placeholder || 'Select...' }}</option>
|
|
1478
|
-
@for (option of options(); track getOptLabel(option)) {
|
|
1479
|
-
@if (isObjectVal(option)) {
|
|
1480
|
-
<option [ngValue]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1481
|
-
} @else {
|
|
1482
|
-
<option [value]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
</select>
|
|
1486
|
-
@if (errorMessage) {
|
|
1487
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'" [id]="field.id + '-error'" role="alert">{{ errorMessage }}</span>
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
</div>
|
|
1468
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: DropdownFieldComponent, isStandalone: true, selector: "ngx-dropdown-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
1469
|
+
<div
|
|
1470
|
+
class="ngx-field ngx-field--dropdown"
|
|
1471
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
|
|
1472
|
+
[class.ngx-field--readonly]="readonly"
|
|
1473
|
+
[class.ngx-field--masked]="masked"
|
|
1474
|
+
[class.ngx-field--invalid]="control && control.invalid && control.touched"
|
|
1475
|
+
>
|
|
1476
|
+
<label class="ngx-field__label" [attr.for]="field.id">
|
|
1477
|
+
{{ label }}
|
|
1478
|
+
@if (field.validators?.required) { <span class="ngx-field__req">*</span> }
|
|
1479
|
+
</label>
|
|
1480
|
+
@if (masked) {
|
|
1481
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1482
|
+
} @else if (readonly) {
|
|
1483
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getLabel(control.value) }}</span>
|
|
1484
|
+
} @else {
|
|
1485
|
+
<select
|
|
1486
|
+
[id]="field.id"
|
|
1487
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
1488
|
+
[formControl]="$any(control)"
|
|
1489
|
+
[compareWith]="compareFn"
|
|
1490
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
1491
|
+
[attr.aria-invalid]="control.invalid && control.touched"
|
|
1492
|
+
[attr.aria-describedby]="errorMessage ? field.id + '-error' : null"
|
|
1493
|
+
>
|
|
1494
|
+
<option [value]="''">{{ placeholder || 'Select...' }}</option>
|
|
1495
|
+
@for (option of options(); track getOptLabel(option)) {
|
|
1496
|
+
@if (isObjectVal(option)) {
|
|
1497
|
+
<option [ngValue]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1498
|
+
} @else {
|
|
1499
|
+
<option [value]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
</select>
|
|
1503
|
+
@if (errorMessage) {
|
|
1504
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'" [id]="field.id + '-error'" role="alert">{{ errorMessage }}</span>
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
</div>
|
|
1491
1508
|
`, 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"] }] });
|
|
1492
1509
|
}
|
|
1493
1510
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DropdownFieldComponent, decorators: [{
|
|
@@ -1496,46 +1513,46 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1496
1513
|
selector: 'ngx-dropdown-field',
|
|
1497
1514
|
standalone: true,
|
|
1498
1515
|
imports: [ReactiveFormsModule],
|
|
1499
|
-
template: `
|
|
1500
|
-
<div
|
|
1501
|
-
class="ngx-field ngx-field--dropdown"
|
|
1502
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
|
|
1503
|
-
[class.ngx-field--readonly]="readonly"
|
|
1504
|
-
[class.ngx-field--masked]="masked"
|
|
1505
|
-
[class.ngx-field--invalid]="control && control.invalid && control.touched"
|
|
1506
|
-
>
|
|
1507
|
-
<label class="ngx-field__label" [attr.for]="field.id">
|
|
1508
|
-
{{ label }}
|
|
1509
|
-
@if (field.validators?.required) { <span class="ngx-field__req">*</span> }
|
|
1510
|
-
</label>
|
|
1511
|
-
@if (masked) {
|
|
1512
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1513
|
-
} @else if (readonly) {
|
|
1514
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getLabel(control.value) }}</span>
|
|
1515
|
-
} @else {
|
|
1516
|
-
<select
|
|
1517
|
-
[id]="field.id"
|
|
1518
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
1519
|
-
[formControl]="$any(control)"
|
|
1520
|
-
[compareWith]="compareFn"
|
|
1521
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
1522
|
-
[attr.aria-invalid]="control.invalid && control.touched"
|
|
1523
|
-
[attr.aria-describedby]="errorMessage ? field.id + '-error' : null"
|
|
1524
|
-
>
|
|
1525
|
-
<option [value]="''">{{ placeholder || 'Select...' }}</option>
|
|
1526
|
-
@for (option of options(); track getOptLabel(option)) {
|
|
1527
|
-
@if (isObjectVal(option)) {
|
|
1528
|
-
<option [ngValue]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1529
|
-
} @else {
|
|
1530
|
-
<option [value]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
</select>
|
|
1534
|
-
@if (errorMessage) {
|
|
1535
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'" [id]="field.id + '-error'" role="alert">{{ errorMessage }}</span>
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
</div>
|
|
1516
|
+
template: `
|
|
1517
|
+
<div
|
|
1518
|
+
class="ngx-field ngx-field--dropdown"
|
|
1519
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type"
|
|
1520
|
+
[class.ngx-field--readonly]="readonly"
|
|
1521
|
+
[class.ngx-field--masked]="masked"
|
|
1522
|
+
[class.ngx-field--invalid]="control && control.invalid && control.touched"
|
|
1523
|
+
>
|
|
1524
|
+
<label class="ngx-field__label" [attr.for]="field.id">
|
|
1525
|
+
{{ label }}
|
|
1526
|
+
@if (field.validators?.required) { <span class="ngx-field__req">*</span> }
|
|
1527
|
+
</label>
|
|
1528
|
+
@if (masked) {
|
|
1529
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1530
|
+
} @else if (readonly) {
|
|
1531
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getLabel(control.value) }}</span>
|
|
1532
|
+
} @else {
|
|
1533
|
+
<select
|
|
1534
|
+
[id]="field.id"
|
|
1535
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
1536
|
+
[formControl]="$any(control)"
|
|
1537
|
+
[compareWith]="compareFn"
|
|
1538
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
1539
|
+
[attr.aria-invalid]="control.invalid && control.touched"
|
|
1540
|
+
[attr.aria-describedby]="errorMessage ? field.id + '-error' : null"
|
|
1541
|
+
>
|
|
1542
|
+
<option [value]="''">{{ placeholder || 'Select...' }}</option>
|
|
1543
|
+
@for (option of options(); track getOptLabel(option)) {
|
|
1544
|
+
@if (isObjectVal(option)) {
|
|
1545
|
+
<option [ngValue]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1546
|
+
} @else {
|
|
1547
|
+
<option [value]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
</select>
|
|
1551
|
+
@if (errorMessage) {
|
|
1552
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'" [id]="field.id + '-error'" role="alert">{{ errorMessage }}</span>
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
</div>
|
|
1539
1556
|
`,
|
|
1540
1557
|
}]
|
|
1541
1558
|
}], propDecorators: { field: [{
|
|
@@ -1612,37 +1629,37 @@ class RadioFieldComponent {
|
|
|
1612
1629
|
return typeof value === 'object' ? resolveLabel(value, this.language) : (value ?? '—');
|
|
1613
1630
|
}
|
|
1614
1631
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RadioFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1615
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: RadioFieldComponent, isStandalone: true, selector: "ngx-radio-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
1616
|
-
<div class="ngx-field ngx-field--radio"
|
|
1617
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1618
|
-
<fieldset class="ngx-field__fieldset">
|
|
1619
|
-
<legend class="ngx-field__label">{{ label }}</legend>
|
|
1620
|
-
@if (masked) {
|
|
1621
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1622
|
-
} @else if (readonly) {
|
|
1623
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getSelectedLabel() }}</span>
|
|
1624
|
-
} @else {
|
|
1625
|
-
<div class="ngx-field__radio-group">
|
|
1626
|
-
@for (option of options(); track getOptLabel(option)) {
|
|
1627
|
-
<label class="ngx-field__radio-option" [attr.for]="getRadioId(option)">
|
|
1628
|
-
<input
|
|
1629
|
-
[id]="getRadioId(option)"
|
|
1630
|
-
type="radio"
|
|
1631
|
-
class="ngx-field__radio-input"
|
|
1632
|
-
[formControl]="$any(control)"
|
|
1633
|
-
[value]="getOptStoredVal(option)"
|
|
1634
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
1635
|
-
/>
|
|
1636
|
-
<span class="ngx-field__radio-label">{{ getOptLabel(option) }}</span>
|
|
1637
|
-
</label>
|
|
1638
|
-
}
|
|
1639
|
-
</div>
|
|
1640
|
-
@if (control.invalid && control.touched) {
|
|
1641
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
</fieldset>
|
|
1645
|
-
</div>
|
|
1632
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: RadioFieldComponent, isStandalone: true, selector: "ngx-radio-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
1633
|
+
<div class="ngx-field ngx-field--radio"
|
|
1634
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1635
|
+
<fieldset class="ngx-field__fieldset">
|
|
1636
|
+
<legend class="ngx-field__label">{{ label }}</legend>
|
|
1637
|
+
@if (masked) {
|
|
1638
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1639
|
+
} @else if (readonly) {
|
|
1640
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getSelectedLabel() }}</span>
|
|
1641
|
+
} @else {
|
|
1642
|
+
<div class="ngx-field__radio-group">
|
|
1643
|
+
@for (option of options(); track getOptLabel(option)) {
|
|
1644
|
+
<label class="ngx-field__radio-option" [attr.for]="getRadioId(option)">
|
|
1645
|
+
<input
|
|
1646
|
+
[id]="getRadioId(option)"
|
|
1647
|
+
type="radio"
|
|
1648
|
+
class="ngx-field__radio-input"
|
|
1649
|
+
[formControl]="$any(control)"
|
|
1650
|
+
[value]="getOptStoredVal(option)"
|
|
1651
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
1652
|
+
/>
|
|
1653
|
+
<span class="ngx-field__radio-label">{{ getOptLabel(option) }}</span>
|
|
1654
|
+
</label>
|
|
1655
|
+
}
|
|
1656
|
+
</div>
|
|
1657
|
+
@if (control.invalid && control.touched) {
|
|
1658
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
</fieldset>
|
|
1662
|
+
</div>
|
|
1646
1663
|
`, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
|
|
1647
1664
|
}
|
|
1648
1665
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RadioFieldComponent, decorators: [{
|
|
@@ -1651,37 +1668,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1651
1668
|
selector: 'ngx-radio-field',
|
|
1652
1669
|
standalone: true,
|
|
1653
1670
|
imports: [ReactiveFormsModule],
|
|
1654
|
-
template: `
|
|
1655
|
-
<div class="ngx-field ngx-field--radio"
|
|
1656
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1657
|
-
<fieldset class="ngx-field__fieldset">
|
|
1658
|
-
<legend class="ngx-field__label">{{ label }}</legend>
|
|
1659
|
-
@if (masked) {
|
|
1660
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1661
|
-
} @else if (readonly) {
|
|
1662
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getSelectedLabel() }}</span>
|
|
1663
|
-
} @else {
|
|
1664
|
-
<div class="ngx-field__radio-group">
|
|
1665
|
-
@for (option of options(); track getOptLabel(option)) {
|
|
1666
|
-
<label class="ngx-field__radio-option" [attr.for]="getRadioId(option)">
|
|
1667
|
-
<input
|
|
1668
|
-
[id]="getRadioId(option)"
|
|
1669
|
-
type="radio"
|
|
1670
|
-
class="ngx-field__radio-input"
|
|
1671
|
-
[formControl]="$any(control)"
|
|
1672
|
-
[value]="getOptStoredVal(option)"
|
|
1673
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
1674
|
-
/>
|
|
1675
|
-
<span class="ngx-field__radio-label">{{ getOptLabel(option) }}</span>
|
|
1676
|
-
</label>
|
|
1677
|
-
}
|
|
1678
|
-
</div>
|
|
1679
|
-
@if (control.invalid && control.touched) {
|
|
1680
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
</fieldset>
|
|
1684
|
-
</div>
|
|
1671
|
+
template: `
|
|
1672
|
+
<div class="ngx-field ngx-field--radio"
|
|
1673
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1674
|
+
<fieldset class="ngx-field__fieldset">
|
|
1675
|
+
<legend class="ngx-field__label">{{ label }}</legend>
|
|
1676
|
+
@if (masked) {
|
|
1677
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1678
|
+
} @else if (readonly) {
|
|
1679
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getSelectedLabel() }}</span>
|
|
1680
|
+
} @else {
|
|
1681
|
+
<div class="ngx-field__radio-group">
|
|
1682
|
+
@for (option of options(); track getOptLabel(option)) {
|
|
1683
|
+
<label class="ngx-field__radio-option" [attr.for]="getRadioId(option)">
|
|
1684
|
+
<input
|
|
1685
|
+
[id]="getRadioId(option)"
|
|
1686
|
+
type="radio"
|
|
1687
|
+
class="ngx-field__radio-input"
|
|
1688
|
+
[formControl]="$any(control)"
|
|
1689
|
+
[value]="getOptStoredVal(option)"
|
|
1690
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
1691
|
+
/>
|
|
1692
|
+
<span class="ngx-field__radio-label">{{ getOptLabel(option) }}</span>
|
|
1693
|
+
</label>
|
|
1694
|
+
}
|
|
1695
|
+
</div>
|
|
1696
|
+
@if (control.invalid && control.touched) {
|
|
1697
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
</fieldset>
|
|
1701
|
+
</div>
|
|
1685
1702
|
`,
|
|
1686
1703
|
}]
|
|
1687
1704
|
}], propDecorators: { field: [{
|
|
@@ -1751,37 +1768,37 @@ class MultiSelectFieldComponent {
|
|
|
1751
1768
|
.join(', ');
|
|
1752
1769
|
}
|
|
1753
1770
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MultiSelectFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1754
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: MultiSelectFieldComponent, isStandalone: true, selector: "ngx-multi-select-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
1755
|
-
<div class="ngx-field ngx-field--multiSelect"
|
|
1756
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1757
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
1758
|
-
@if (masked) {
|
|
1759
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1760
|
-
} @else if (readonly) {
|
|
1761
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getLabels(control.value) }}</span>
|
|
1762
|
-
} @else {
|
|
1763
|
-
<select
|
|
1764
|
-
[id]="field.id"
|
|
1765
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
1766
|
-
[formControl]="$any(control)"
|
|
1767
|
-
[compareWith]="compareFn"
|
|
1768
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
1769
|
-
multiple
|
|
1770
|
-
size="4"
|
|
1771
|
-
>
|
|
1772
|
-
@for (option of options(); track getOptLabel(option)) {
|
|
1773
|
-
@if (isObjectVal(option)) {
|
|
1774
|
-
<option [ngValue]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1775
|
-
} @else {
|
|
1776
|
-
<option [value]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
</select>
|
|
1780
|
-
@if (control.invalid && control.touched) {
|
|
1781
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1782
|
-
}
|
|
1783
|
-
}
|
|
1784
|
-
</div>
|
|
1771
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: MultiSelectFieldComponent, isStandalone: true, selector: "ngx-multi-select-field", inputs: { field: "field", language: "language", control: "control", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
1772
|
+
<div class="ngx-field ngx-field--multiSelect"
|
|
1773
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1774
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
1775
|
+
@if (masked) {
|
|
1776
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1777
|
+
} @else if (readonly) {
|
|
1778
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getLabels(control.value) }}</span>
|
|
1779
|
+
} @else {
|
|
1780
|
+
<select
|
|
1781
|
+
[id]="field.id"
|
|
1782
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
1783
|
+
[formControl]="$any(control)"
|
|
1784
|
+
[compareWith]="compareFn"
|
|
1785
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
1786
|
+
multiple
|
|
1787
|
+
size="4"
|
|
1788
|
+
>
|
|
1789
|
+
@for (option of options(); track getOptLabel(option)) {
|
|
1790
|
+
@if (isObjectVal(option)) {
|
|
1791
|
+
<option [ngValue]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1792
|
+
} @else {
|
|
1793
|
+
<option [value]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
</select>
|
|
1797
|
+
@if (control.invalid && control.touched) {
|
|
1798
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
</div>
|
|
1785
1802
|
`, 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"] }] });
|
|
1786
1803
|
}
|
|
1787
1804
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: MultiSelectFieldComponent, decorators: [{
|
|
@@ -1790,37 +1807,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
1790
1807
|
selector: 'ngx-multi-select-field',
|
|
1791
1808
|
standalone: true,
|
|
1792
1809
|
imports: [ReactiveFormsModule],
|
|
1793
|
-
template: `
|
|
1794
|
-
<div class="ngx-field ngx-field--multiSelect"
|
|
1795
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1796
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
1797
|
-
@if (masked) {
|
|
1798
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1799
|
-
} @else if (readonly) {
|
|
1800
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getLabels(control.value) }}</span>
|
|
1801
|
-
} @else {
|
|
1802
|
-
<select
|
|
1803
|
-
[id]="field.id"
|
|
1804
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
1805
|
-
[formControl]="$any(control)"
|
|
1806
|
-
[compareWith]="compareFn"
|
|
1807
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
1808
|
-
multiple
|
|
1809
|
-
size="4"
|
|
1810
|
-
>
|
|
1811
|
-
@for (option of options(); track getOptLabel(option)) {
|
|
1812
|
-
@if (isObjectVal(option)) {
|
|
1813
|
-
<option [ngValue]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1814
|
-
} @else {
|
|
1815
|
-
<option [value]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
</select>
|
|
1819
|
-
@if (control.invalid && control.touched) {
|
|
1820
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
</div>
|
|
1810
|
+
template: `
|
|
1811
|
+
<div class="ngx-field ngx-field--multiSelect"
|
|
1812
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
1813
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
1814
|
+
@if (masked) {
|
|
1815
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
1816
|
+
} @else if (readonly) {
|
|
1817
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getLabels(control.value) }}</span>
|
|
1818
|
+
} @else {
|
|
1819
|
+
<select
|
|
1820
|
+
[id]="field.id"
|
|
1821
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
1822
|
+
[formControl]="$any(control)"
|
|
1823
|
+
[compareWith]="compareFn"
|
|
1824
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
1825
|
+
multiple
|
|
1826
|
+
size="4"
|
|
1827
|
+
>
|
|
1828
|
+
@for (option of options(); track getOptLabel(option)) {
|
|
1829
|
+
@if (isObjectVal(option)) {
|
|
1830
|
+
<option [ngValue]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1831
|
+
} @else {
|
|
1832
|
+
<option [value]="getOptStoredVal(option)">{{ getOptLabel(option) }}</option>
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
</select>
|
|
1836
|
+
@if (control.invalid && control.touched) {
|
|
1837
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
</div>
|
|
1824
1841
|
`,
|
|
1825
1842
|
}]
|
|
1826
1843
|
}], propDecorators: { field: [{
|
|
@@ -2259,39 +2276,39 @@ class EntityRefFieldComponent {
|
|
|
2259
2276
|
});
|
|
2260
2277
|
}
|
|
2261
2278
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityRefFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2262
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EntityRefFieldComponent, isStandalone: true, selector: "ngx-entity-ref-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
2263
|
-
<div class="ngx-field ngx-field--entity-ref"
|
|
2264
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2265
|
-
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
2266
|
-
@if (masked) {
|
|
2267
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
2268
|
-
} @else if (readonly) {
|
|
2269
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getLabel(control.value) }}</span>
|
|
2270
|
-
} @else {
|
|
2271
|
-
@if (loading()) {
|
|
2272
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-loading'" role="status">Loading…</span>
|
|
2273
|
-
} @else {
|
|
2274
|
-
<select
|
|
2275
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
2276
|
-
[id]="'field-' + field.id"
|
|
2277
|
-
[formControl]="$any(control)"
|
|
2278
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
2279
|
-
(change)="onSelectionChange()"
|
|
2280
|
-
>
|
|
2281
|
-
<option value="">{{ placeholder || 'Select...' }}</option>
|
|
2282
|
-
@for (option of options(); track option.value) {
|
|
2283
|
-
<option [value]="option.value">{{ option.label }}</option>
|
|
2284
|
-
}
|
|
2285
|
-
</select>
|
|
2286
|
-
@if (awaitingParent()) {
|
|
2287
|
-
<span class="ngx-field__hint" [attr.data-testid]="'field-' + field.id + '-hint'">Select {{ parentFieldId }} first.</span>
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
@if (control.invalid && control.touched) {
|
|
2291
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
2292
|
-
}
|
|
2293
|
-
}
|
|
2294
|
-
</div>
|
|
2279
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: EntityRefFieldComponent, isStandalone: true, selector: "ngx-entity-ref-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
2280
|
+
<div class="ngx-field ngx-field--entity-ref"
|
|
2281
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2282
|
+
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
2283
|
+
@if (masked) {
|
|
2284
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
2285
|
+
} @else if (readonly) {
|
|
2286
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getLabel(control.value) }}</span>
|
|
2287
|
+
} @else {
|
|
2288
|
+
@if (loading()) {
|
|
2289
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-loading'" role="status">Loading…</span>
|
|
2290
|
+
} @else {
|
|
2291
|
+
<select
|
|
2292
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
2293
|
+
[id]="'field-' + field.id"
|
|
2294
|
+
[formControl]="$any(control)"
|
|
2295
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
2296
|
+
(change)="onSelectionChange()"
|
|
2297
|
+
>
|
|
2298
|
+
<option value="">{{ placeholder || 'Select...' }}</option>
|
|
2299
|
+
@for (option of options(); track option.value) {
|
|
2300
|
+
<option [value]="option.value">{{ option.label }}</option>
|
|
2301
|
+
}
|
|
2302
|
+
</select>
|
|
2303
|
+
@if (awaitingParent()) {
|
|
2304
|
+
<span class="ngx-field__hint" [attr.data-testid]="'field-' + field.id + '-hint'">Select {{ parentFieldId }} first.</span>
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
@if (control.invalid && control.touched) {
|
|
2308
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
</div>
|
|
2295
2312
|
`, 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"] }] });
|
|
2296
2313
|
}
|
|
2297
2314
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: EntityRefFieldComponent, decorators: [{
|
|
@@ -2300,39 +2317,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
2300
2317
|
selector: 'ngx-entity-ref-field',
|
|
2301
2318
|
standalone: true,
|
|
2302
2319
|
imports: [ReactiveFormsModule],
|
|
2303
|
-
template: `
|
|
2304
|
-
<div class="ngx-field ngx-field--entity-ref"
|
|
2305
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2306
|
-
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
2307
|
-
@if (masked) {
|
|
2308
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
2309
|
-
} @else if (readonly) {
|
|
2310
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getLabel(control.value) }}</span>
|
|
2311
|
-
} @else {
|
|
2312
|
-
@if (loading()) {
|
|
2313
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-loading'" role="status">Loading…</span>
|
|
2314
|
-
} @else {
|
|
2315
|
-
<select
|
|
2316
|
-
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
2317
|
-
[id]="'field-' + field.id"
|
|
2318
|
-
[formControl]="$any(control)"
|
|
2319
|
-
[attr.disabled]="field.disabled ? true : null"
|
|
2320
|
-
(change)="onSelectionChange()"
|
|
2321
|
-
>
|
|
2322
|
-
<option value="">{{ placeholder || 'Select...' }}</option>
|
|
2323
|
-
@for (option of options(); track option.value) {
|
|
2324
|
-
<option [value]="option.value">{{ option.label }}</option>
|
|
2325
|
-
}
|
|
2326
|
-
</select>
|
|
2327
|
-
@if (awaitingParent()) {
|
|
2328
|
-
<span class="ngx-field__hint" [attr.data-testid]="'field-' + field.id + '-hint'">Select {{ parentFieldId }} first.</span>
|
|
2329
|
-
}
|
|
2330
|
-
}
|
|
2331
|
-
@if (control.invalid && control.touched) {
|
|
2332
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
|
-
</div>
|
|
2320
|
+
template: `
|
|
2321
|
+
<div class="ngx-field ngx-field--entity-ref"
|
|
2322
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2323
|
+
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
2324
|
+
@if (masked) {
|
|
2325
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
2326
|
+
} @else if (readonly) {
|
|
2327
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">{{ getLabel(control.value) }}</span>
|
|
2328
|
+
} @else {
|
|
2329
|
+
@if (loading()) {
|
|
2330
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-loading'" role="status">Loading…</span>
|
|
2331
|
+
} @else {
|
|
2332
|
+
<select
|
|
2333
|
+
class="ngx-field__input" [attr.data-testid]="'field-' + field.id + '-input'"
|
|
2334
|
+
[id]="'field-' + field.id"
|
|
2335
|
+
[formControl]="$any(control)"
|
|
2336
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
2337
|
+
(change)="onSelectionChange()"
|
|
2338
|
+
>
|
|
2339
|
+
<option value="">{{ placeholder || 'Select...' }}</option>
|
|
2340
|
+
@for (option of options(); track option.value) {
|
|
2341
|
+
<option [value]="option.value">{{ option.label }}</option>
|
|
2342
|
+
}
|
|
2343
|
+
</select>
|
|
2344
|
+
@if (awaitingParent()) {
|
|
2345
|
+
<span class="ngx-field__hint" [attr.data-testid]="'field-' + field.id + '-hint'">Select {{ parentFieldId }} first.</span>
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
@if (control.invalid && control.touched) {
|
|
2349
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
</div>
|
|
2336
2353
|
`,
|
|
2337
2354
|
}]
|
|
2338
2355
|
}], propDecorators: { field: [{
|
|
@@ -2557,56 +2574,56 @@ class GroupFieldComponent {
|
|
|
2557
2574
|
return group?.get(childId);
|
|
2558
2575
|
}
|
|
2559
2576
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: GroupFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2560
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: GroupFieldComponent, isStandalone: true, selector: "ngx-group-field", inputs: { field: "field", control: "control", config: "config", language: "language", readonly: "readonly", masked: "masked", userRoles: "userRoles" }, ngImport: i0, template: `
|
|
2561
|
-
<fieldset class="ngx-field ngx-field--group"
|
|
2562
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2563
|
-
<legend class="ngx-field__legend">{{ label }}</legend>
|
|
2564
|
-
@if (masked) {
|
|
2565
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
2566
|
-
} @else {
|
|
2567
|
-
<div class="ngx-field__group-children">
|
|
2568
|
-
@for (child of field.children ?? []; track child.id) {
|
|
2569
|
-
@if (child.visibility !== false) {
|
|
2570
|
-
<ngx-dynamic-field
|
|
2571
|
-
[field]="child"
|
|
2572
|
-
[control]="getChildControl(child.id)"
|
|
2573
|
-
[config]="config"
|
|
2574
|
-
[language]="language"
|
|
2575
|
-
[readonly]="readonly || !!child.readonly"
|
|
2576
|
-
[userRoles]="userRoles"
|
|
2577
|
-
/>
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
</div>
|
|
2581
|
-
}
|
|
2582
|
-
</fieldset>
|
|
2577
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: GroupFieldComponent, isStandalone: true, selector: "ngx-group-field", inputs: { field: "field", control: "control", config: "config", language: "language", readonly: "readonly", masked: "masked", userRoles: "userRoles" }, ngImport: i0, template: `
|
|
2578
|
+
<fieldset class="ngx-field ngx-field--group"
|
|
2579
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2580
|
+
<legend class="ngx-field__legend">{{ label }}</legend>
|
|
2581
|
+
@if (masked) {
|
|
2582
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
2583
|
+
} @else {
|
|
2584
|
+
<div class="ngx-field__group-children">
|
|
2585
|
+
@for (child of field.children ?? []; track child.id) {
|
|
2586
|
+
@if (child.visibility !== false) {
|
|
2587
|
+
<ngx-dynamic-field
|
|
2588
|
+
[field]="child"
|
|
2589
|
+
[control]="getChildControl(child.id)"
|
|
2590
|
+
[config]="config"
|
|
2591
|
+
[language]="language"
|
|
2592
|
+
[readonly]="readonly || !!child.readonly"
|
|
2593
|
+
[userRoles]="userRoles"
|
|
2594
|
+
/>
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
</div>
|
|
2598
|
+
}
|
|
2599
|
+
</fieldset>
|
|
2583
2600
|
`, 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"] }] });
|
|
2584
2601
|
}
|
|
2585
2602
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: GroupFieldComponent, decorators: [{
|
|
2586
2603
|
type: Component,
|
|
2587
|
-
args: [{ selector: 'ngx-group-field', standalone: true, imports: [ReactiveFormsModule, forwardRef(() => DynamicFieldComponent)], template: `
|
|
2588
|
-
<fieldset class="ngx-field ngx-field--group"
|
|
2589
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2590
|
-
<legend class="ngx-field__legend">{{ label }}</legend>
|
|
2591
|
-
@if (masked) {
|
|
2592
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
2593
|
-
} @else {
|
|
2594
|
-
<div class="ngx-field__group-children">
|
|
2595
|
-
@for (child of field.children ?? []; track child.id) {
|
|
2596
|
-
@if (child.visibility !== false) {
|
|
2597
|
-
<ngx-dynamic-field
|
|
2598
|
-
[field]="child"
|
|
2599
|
-
[control]="getChildControl(child.id)"
|
|
2600
|
-
[config]="config"
|
|
2601
|
-
[language]="language"
|
|
2602
|
-
[readonly]="readonly || !!child.readonly"
|
|
2603
|
-
[userRoles]="userRoles"
|
|
2604
|
-
/>
|
|
2605
|
-
}
|
|
2606
|
-
}
|
|
2607
|
-
</div>
|
|
2608
|
-
}
|
|
2609
|
-
</fieldset>
|
|
2604
|
+
args: [{ selector: 'ngx-group-field', standalone: true, imports: [ReactiveFormsModule, forwardRef(() => DynamicFieldComponent)], template: `
|
|
2605
|
+
<fieldset class="ngx-field ngx-field--group"
|
|
2606
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2607
|
+
<legend class="ngx-field__legend">{{ label }}</legend>
|
|
2608
|
+
@if (masked) {
|
|
2609
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
2610
|
+
} @else {
|
|
2611
|
+
<div class="ngx-field__group-children">
|
|
2612
|
+
@for (child of field.children ?? []; track child.id) {
|
|
2613
|
+
@if (child.visibility !== false) {
|
|
2614
|
+
<ngx-dynamic-field
|
|
2615
|
+
[field]="child"
|
|
2616
|
+
[control]="getChildControl(child.id)"
|
|
2617
|
+
[config]="config"
|
|
2618
|
+
[language]="language"
|
|
2619
|
+
[readonly]="readonly || !!child.readonly"
|
|
2620
|
+
[userRoles]="userRoles"
|
|
2621
|
+
/>
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
</div>
|
|
2625
|
+
}
|
|
2626
|
+
</fieldset>
|
|
2610
2627
|
`, 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"] }]
|
|
2611
2628
|
}], propDecorators: { field: [{
|
|
2612
2629
|
type: Input
|
|
@@ -2676,6 +2693,17 @@ class ValidatorRegistryService {
|
|
|
2676
2693
|
fnList.push(Validators.maxLength(config.maxLength));
|
|
2677
2694
|
if (config.pattern)
|
|
2678
2695
|
fnList.push(Validators.pattern(config.pattern));
|
|
2696
|
+
if (config.email)
|
|
2697
|
+
fnList.push(Validators.email);
|
|
2698
|
+
// Named validators from the consumer registry. This branch previously hardcoded the
|
|
2699
|
+
// built-ins and consulted the registry only for the `string[]` form of this config, so
|
|
2700
|
+
// a validator the consumer had registered could not be named from a typed schema
|
|
2701
|
+
// without casting it to `any`.
|
|
2702
|
+
for (const key of config.custom ?? []) {
|
|
2703
|
+
const fn = this.resolve(key);
|
|
2704
|
+
if (fn)
|
|
2705
|
+
fnList.push(fn);
|
|
2706
|
+
}
|
|
2679
2707
|
return fnList;
|
|
2680
2708
|
}
|
|
2681
2709
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ValidatorRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -2719,110 +2747,110 @@ class ArrayFieldComponent {
|
|
|
2719
2747
|
this.formArray.removeAt(index);
|
|
2720
2748
|
}
|
|
2721
2749
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ArrayFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2722
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: ArrayFieldComponent, isStandalone: true, selector: "ngx-array-field", inputs: { field: "field", control: "control", config: "config", language: "language", readonly: "readonly", masked: "masked", userRoles: "userRoles" }, ngImport: i0, template: `
|
|
2723
|
-
<div class="ngx-field ngx-field--array"
|
|
2724
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2725
|
-
<div class="ngx-field__array-header">
|
|
2726
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
2727
|
-
@if (!readonly && !masked) {
|
|
2728
|
-
<button type="button" class="ngx-field__array-add-btn"
|
|
2729
|
-
[attr.data-testid]="'field-' + field.id + '-add'" (click)="addItem()">
|
|
2730
|
-
+ Add Item
|
|
2731
|
-
</button>
|
|
2732
|
-
}
|
|
2733
|
-
</div>
|
|
2734
|
-
|
|
2735
|
-
@if (masked) {
|
|
2736
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
2737
|
-
} @else {
|
|
2738
|
-
<div class="ngx-field__array-list">
|
|
2739
|
-
@for (itemGroup of formArray.controls; track idx; let idx = $index) {
|
|
2740
|
-
<div class="ngx-field__array-item" [attr.data-testid]="'field-' + field.id + '-row'">
|
|
2741
|
-
<div class="ngx-field__array-item-header">
|
|
2742
|
-
<span class="ngx-field__array-item-title">Item #{{ idx + 1 }}</span>
|
|
2743
|
-
@if (!readonly) {
|
|
2744
|
-
<button type="button" class="ngx-field__array-remove-btn"
|
|
2745
|
-
[attr.data-testid]="'field-' + field.id + '-remove-' + idx" (click)="removeItem(idx)">
|
|
2746
|
-
Remove
|
|
2747
|
-
</button>
|
|
2748
|
-
}
|
|
2749
|
-
</div>
|
|
2750
|
-
<div class="ngx-field__array-item-fields">
|
|
2751
|
-
@for (child of field.children ?? []; track child.id) {
|
|
2752
|
-
@if (child.visibility !== false) {
|
|
2753
|
-
<ngx-dynamic-field
|
|
2754
|
-
[field]="child"
|
|
2755
|
-
[control]="getItemChildControl(itemGroup, child.id)"
|
|
2756
|
-
[config]="config"
|
|
2757
|
-
[language]="language"
|
|
2758
|
-
[readonly]="readonly || !!child.readonly"
|
|
2759
|
-
[userRoles]="userRoles"
|
|
2760
|
-
/>
|
|
2761
|
-
}
|
|
2762
|
-
}
|
|
2763
|
-
</div>
|
|
2764
|
-
</div>
|
|
2765
|
-
}
|
|
2766
|
-
@if (formArray.controls.length === 0) {
|
|
2767
|
-
<div class="ngx-field__array-empty" [attr.data-testid]="'field-' + field.id + '-empty'">No items added yet.</div>
|
|
2768
|
-
}
|
|
2769
|
-
</div>
|
|
2770
|
-
}
|
|
2771
|
-
</div>
|
|
2750
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: ArrayFieldComponent, isStandalone: true, selector: "ngx-array-field", inputs: { field: "field", control: "control", config: "config", language: "language", readonly: "readonly", masked: "masked", userRoles: "userRoles" }, ngImport: i0, template: `
|
|
2751
|
+
<div class="ngx-field ngx-field--array"
|
|
2752
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2753
|
+
<div class="ngx-field__array-header">
|
|
2754
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
2755
|
+
@if (!readonly && !masked) {
|
|
2756
|
+
<button type="button" class="ngx-field__array-add-btn"
|
|
2757
|
+
[attr.data-testid]="'field-' + field.id + '-add'" (click)="addItem()">
|
|
2758
|
+
+ Add Item
|
|
2759
|
+
</button>
|
|
2760
|
+
}
|
|
2761
|
+
</div>
|
|
2762
|
+
|
|
2763
|
+
@if (masked) {
|
|
2764
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
2765
|
+
} @else {
|
|
2766
|
+
<div class="ngx-field__array-list">
|
|
2767
|
+
@for (itemGroup of formArray.controls; track idx; let idx = $index) {
|
|
2768
|
+
<div class="ngx-field__array-item" [attr.data-testid]="'field-' + field.id + '-row'">
|
|
2769
|
+
<div class="ngx-field__array-item-header">
|
|
2770
|
+
<span class="ngx-field__array-item-title">Item #{{ idx + 1 }}</span>
|
|
2771
|
+
@if (!readonly) {
|
|
2772
|
+
<button type="button" class="ngx-field__array-remove-btn"
|
|
2773
|
+
[attr.data-testid]="'field-' + field.id + '-remove-' + idx" (click)="removeItem(idx)">
|
|
2774
|
+
Remove
|
|
2775
|
+
</button>
|
|
2776
|
+
}
|
|
2777
|
+
</div>
|
|
2778
|
+
<div class="ngx-field__array-item-fields">
|
|
2779
|
+
@for (child of field.children ?? []; track child.id) {
|
|
2780
|
+
@if (child.visibility !== false) {
|
|
2781
|
+
<ngx-dynamic-field
|
|
2782
|
+
[field]="child"
|
|
2783
|
+
[control]="getItemChildControl(itemGroup, child.id)"
|
|
2784
|
+
[config]="config"
|
|
2785
|
+
[language]="language"
|
|
2786
|
+
[readonly]="readonly || !!child.readonly"
|
|
2787
|
+
[userRoles]="userRoles"
|
|
2788
|
+
/>
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
</div>
|
|
2792
|
+
</div>
|
|
2793
|
+
}
|
|
2794
|
+
@if (formArray.controls.length === 0) {
|
|
2795
|
+
<div class="ngx-field__array-empty" [attr.data-testid]="'field-' + field.id + '-empty'">No items added yet.</div>
|
|
2796
|
+
}
|
|
2797
|
+
</div>
|
|
2798
|
+
}
|
|
2799
|
+
</div>
|
|
2772
2800
|
`, 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"] }] });
|
|
2773
2801
|
}
|
|
2774
2802
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ArrayFieldComponent, decorators: [{
|
|
2775
2803
|
type: Component,
|
|
2776
|
-
args: [{ selector: 'ngx-array-field', standalone: true, imports: [ReactiveFormsModule, forwardRef(() => DynamicFieldComponent)], template: `
|
|
2777
|
-
<div class="ngx-field ngx-field--array"
|
|
2778
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2779
|
-
<div class="ngx-field__array-header">
|
|
2780
|
-
<label class="ngx-field__label">{{ label }}</label>
|
|
2781
|
-
@if (!readonly && !masked) {
|
|
2782
|
-
<button type="button" class="ngx-field__array-add-btn"
|
|
2783
|
-
[attr.data-testid]="'field-' + field.id + '-add'" (click)="addItem()">
|
|
2784
|
-
+ Add Item
|
|
2785
|
-
</button>
|
|
2786
|
-
}
|
|
2787
|
-
</div>
|
|
2788
|
-
|
|
2789
|
-
@if (masked) {
|
|
2790
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
2791
|
-
} @else {
|
|
2792
|
-
<div class="ngx-field__array-list">
|
|
2793
|
-
@for (itemGroup of formArray.controls; track idx; let idx = $index) {
|
|
2794
|
-
<div class="ngx-field__array-item" [attr.data-testid]="'field-' + field.id + '-row'">
|
|
2795
|
-
<div class="ngx-field__array-item-header">
|
|
2796
|
-
<span class="ngx-field__array-item-title">Item #{{ idx + 1 }}</span>
|
|
2797
|
-
@if (!readonly) {
|
|
2798
|
-
<button type="button" class="ngx-field__array-remove-btn"
|
|
2799
|
-
[attr.data-testid]="'field-' + field.id + '-remove-' + idx" (click)="removeItem(idx)">
|
|
2800
|
-
Remove
|
|
2801
|
-
</button>
|
|
2802
|
-
}
|
|
2803
|
-
</div>
|
|
2804
|
-
<div class="ngx-field__array-item-fields">
|
|
2805
|
-
@for (child of field.children ?? []; track child.id) {
|
|
2806
|
-
@if (child.visibility !== false) {
|
|
2807
|
-
<ngx-dynamic-field
|
|
2808
|
-
[field]="child"
|
|
2809
|
-
[control]="getItemChildControl(itemGroup, child.id)"
|
|
2810
|
-
[config]="config"
|
|
2811
|
-
[language]="language"
|
|
2812
|
-
[readonly]="readonly || !!child.readonly"
|
|
2813
|
-
[userRoles]="userRoles"
|
|
2814
|
-
/>
|
|
2815
|
-
}
|
|
2816
|
-
}
|
|
2817
|
-
</div>
|
|
2818
|
-
</div>
|
|
2819
|
-
}
|
|
2820
|
-
@if (formArray.controls.length === 0) {
|
|
2821
|
-
<div class="ngx-field__array-empty" [attr.data-testid]="'field-' + field.id + '-empty'">No items added yet.</div>
|
|
2822
|
-
}
|
|
2823
|
-
</div>
|
|
2824
|
-
}
|
|
2825
|
-
</div>
|
|
2804
|
+
args: [{ selector: 'ngx-array-field', standalone: true, imports: [ReactiveFormsModule, forwardRef(() => DynamicFieldComponent)], template: `
|
|
2805
|
+
<div class="ngx-field ngx-field--array"
|
|
2806
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2807
|
+
<div class="ngx-field__array-header">
|
|
2808
|
+
<label class="ngx-field__label">{{ label }}</label>
|
|
2809
|
+
@if (!readonly && !masked) {
|
|
2810
|
+
<button type="button" class="ngx-field__array-add-btn"
|
|
2811
|
+
[attr.data-testid]="'field-' + field.id + '-add'" (click)="addItem()">
|
|
2812
|
+
+ Add Item
|
|
2813
|
+
</button>
|
|
2814
|
+
}
|
|
2815
|
+
</div>
|
|
2816
|
+
|
|
2817
|
+
@if (masked) {
|
|
2818
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
2819
|
+
} @else {
|
|
2820
|
+
<div class="ngx-field__array-list">
|
|
2821
|
+
@for (itemGroup of formArray.controls; track idx; let idx = $index) {
|
|
2822
|
+
<div class="ngx-field__array-item" [attr.data-testid]="'field-' + field.id + '-row'">
|
|
2823
|
+
<div class="ngx-field__array-item-header">
|
|
2824
|
+
<span class="ngx-field__array-item-title">Item #{{ idx + 1 }}</span>
|
|
2825
|
+
@if (!readonly) {
|
|
2826
|
+
<button type="button" class="ngx-field__array-remove-btn"
|
|
2827
|
+
[attr.data-testid]="'field-' + field.id + '-remove-' + idx" (click)="removeItem(idx)">
|
|
2828
|
+
Remove
|
|
2829
|
+
</button>
|
|
2830
|
+
}
|
|
2831
|
+
</div>
|
|
2832
|
+
<div class="ngx-field__array-item-fields">
|
|
2833
|
+
@for (child of field.children ?? []; track child.id) {
|
|
2834
|
+
@if (child.visibility !== false) {
|
|
2835
|
+
<ngx-dynamic-field
|
|
2836
|
+
[field]="child"
|
|
2837
|
+
[control]="getItemChildControl(itemGroup, child.id)"
|
|
2838
|
+
[config]="config"
|
|
2839
|
+
[language]="language"
|
|
2840
|
+
[readonly]="readonly || !!child.readonly"
|
|
2841
|
+
[userRoles]="userRoles"
|
|
2842
|
+
/>
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
</div>
|
|
2846
|
+
</div>
|
|
2847
|
+
}
|
|
2848
|
+
@if (formArray.controls.length === 0) {
|
|
2849
|
+
<div class="ngx-field__array-empty" [attr.data-testid]="'field-' + field.id + '-empty'">No items added yet.</div>
|
|
2850
|
+
}
|
|
2851
|
+
</div>
|
|
2852
|
+
}
|
|
2853
|
+
</div>
|
|
2826
2854
|
`, 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"] }]
|
|
2827
2855
|
}], propDecorators: { field: [{
|
|
2828
2856
|
type: Input
|
|
@@ -2980,50 +3008,50 @@ class ImageFieldComponent {
|
|
|
2980
3008
|
this.uploads.revokePreviewUrl(this.previewCache);
|
|
2981
3009
|
}
|
|
2982
3010
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ImageFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2983
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: ImageFieldComponent, isStandalone: true, selector: "ngx-image-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
2984
|
-
<div class="ngx-field ngx-field--image"
|
|
2985
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
2986
|
-
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
2987
|
-
@if (masked) {
|
|
2988
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
2989
|
-
} @else {
|
|
2990
|
-
<div class="ngx-field__image-wrap">
|
|
2991
|
-
@if (previewUrl()) {
|
|
2992
|
-
<img class="ngx-field__image-preview" [src]="previewUrl()" [alt]="label" />
|
|
2993
|
-
} @else {
|
|
2994
|
-
<div class="ngx-field__image-placeholder">
|
|
2995
|
-
<span>📷</span>
|
|
2996
|
-
<span>No image</span>
|
|
2997
|
-
</div>
|
|
2998
|
-
}
|
|
2999
|
-
@if (!readonly) {
|
|
3000
|
-
<div class="ngx-field__image-actions">
|
|
3001
|
-
<label class="ngx-field__upload-btn" [class.ngx-field__upload-btn--loading]="uploading()">
|
|
3002
|
-
@if (uploading()) {
|
|
3003
|
-
<span>Uploading…</span>
|
|
3004
|
-
} @else {
|
|
3005
|
-
<span>{{ previewUrl() ? 'Change' : 'Upload' }}</span>
|
|
3006
|
-
<input
|
|
3007
|
-
type="file"
|
|
3008
|
-
class="ngx-field__file-input"
|
|
3009
|
-
[id]="'field-' + field.id"
|
|
3010
|
-
accept="image/*"
|
|
3011
|
-
[disabled]="field.disabled || uploading()"
|
|
3012
|
-
(change)="onFileSelect($any($event.target).files[0])"
|
|
3013
|
-
/>
|
|
3014
|
-
}
|
|
3015
|
-
</label>
|
|
3016
|
-
@if (previewUrl()) {
|
|
3017
|
-
<button type="button" class="ngx-field__remove-btn" (click)="remove()">Remove</button>
|
|
3018
|
-
}
|
|
3019
|
-
</div>
|
|
3020
|
-
}
|
|
3021
|
-
</div>
|
|
3022
|
-
@if (uploadError()) {
|
|
3023
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">{{ uploadError() }}</span>
|
|
3024
|
-
}
|
|
3025
|
-
}
|
|
3026
|
-
</div>
|
|
3011
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: ImageFieldComponent, isStandalone: true, selector: "ngx-image-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
3012
|
+
<div class="ngx-field ngx-field--image"
|
|
3013
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
3014
|
+
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
3015
|
+
@if (masked) {
|
|
3016
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
3017
|
+
} @else {
|
|
3018
|
+
<div class="ngx-field__image-wrap">
|
|
3019
|
+
@if (previewUrl()) {
|
|
3020
|
+
<img class="ngx-field__image-preview" [src]="previewUrl()" [alt]="label" />
|
|
3021
|
+
} @else {
|
|
3022
|
+
<div class="ngx-field__image-placeholder">
|
|
3023
|
+
<span>📷</span>
|
|
3024
|
+
<span>No image</span>
|
|
3025
|
+
</div>
|
|
3026
|
+
}
|
|
3027
|
+
@if (!readonly) {
|
|
3028
|
+
<div class="ngx-field__image-actions">
|
|
3029
|
+
<label class="ngx-field__upload-btn" [class.ngx-field__upload-btn--loading]="uploading()">
|
|
3030
|
+
@if (uploading()) {
|
|
3031
|
+
<span>Uploading…</span>
|
|
3032
|
+
} @else {
|
|
3033
|
+
<span>{{ previewUrl() ? 'Change' : 'Upload' }}</span>
|
|
3034
|
+
<input
|
|
3035
|
+
type="file"
|
|
3036
|
+
class="ngx-field__file-input"
|
|
3037
|
+
[id]="'field-' + field.id"
|
|
3038
|
+
accept="image/*"
|
|
3039
|
+
[disabled]="field.disabled || uploading()"
|
|
3040
|
+
(change)="onFileSelect($any($event.target).files[0])"
|
|
3041
|
+
/>
|
|
3042
|
+
}
|
|
3043
|
+
</label>
|
|
3044
|
+
@if (previewUrl()) {
|
|
3045
|
+
<button type="button" class="ngx-field__remove-btn" (click)="remove()">Remove</button>
|
|
3046
|
+
}
|
|
3047
|
+
</div>
|
|
3048
|
+
}
|
|
3049
|
+
</div>
|
|
3050
|
+
@if (uploadError()) {
|
|
3051
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">{{ uploadError() }}</span>
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
</div>
|
|
3027
3055
|
`, isInline: true });
|
|
3028
3056
|
}
|
|
3029
3057
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ImageFieldComponent, decorators: [{
|
|
@@ -3032,50 +3060,50 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3032
3060
|
selector: 'ngx-image-field',
|
|
3033
3061
|
standalone: true,
|
|
3034
3062
|
imports: [],
|
|
3035
|
-
template: `
|
|
3036
|
-
<div class="ngx-field ngx-field--image"
|
|
3037
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
3038
|
-
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
3039
|
-
@if (masked) {
|
|
3040
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
3041
|
-
} @else {
|
|
3042
|
-
<div class="ngx-field__image-wrap">
|
|
3043
|
-
@if (previewUrl()) {
|
|
3044
|
-
<img class="ngx-field__image-preview" [src]="previewUrl()" [alt]="label" />
|
|
3045
|
-
} @else {
|
|
3046
|
-
<div class="ngx-field__image-placeholder">
|
|
3047
|
-
<span>📷</span>
|
|
3048
|
-
<span>No image</span>
|
|
3049
|
-
</div>
|
|
3050
|
-
}
|
|
3051
|
-
@if (!readonly) {
|
|
3052
|
-
<div class="ngx-field__image-actions">
|
|
3053
|
-
<label class="ngx-field__upload-btn" [class.ngx-field__upload-btn--loading]="uploading()">
|
|
3054
|
-
@if (uploading()) {
|
|
3055
|
-
<span>Uploading…</span>
|
|
3056
|
-
} @else {
|
|
3057
|
-
<span>{{ previewUrl() ? 'Change' : 'Upload' }}</span>
|
|
3058
|
-
<input
|
|
3059
|
-
type="file"
|
|
3060
|
-
class="ngx-field__file-input"
|
|
3061
|
-
[id]="'field-' + field.id"
|
|
3062
|
-
accept="image/*"
|
|
3063
|
-
[disabled]="field.disabled || uploading()"
|
|
3064
|
-
(change)="onFileSelect($any($event.target).files[0])"
|
|
3065
|
-
/>
|
|
3066
|
-
}
|
|
3067
|
-
</label>
|
|
3068
|
-
@if (previewUrl()) {
|
|
3069
|
-
<button type="button" class="ngx-field__remove-btn" (click)="remove()">Remove</button>
|
|
3070
|
-
}
|
|
3071
|
-
</div>
|
|
3072
|
-
}
|
|
3073
|
-
</div>
|
|
3074
|
-
@if (uploadError()) {
|
|
3075
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">{{ uploadError() }}</span>
|
|
3076
|
-
}
|
|
3077
|
-
}
|
|
3078
|
-
</div>
|
|
3063
|
+
template: `
|
|
3064
|
+
<div class="ngx-field ngx-field--image"
|
|
3065
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
3066
|
+
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
3067
|
+
@if (masked) {
|
|
3068
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
3069
|
+
} @else {
|
|
3070
|
+
<div class="ngx-field__image-wrap">
|
|
3071
|
+
@if (previewUrl()) {
|
|
3072
|
+
<img class="ngx-field__image-preview" [src]="previewUrl()" [alt]="label" />
|
|
3073
|
+
} @else {
|
|
3074
|
+
<div class="ngx-field__image-placeholder">
|
|
3075
|
+
<span>📷</span>
|
|
3076
|
+
<span>No image</span>
|
|
3077
|
+
</div>
|
|
3078
|
+
}
|
|
3079
|
+
@if (!readonly) {
|
|
3080
|
+
<div class="ngx-field__image-actions">
|
|
3081
|
+
<label class="ngx-field__upload-btn" [class.ngx-field__upload-btn--loading]="uploading()">
|
|
3082
|
+
@if (uploading()) {
|
|
3083
|
+
<span>Uploading…</span>
|
|
3084
|
+
} @else {
|
|
3085
|
+
<span>{{ previewUrl() ? 'Change' : 'Upload' }}</span>
|
|
3086
|
+
<input
|
|
3087
|
+
type="file"
|
|
3088
|
+
class="ngx-field__file-input"
|
|
3089
|
+
[id]="'field-' + field.id"
|
|
3090
|
+
accept="image/*"
|
|
3091
|
+
[disabled]="field.disabled || uploading()"
|
|
3092
|
+
(change)="onFileSelect($any($event.target).files[0])"
|
|
3093
|
+
/>
|
|
3094
|
+
}
|
|
3095
|
+
</label>
|
|
3096
|
+
@if (previewUrl()) {
|
|
3097
|
+
<button type="button" class="ngx-field__remove-btn" (click)="remove()">Remove</button>
|
|
3098
|
+
}
|
|
3099
|
+
</div>
|
|
3100
|
+
}
|
|
3101
|
+
</div>
|
|
3102
|
+
@if (uploadError()) {
|
|
3103
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">{{ uploadError() }}</span>
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
</div>
|
|
3079
3107
|
`,
|
|
3080
3108
|
}]
|
|
3081
3109
|
}], propDecorators: { field: [{
|
|
@@ -3136,48 +3164,48 @@ class FileFieldComponent {
|
|
|
3136
3164
|
this.control.markAsTouched();
|
|
3137
3165
|
}
|
|
3138
3166
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FileFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3139
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: FileFieldComponent, isStandalone: true, selector: "ngx-file-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
3140
|
-
<div class="ngx-field ngx-field--file"
|
|
3141
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
3142
|
-
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
3143
|
-
@if (masked) {
|
|
3144
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
3145
|
-
} @else if (readonly) {
|
|
3146
|
-
@if (fileUrl()) {
|
|
3147
|
-
<a class="ngx-field__file-link" [href]="fileUrl()!" target="_blank" rel="noopener">
|
|
3148
|
-
📎 {{ fileName() || 'Download file' }}
|
|
3149
|
-
</a>
|
|
3150
|
-
} @else {
|
|
3151
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">—</span>
|
|
3152
|
-
}
|
|
3153
|
-
} @else {
|
|
3154
|
-
<div class="ngx-field__file-wrap">
|
|
3155
|
-
@if (fileName()) {
|
|
3156
|
-
<div class="ngx-field__file-selected">
|
|
3157
|
-
<span class="ngx-field__file-name">📎 {{ fileName() }}</span>
|
|
3158
|
-
<button type="button" class="ngx-field__remove-btn" (click)="remove()" aria-label="Remove file">✕</button>
|
|
3159
|
-
</div>
|
|
3160
|
-
}
|
|
3161
|
-
<label class="ngx-field__upload-btn" [class.ngx-field__upload-btn--loading]="uploading()">
|
|
3162
|
-
@if (uploading()) {
|
|
3163
|
-
<span>Uploading…</span>
|
|
3164
|
-
} @else {
|
|
3165
|
-
<span>{{ fileName() ? 'Replace file' : 'Choose file' }}</span>
|
|
3166
|
-
<input
|
|
3167
|
-
type="file"
|
|
3168
|
-
class="ngx-field__file-input"
|
|
3169
|
-
[id]="'field-' + field.id"
|
|
3170
|
-
[disabled]="field.disabled || uploading()"
|
|
3171
|
-
(change)="onFileSelect($any($event.target).files[0])"
|
|
3172
|
-
/>
|
|
3173
|
-
}
|
|
3174
|
-
</label>
|
|
3175
|
-
</div>
|
|
3176
|
-
@if (uploadError()) {
|
|
3177
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">{{ uploadError() }}</span>
|
|
3178
|
-
}
|
|
3179
|
-
}
|
|
3180
|
-
</div>
|
|
3167
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: FileFieldComponent, isStandalone: true, selector: "ngx-file-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
3168
|
+
<div class="ngx-field ngx-field--file"
|
|
3169
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
3170
|
+
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
3171
|
+
@if (masked) {
|
|
3172
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
3173
|
+
} @else if (readonly) {
|
|
3174
|
+
@if (fileUrl()) {
|
|
3175
|
+
<a class="ngx-field__file-link" [href]="fileUrl()!" target="_blank" rel="noopener">
|
|
3176
|
+
📎 {{ fileName() || 'Download file' }}
|
|
3177
|
+
</a>
|
|
3178
|
+
} @else {
|
|
3179
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">—</span>
|
|
3180
|
+
}
|
|
3181
|
+
} @else {
|
|
3182
|
+
<div class="ngx-field__file-wrap">
|
|
3183
|
+
@if (fileName()) {
|
|
3184
|
+
<div class="ngx-field__file-selected">
|
|
3185
|
+
<span class="ngx-field__file-name">📎 {{ fileName() }}</span>
|
|
3186
|
+
<button type="button" class="ngx-field__remove-btn" (click)="remove()" aria-label="Remove file">✕</button>
|
|
3187
|
+
</div>
|
|
3188
|
+
}
|
|
3189
|
+
<label class="ngx-field__upload-btn" [class.ngx-field__upload-btn--loading]="uploading()">
|
|
3190
|
+
@if (uploading()) {
|
|
3191
|
+
<span>Uploading…</span>
|
|
3192
|
+
} @else {
|
|
3193
|
+
<span>{{ fileName() ? 'Replace file' : 'Choose file' }}</span>
|
|
3194
|
+
<input
|
|
3195
|
+
type="file"
|
|
3196
|
+
class="ngx-field__file-input"
|
|
3197
|
+
[id]="'field-' + field.id"
|
|
3198
|
+
[disabled]="field.disabled || uploading()"
|
|
3199
|
+
(change)="onFileSelect($any($event.target).files[0])"
|
|
3200
|
+
/>
|
|
3201
|
+
}
|
|
3202
|
+
</label>
|
|
3203
|
+
</div>
|
|
3204
|
+
@if (uploadError()) {
|
|
3205
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">{{ uploadError() }}</span>
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
</div>
|
|
3181
3209
|
`, isInline: true });
|
|
3182
3210
|
}
|
|
3183
3211
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FileFieldComponent, decorators: [{
|
|
@@ -3186,48 +3214,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3186
3214
|
selector: 'ngx-file-field',
|
|
3187
3215
|
standalone: true,
|
|
3188
3216
|
imports: [],
|
|
3189
|
-
template: `
|
|
3190
|
-
<div class="ngx-field ngx-field--file"
|
|
3191
|
-
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
3192
|
-
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
3193
|
-
@if (masked) {
|
|
3194
|
-
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
3195
|
-
} @else if (readonly) {
|
|
3196
|
-
@if (fileUrl()) {
|
|
3197
|
-
<a class="ngx-field__file-link" [href]="fileUrl()!" target="_blank" rel="noopener">
|
|
3198
|
-
📎 {{ fileName() || 'Download file' }}
|
|
3199
|
-
</a>
|
|
3200
|
-
} @else {
|
|
3201
|
-
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">—</span>
|
|
3202
|
-
}
|
|
3203
|
-
} @else {
|
|
3204
|
-
<div class="ngx-field__file-wrap">
|
|
3205
|
-
@if (fileName()) {
|
|
3206
|
-
<div class="ngx-field__file-selected">
|
|
3207
|
-
<span class="ngx-field__file-name">📎 {{ fileName() }}</span>
|
|
3208
|
-
<button type="button" class="ngx-field__remove-btn" (click)="remove()" aria-label="Remove file">✕</button>
|
|
3209
|
-
</div>
|
|
3210
|
-
}
|
|
3211
|
-
<label class="ngx-field__upload-btn" [class.ngx-field__upload-btn--loading]="uploading()">
|
|
3212
|
-
@if (uploading()) {
|
|
3213
|
-
<span>Uploading…</span>
|
|
3214
|
-
} @else {
|
|
3215
|
-
<span>{{ fileName() ? 'Replace file' : 'Choose file' }}</span>
|
|
3216
|
-
<input
|
|
3217
|
-
type="file"
|
|
3218
|
-
class="ngx-field__file-input"
|
|
3219
|
-
[id]="'field-' + field.id"
|
|
3220
|
-
[disabled]="field.disabled || uploading()"
|
|
3221
|
-
(change)="onFileSelect($any($event.target).files[0])"
|
|
3222
|
-
/>
|
|
3223
|
-
}
|
|
3224
|
-
</label>
|
|
3225
|
-
</div>
|
|
3226
|
-
@if (uploadError()) {
|
|
3227
|
-
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">{{ uploadError() }}</span>
|
|
3228
|
-
}
|
|
3229
|
-
}
|
|
3230
|
-
</div>
|
|
3217
|
+
template: `
|
|
3218
|
+
<div class="ngx-field ngx-field--file"
|
|
3219
|
+
[attr.data-testid]="'field-' + field.id" [attr.data-field-type]="field.type" [class.ngx-field--readonly]="readonly" [class.ngx-field--masked]="masked">
|
|
3220
|
+
<label class="ngx-field__label" [attr.for]="'field-' + field.id">{{ label }}</label>
|
|
3221
|
+
@if (masked) {
|
|
3222
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
3223
|
+
} @else if (readonly) {
|
|
3224
|
+
@if (fileUrl()) {
|
|
3225
|
+
<a class="ngx-field__file-link" [href]="fileUrl()!" target="_blank" rel="noopener">
|
|
3226
|
+
📎 {{ fileName() || 'Download file' }}
|
|
3227
|
+
</a>
|
|
3228
|
+
} @else {
|
|
3229
|
+
<span class="ngx-field__value" [attr.data-testid]="'field-' + field.id + '-value'">—</span>
|
|
3230
|
+
}
|
|
3231
|
+
} @else {
|
|
3232
|
+
<div class="ngx-field__file-wrap">
|
|
3233
|
+
@if (fileName()) {
|
|
3234
|
+
<div class="ngx-field__file-selected">
|
|
3235
|
+
<span class="ngx-field__file-name">📎 {{ fileName() }}</span>
|
|
3236
|
+
<button type="button" class="ngx-field__remove-btn" (click)="remove()" aria-label="Remove file">✕</button>
|
|
3237
|
+
</div>
|
|
3238
|
+
}
|
|
3239
|
+
<label class="ngx-field__upload-btn" [class.ngx-field__upload-btn--loading]="uploading()">
|
|
3240
|
+
@if (uploading()) {
|
|
3241
|
+
<span>Uploading…</span>
|
|
3242
|
+
} @else {
|
|
3243
|
+
<span>{{ fileName() ? 'Replace file' : 'Choose file' }}</span>
|
|
3244
|
+
<input
|
|
3245
|
+
type="file"
|
|
3246
|
+
class="ngx-field__file-input"
|
|
3247
|
+
[id]="'field-' + field.id"
|
|
3248
|
+
[disabled]="field.disabled || uploading()"
|
|
3249
|
+
(change)="onFileSelect($any($event.target).files[0])"
|
|
3250
|
+
/>
|
|
3251
|
+
}
|
|
3252
|
+
</label>
|
|
3253
|
+
</div>
|
|
3254
|
+
@if (uploadError()) {
|
|
3255
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">{{ uploadError() }}</span>
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
</div>
|
|
3231
3259
|
`,
|
|
3232
3260
|
}]
|
|
3233
3261
|
}], propDecorators: { field: [{
|
|
@@ -3454,6 +3482,7 @@ class DynamicFormComponent {
|
|
|
3454
3482
|
rulesEvaluation = inject(RulesEvaluationService);
|
|
3455
3483
|
entityRefSelection = inject(EntityRefSelectionService);
|
|
3456
3484
|
commonModulesRegistry = inject(COMMON_MODULES_REGISTRY, { optional: true });
|
|
3485
|
+
migrations = inject(RECORD_MIGRATIONS, { optional: true }) ?? [];
|
|
3457
3486
|
Object = Object;
|
|
3458
3487
|
// ─── Signals (local reactive state) ───────────────────────────────────────
|
|
3459
3488
|
activeTab = signal('');
|
|
@@ -3536,11 +3565,62 @@ class DynamicFormComponent {
|
|
|
3536
3565
|
const hiddenFields = this.ruleResult().hiddenFields;
|
|
3537
3566
|
return rawFields.filter(field => evaluateFieldVisibility(field, currentValues) && !hiddenFields.includes(field.id));
|
|
3538
3567
|
}
|
|
3568
|
+
/**
|
|
3569
|
+
* Entity-level permissions for the current roles.
|
|
3570
|
+
*
|
|
3571
|
+
* Cached rather than recomputed: this is read from the template several times per change
|
|
3572
|
+
* detection pass, and a fresh object each time is both wasted allocation and a new
|
|
3573
|
+
* identity for anything comparing by reference. Invalidated in `ngOnChanges` when
|
|
3574
|
+
* `config` or `userRoles` change, which are its only inputs.
|
|
3575
|
+
*/
|
|
3539
3576
|
get permissions() {
|
|
3540
|
-
return this.rbacService.getPermissions(this.config, this.userRoles);
|
|
3577
|
+
return (this.permissionsCache ??= this.rbacService.getPermissions(this.config, this.userRoles));
|
|
3578
|
+
}
|
|
3579
|
+
permissionsCache = null;
|
|
3580
|
+
/**
|
|
3581
|
+
* Whether the current roles may see this record at all.
|
|
3582
|
+
*
|
|
3583
|
+
* `permissions.view` used to be computed and thrown away — a user whose roles failed it
|
|
3584
|
+
* still received the complete form with every value in the DOM. It is honoured now.
|
|
3585
|
+
*
|
|
3586
|
+
* As with masking, this is presentational: it stops the browser rendering data, it does
|
|
3587
|
+
* not stop the data reaching the browser. Authorize on the server.
|
|
3588
|
+
*/
|
|
3589
|
+
get canView() {
|
|
3590
|
+
return this.permissions.canView;
|
|
3591
|
+
}
|
|
3592
|
+
/**
|
|
3593
|
+
* Whether the current roles may delete this record.
|
|
3594
|
+
*
|
|
3595
|
+
* The library ships no delete affordance, so this is surfaced for the consumer to gate
|
|
3596
|
+
* their own — exposed rather than dropped, because a permission the schema declares
|
|
3597
|
+
* should be answerable.
|
|
3598
|
+
*/
|
|
3599
|
+
get canDelete() {
|
|
3600
|
+
return this.permissions.canDelete;
|
|
3541
3601
|
}
|
|
3542
3602
|
get canSubmit() {
|
|
3543
|
-
return this.permissions.canEdit && !this.readonly;
|
|
3603
|
+
return this.canView && this.permissions.canEdit && !this.readonly;
|
|
3604
|
+
}
|
|
3605
|
+
/**
|
|
3606
|
+
* Errors raised by `validation` rules, keyed by the target they point at.
|
|
3607
|
+
*
|
|
3608
|
+
* A rule can declare the record invalid independently of Angular's validators, so form
|
|
3609
|
+
* validity alone is not the whole picture. `DynamicRecordFormComponent.saveSection()`
|
|
3610
|
+
* has always honoured these; `submit()` did not, which meant the same rule blocked one
|
|
3611
|
+
* save path and merely painted a banner on the other — and anything wiring
|
|
3612
|
+
* `(formSubmit)` to persistence wrote records the rules engine had already rejected.
|
|
3613
|
+
*/
|
|
3614
|
+
get ruleValidationErrors() {
|
|
3615
|
+
return this.ruleResult().validationErrors;
|
|
3616
|
+
}
|
|
3617
|
+
/** True when a `validation` rule is currently blocking submission. */
|
|
3618
|
+
get hasRuleErrors() {
|
|
3619
|
+
return Object.keys(this.ruleValidationErrors).length > 0;
|
|
3620
|
+
}
|
|
3621
|
+
/** Single source of truth for both the submit guard and the button's disabled state. */
|
|
3622
|
+
get submitBlocked() {
|
|
3623
|
+
return this.form.invalid || this.hasRuleErrors;
|
|
3544
3624
|
}
|
|
3545
3625
|
ngOnInit() {
|
|
3546
3626
|
this.selectionSub = this.entityRefSelection.selection$.subscribe(({ fieldId, option }) => {
|
|
@@ -3548,6 +3628,9 @@ class DynamicFormComponent {
|
|
|
3548
3628
|
});
|
|
3549
3629
|
}
|
|
3550
3630
|
ngOnChanges(changes) {
|
|
3631
|
+
if (changes['config'] || changes['userRoles']) {
|
|
3632
|
+
this.permissionsCache = null;
|
|
3633
|
+
}
|
|
3551
3634
|
if (changes['config'] && this.config) {
|
|
3552
3635
|
// Configs arrive as plain JSON from storage or an API, where TypeScript cannot enforce
|
|
3553
3636
|
// the option shape. Normalise here, at the library boundary, so everything downstream
|
|
@@ -3641,13 +3724,14 @@ class DynamicFormComponent {
|
|
|
3641
3724
|
buildTabControls(this.config.tabs || [], group);
|
|
3642
3725
|
this.form = this.fb.group(group);
|
|
3643
3726
|
if (this.initialData) {
|
|
3644
|
-
this.patchForm(this.initialData);
|
|
3727
|
+
this.patchForm(this.upgradeRecord(this.initialData));
|
|
3645
3728
|
}
|
|
3646
3729
|
const initialFlattened = this.flattenFormValues();
|
|
3647
3730
|
this.formValues.set(initialFlattened);
|
|
3648
3731
|
this.previousValues = { ...initialFlattened };
|
|
3649
3732
|
this.sessionBaseline.set({ ...initialFlattened });
|
|
3650
3733
|
this.unlockedFields.set(new Set());
|
|
3734
|
+
this.syncHiddenFieldState(initialFlattened);
|
|
3651
3735
|
if (this.preview) {
|
|
3652
3736
|
// Show what an array field looks like rather than an empty slot, then freeze.
|
|
3653
3737
|
for (const field of this.allFields()) {
|
|
@@ -3668,6 +3752,7 @@ class DynamicFormComponent {
|
|
|
3668
3752
|
const flattened = this.flattenFormValues();
|
|
3669
3753
|
this.formValues.set(flattened);
|
|
3670
3754
|
this.runPatchOnTrue(flattened);
|
|
3755
|
+
this.syncHiddenFieldState(flattened);
|
|
3671
3756
|
this.previousValues = { ...flattened };
|
|
3672
3757
|
this.formChange.emit(this.extractRecord());
|
|
3673
3758
|
});
|
|
@@ -3716,10 +3801,31 @@ class DynamicFormComponent {
|
|
|
3716
3801
|
group.patchValue(item);
|
|
3717
3802
|
return group;
|
|
3718
3803
|
}
|
|
3804
|
+
/**
|
|
3805
|
+
* Bring a record up to the config's `version` before it reaches the form.
|
|
3806
|
+
*
|
|
3807
|
+
* Done here because this is the one place a record enters the renderer, so a consumer
|
|
3808
|
+
* cannot forget it. Migration is a no-op unless the consumer registered steps *and* the
|
|
3809
|
+
* record is behind — an unstamped record is deliberately left alone (see `migrateRecord`).
|
|
3810
|
+
*
|
|
3811
|
+
* A missing or broken step throws from core. That is not swallowed: rendering a
|
|
3812
|
+
* half-understood record would write it back in a shape matching neither schema.
|
|
3813
|
+
*/
|
|
3814
|
+
upgradeRecord(data) {
|
|
3815
|
+
if (!this.migrations.length || !this.config)
|
|
3816
|
+
return data;
|
|
3817
|
+
const result = migrateRecord(data, this.config, this.migrations);
|
|
3818
|
+
if (result.applied.length && isDevMode()) {
|
|
3819
|
+
console.info(`[ngx-dynamic-entity] Migrated a "${this.config.entity}" record from config version ` +
|
|
3820
|
+
`${result.from} to ${result.to} (steps: ${result.applied.join(', ')}).`);
|
|
3821
|
+
}
|
|
3822
|
+
return result.record;
|
|
3823
|
+
}
|
|
3719
3824
|
patchForm(data) {
|
|
3720
3825
|
if (!data || !this.form)
|
|
3721
3826
|
return;
|
|
3722
3827
|
const fieldsById = new Map(this.allFields().map(f => [f.id, f]));
|
|
3828
|
+
const patchedFieldIds = new Set();
|
|
3723
3829
|
const walkTabs = (tabs) => {
|
|
3724
3830
|
for (const tab of tabs) {
|
|
3725
3831
|
const tabData = getTabData(tab.id, data, this.config);
|
|
@@ -3744,13 +3850,57 @@ class DynamicFormComponent {
|
|
|
3744
3850
|
else {
|
|
3745
3851
|
ctrl.patchValue(val, { emitEvent: false });
|
|
3746
3852
|
}
|
|
3853
|
+
patchedFieldIds.add(field.id);
|
|
3747
3854
|
}
|
|
3748
3855
|
if (tab.children)
|
|
3749
3856
|
walkTabs(tab.children);
|
|
3750
3857
|
}
|
|
3751
3858
|
};
|
|
3752
3859
|
walkTabs(this.config.tabs || []);
|
|
3860
|
+
this.warnUnconsumedInitialData(data, fieldsById, patchedFieldIds);
|
|
3753
3861
|
}
|
|
3862
|
+
/**
|
|
3863
|
+
* A record is nested by tab id (`{ tabId: { fieldId: value } }`) unless the tab sets
|
|
3864
|
+
* `flatData: true`. Handing a flat record to a nested tab finds nothing, so the fields
|
|
3865
|
+
* stay empty — with no error and no clue. That silent miss is the most expensive way to
|
|
3866
|
+
* lose an hour with this library, so name it in dev builds.
|
|
3867
|
+
*
|
|
3868
|
+
* Only top-level keys that match a known field id are reported: anything else is assumed
|
|
3869
|
+
* to be the consumer's own record metadata (ids, timestamps, `_configVersion`) and is not
|
|
3870
|
+
* our business.
|
|
3871
|
+
*/
|
|
3872
|
+
warnUnconsumedInitialData(data, fieldsById, patchedFieldIds) {
|
|
3873
|
+
if (!isDevMode() || !this.config)
|
|
3874
|
+
return;
|
|
3875
|
+
const tabIds = new Set();
|
|
3876
|
+
const collectTabIds = (tabs) => {
|
|
3877
|
+
for (const tab of tabs) {
|
|
3878
|
+
tabIds.add(tab.id);
|
|
3879
|
+
if (tab.children)
|
|
3880
|
+
collectTabIds(tab.children);
|
|
3881
|
+
}
|
|
3882
|
+
};
|
|
3883
|
+
collectTabIds(this.config.tabs || []);
|
|
3884
|
+
const unconsumed = Object.keys(data).filter(key => !tabIds.has(key) && // a tab id at the root is the nested container, not a stray field
|
|
3885
|
+
data[key] !== undefined &&
|
|
3886
|
+
fieldsById.has(key) &&
|
|
3887
|
+
!patchedFieldIds.has(key) &&
|
|
3888
|
+
!this.warnedUnconsumedKeys.has(key));
|
|
3889
|
+
if (!unconsumed.length)
|
|
3890
|
+
return;
|
|
3891
|
+
for (const key of unconsumed)
|
|
3892
|
+
this.warnedUnconsumedKeys.add(key);
|
|
3893
|
+
const shown = unconsumed.slice(0, 5).join(', ');
|
|
3894
|
+
const more = unconsumed.length > 5 ? ` (+${unconsumed.length - 5} more)` : '';
|
|
3895
|
+
const plural = unconsumed.length === 1;
|
|
3896
|
+
console.warn(`[ngx-dynamic-entity] initialData has top-level ${plural ? 'key' : 'keys'} matching ` +
|
|
3897
|
+
`${plural ? 'a field that was' : 'fields that were'} not populated: ${shown}${more}. ` +
|
|
3898
|
+
`A record is nested by tab id ({ tabId: { fieldId: value } }) unless the tab sets ` +
|
|
3899
|
+
`flatData: true. Either nest the value under its tab id, or set flatData: true on ` +
|
|
3900
|
+
`the tab holding ${plural ? 'that field' : 'those fields'}.`);
|
|
3901
|
+
}
|
|
3902
|
+
/** Warn once per key per form instance — patchForm re-runs on every initialData change. */
|
|
3903
|
+
warnedUnconsumedKeys = new Set();
|
|
3754
3904
|
/** Assemble full nested record from per-tab FormGroups, respecting flatData, refererField & arrays. */
|
|
3755
3905
|
extractRecord() {
|
|
3756
3906
|
if (!this.form || !this.config)
|
|
@@ -3849,6 +3999,57 @@ class DynamicFormComponent {
|
|
|
3849
3999
|
return this.getControl(targetId, autoPatch.targetTab);
|
|
3850
4000
|
}
|
|
3851
4001
|
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
4002
|
+
/**
|
|
4003
|
+
* The fields the render filter operates on — a tab's own fields, at every tab depth.
|
|
4004
|
+
* Deliberately not `allFields()`: `group` and `array` children are shown or hidden with
|
|
4005
|
+
* their parent, and toggling a child's state independently would fight the parent's.
|
|
4006
|
+
*/
|
|
4007
|
+
tabFields() {
|
|
4008
|
+
const out = [];
|
|
4009
|
+
const walk = (tabs) => {
|
|
4010
|
+
for (const tab of tabs ?? []) {
|
|
4011
|
+
out.push(...(tab.fields ?? []));
|
|
4012
|
+
walk(tab.children);
|
|
4013
|
+
}
|
|
4014
|
+
};
|
|
4015
|
+
walk(this.config?.tabs);
|
|
4016
|
+
return out;
|
|
4017
|
+
}
|
|
4018
|
+
/**
|
|
4019
|
+
* Keep a hidden field's control out of form validity.
|
|
4020
|
+
*
|
|
4021
|
+
* A field hidden by a rule or a `showWhen` condition is filtered out of the render, but
|
|
4022
|
+
* its validators stay attached — so a hidden *required* field holds `form.invalid` true
|
|
4023
|
+
* forever, permanently disabling Save with nothing on screen to explain why.
|
|
4024
|
+
*
|
|
4025
|
+
* Disabling is the fix rather than stripping validators: Angular excludes disabled
|
|
4026
|
+
* controls from validity, and the field's own validators survive intact for when it
|
|
4027
|
+
* comes back. Values are untouched — `flattenFormValues` and `extractRecord` both read
|
|
4028
|
+
* `control.value`, which a disabled control still carries, so rule evaluation and the
|
|
4029
|
+
* emitted record see exactly what they saw before. That also keeps this from feeding
|
|
4030
|
+
* back on itself: hiding a field cannot change the values the rules are evaluated over.
|
|
4031
|
+
*
|
|
4032
|
+
* The visibility predicate is the same one `fieldsForActiveTab` filters with, so what is
|
|
4033
|
+
* rendered and what counts toward validity cannot drift apart.
|
|
4034
|
+
*/
|
|
4035
|
+
syncHiddenFieldState(values) {
|
|
4036
|
+
if (this.preview)
|
|
4037
|
+
return; // preview freezes the whole form on purpose
|
|
4038
|
+
const hiddenByRule = new Set(this.ruleResult().hiddenFields);
|
|
4039
|
+
for (const field of this.tabFields()) {
|
|
4040
|
+
const ctrl = this.getControl(field.id);
|
|
4041
|
+
if (!ctrl)
|
|
4042
|
+
continue;
|
|
4043
|
+
const hidden = hiddenByRule.has(field.id) || !evaluateFieldVisibility(field, values);
|
|
4044
|
+
if (hidden) {
|
|
4045
|
+
if (ctrl.enabled)
|
|
4046
|
+
ctrl.disable({ emitEvent: false });
|
|
4047
|
+
}
|
|
4048
|
+
else if (ctrl.disabled && !field.disabled) {
|
|
4049
|
+
ctrl.enable({ emitEvent: false });
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
3852
4053
|
/** Every field in the config, including `group`/`array` children. */
|
|
3853
4054
|
allFields() {
|
|
3854
4055
|
const out = [];
|
|
@@ -3918,7 +4119,7 @@ class DynamicFormComponent {
|
|
|
3918
4119
|
return resolveLabel(tab.label, this.language);
|
|
3919
4120
|
}
|
|
3920
4121
|
async submit() {
|
|
3921
|
-
if (!this.canSubmit || this.
|
|
4122
|
+
if (!this.canSubmit || this.submitBlocked) {
|
|
3922
4123
|
this.form.markAllAsTouched();
|
|
3923
4124
|
return;
|
|
3924
4125
|
}
|
|
@@ -3947,11 +4148,11 @@ class DynamicFormComponent {
|
|
|
3947
4148
|
this.formReset.emit();
|
|
3948
4149
|
}
|
|
3949
4150
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DynamicFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3950
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: DynamicFormComponent, isStandalone: true, selector: "ngx-dynamic-form", inputs: { config: "config", rules: "rules", initialData: "initialData", originalData: "originalData", userRoles: "userRoles", language: "language", readonly: "readonly", readOnlyFields: "readOnlyFields", showInfoBanners: "showInfoBanners", loading: "loading", error: "error", changeDebounceMs: "changeDebounceMs", preview: "preview" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", activeTabChange: "activeTabChange" }, host: { listeners: { "window:keydown": "handleKeyboardEvent($event)" } }, providers: [EntityRefSelectionService], usesOnChanges: true, ngImport: i0, template: "<form [formGroup]=\"form\" (ngSubmit)=\"submit()\" class=\"ngx-form\" novalidate>\
|
|
4151
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: DynamicFormComponent, isStandalone: true, selector: "ngx-dynamic-form", inputs: { config: "config", rules: "rules", initialData: "initialData", originalData: "originalData", userRoles: "userRoles", language: "language", readonly: "readonly", readOnlyFields: "readOnlyFields", showInfoBanners: "showInfoBanners", loading: "loading", error: "error", changeDebounceMs: "changeDebounceMs", preview: "preview" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", activeTabChange: "activeTabChange" }, host: { listeners: { "window:keydown": "handleKeyboardEvent($event)" } }, providers: [EntityRefSelectionService], usesOnChanges: true, ngImport: i0, template: "@if (canView) {\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\" class=\"ngx-form\" novalidate>\n\n <!-- Error message banner -->\n @if (error) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" data-testid=\"form-error\">{{ error }}</div>\n }\n\n <!-- Rules Info Banners -->\n @for (infoKey of showInfoBanners ? Object.keys(ruleResult().infoBanners) : []; track infoKey) {\n <div class=\"ngx-form__info-banner\" role=\"status\" [attr.data-testid]=\"'info-banner-' + infoKey\">\n \u2139\uFE0F {{ ruleResult().infoBanners[infoKey] }}\n </div>\n }\n\n <!-- Rules Validation Errors & Warnings -->\n @for (errKey of Object.keys(ruleResult().validationErrors); track errKey) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" [attr.data-testid]=\"'rule-error-' + errKey\">\n \u26A0\uFE0F {{ ruleResult().validationErrors[errKey] }}\n </div>\n }\n @for (warnKey of Object.keys(ruleResult().validationWarnings); track warnKey) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" [attr.data-testid]=\"'rule-warning-' + warnKey\">\n \u26A1 {{ ruleResult().validationWarnings[warnKey] }}\n </div>\n }\n\n <!-- Deferred VALUE_CHANGED notice for criticalField edits (vs session baseline) -->\n @if (changedCriticalFields().length > 0) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" data-testid=\"critical-change-banner\">\n \uD83D\uDD12 Critical field changed:\n @for (field of changedCriticalFields(); track field.id) {\n <span>{{ resolveFieldLabel(field) }}{{ $last ? '' : ', ' }}</span>\n }\n \u2014 this differs from the value at the start of this session.\n </div>\n }\n\n <!-- Primary Tab bar \u2014 only if visible tabs defined -->\n @if (visibleTabs.length > 0) {\n <div class=\"ngx-form__tabs\" role=\"tablist\" data-testid=\"tab-strip\">\n @for (tab of visibleTabs; track tab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab\"\n [attr.data-testid]=\"'tab-' + tab.id\"\n [class.ngx-form__tab--active]=\"activeTab() === tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n >\n {{ resolveTabLabel(tab) }}\n </button>\n }\n </div>\n }\n\n <!-- Sub-tab bar \u2014 only if active tab has sub-tabs -->\n @if (visibleSubTabs.length > 0) {\n <div class=\"ngx-form__tabs ngx-form__tabs--sub\" role=\"tablist\" data-testid=\"subtab-strip\" style=\"margin-top: -12px; margin-bottom: 12px; font-size: 13px;\">\n @for (subTab of visibleSubTabs; track subTab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab ngx-form__tab--sub\"\n [attr.data-testid]=\"'subtab-' + subTab.id\"\n [class.ngx-form__tab--active]=\"(activeSubTabConfig?.id) === subTab.id\"\n (click)=\"setActiveSubTab(subTab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"(activeSubTabConfig?.id) === subTab.id\"\n >\n {{ resolveTabLabel(subTab) }}\n </button>\n }\n </div>\n }\n\n <!-- Module tab component OR Fields for active tab -->\n @if (activeTabModuleComponent) {\n <div class=\"ngx-form__module-panel\" data-testid=\"module-panel\">\n <ng-container *ngComponentOutlet=\"activeTabModuleComponent; inputs: (activeSubTabConfig || activeTabConfig)?.moduleInputs\"></ng-container>\n </div>\n } @else {\n <!-- Fields for active tab (Responsive 12-column grid) -->\n <div class=\"ngx-form__panel\" data-testid=\"form-panel\">\n @for (field of fieldsForActiveTab; track field.id) {\n <div\n class=\"ngx-form__field-slot\"\n [style.gridColumn]=\"getFieldSpan(field)\"\n [attr.id]=\"'field-container-' + field.id\"\n >\n <ngx-dynamic-field\n [field]=\"field\"\n [control]=\"getControl(field.id, (activeSubTabConfig || activeTabConfig)?.id)!\"\n [config]=\"config\"\n [currentTabId]=\"(activeSubTabConfig || activeTabConfig)?.id\"\n [language]=\"language\"\n [readonly]=\"isFieldReadonly(field)\"\n [userRoles]=\"userRoles\"\n />\n @if (field.criticalField && !readonly) {\n <button\n type=\"button\"\n class=\"ngx-form__lock\"\n [attr.data-testid]=\"'lock-' + field.id\"\n [attr.aria-pressed]=\"!isFieldLocked(field)\"\n [attr.aria-label]=\"\n (isFieldLocked(field) ? 'Unlock ' : 'Lock ') + resolveFieldLabel(field)\n \"\n [title]=\"\n isFieldLocked(field)\n ? 'Critical field \u2014 click to unlock for editing'\n : 'Critical field \u2014 click to lock'\n \"\n (click)=\"toggleFieldLock(field)\"\n >\n {{ isFieldLocked(field) ? '\uD83D\uDD12' : '\uD83D\uDD13' }}\n </button>\n }\n </div>\n }\n </div>\n }\n\n <!-- Submit button \u2014 gated by RBAC and isSaving lock -->\n @if (!readonly && permissions.canEdit) {\n <div class=\"ngx-form__actions\" data-testid=\"form-actions\">\n <button\n type=\"button\"\n class=\"ngx-form__reset\"\n data-testid=\"form-reset\"\n (click)=\"reset()\"\n [disabled]=\"isSaving()\"\n >\n Reset\n </button>\n <button\n type=\"submit\"\n class=\"ngx-form__submit\"\n data-testid=\"form-submit\"\n [disabled]=\"submitBlocked\"\n title=\"Shortcut: Ctrl+S\"\n >\n @if (isSaving() || loading) {\n Saving\u2026\n } @else {\n Save\n }\n </button>\n </div>\n }\n\n </form>\n} @else {\n <!-- permissions.view denied: render nothing of the record.\n Presentational only \u2014 the server must not send data this user may not see. -->\n <div class=\"ngx-form__denied\" role=\"status\" data-testid=\"form-access-denied\">\n You do not have permission to view this record.\n </div>\n}\n", styles: [".ngx-form__panel{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:16px 20px;align-items:start}.ngx-form__field-slot{display:flex;align-items:flex-start;gap:6px;min-width:0}.ngx-form__field-slot ngx-dynamic-field{flex:1;min-width:0}.ngx-form__lock{flex:0 0 auto;margin-top:22px;background:none;border:1px solid transparent;border-radius:6px;cursor:pointer;font-size:14px;line-height:1;padding:4px 6px}.ngx-form__lock:hover{border-color:#d1d5db;background:#f9fafb}@media (max-width: 768px){.ngx-form__panel{grid-template-columns:1fr}.ngx-form__field-slot{grid-column:span 12!important}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicFieldComponent, selector: "ngx-dynamic-field", inputs: ["field", "control", "config", "currentTabId", "language", "readonly", "userRoles"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"] }] });
|
|
3951
4152
|
}
|
|
3952
4153
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DynamicFormComponent, decorators: [{
|
|
3953
4154
|
type: Component,
|
|
3954
|
-
args: [{ selector: 'ngx-dynamic-form', standalone: true, imports: [ReactiveFormsModule, DynamicFieldComponent, NgComponentOutlet], providers: [EntityRefSelectionService], template: "<form [formGroup]=\"form\" (ngSubmit)=\"submit()\" class=\"ngx-form\" novalidate>\
|
|
4155
|
+
args: [{ selector: 'ngx-dynamic-form', standalone: true, imports: [ReactiveFormsModule, DynamicFieldComponent, NgComponentOutlet], providers: [EntityRefSelectionService], template: "@if (canView) {\n <form [formGroup]=\"form\" (ngSubmit)=\"submit()\" class=\"ngx-form\" novalidate>\n\n <!-- Error message banner -->\n @if (error) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" data-testid=\"form-error\">{{ error }}</div>\n }\n\n <!-- Rules Info Banners -->\n @for (infoKey of showInfoBanners ? Object.keys(ruleResult().infoBanners) : []; track infoKey) {\n <div class=\"ngx-form__info-banner\" role=\"status\" [attr.data-testid]=\"'info-banner-' + infoKey\">\n \u2139\uFE0F {{ ruleResult().infoBanners[infoKey] }}\n </div>\n }\n\n <!-- Rules Validation Errors & Warnings -->\n @for (errKey of Object.keys(ruleResult().validationErrors); track errKey) {\n <div class=\"ngx-form__error-banner\" role=\"alert\" [attr.data-testid]=\"'rule-error-' + errKey\">\n \u26A0\uFE0F {{ ruleResult().validationErrors[errKey] }}\n </div>\n }\n @for (warnKey of Object.keys(ruleResult().validationWarnings); track warnKey) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" [attr.data-testid]=\"'rule-warning-' + warnKey\">\n \u26A1 {{ ruleResult().validationWarnings[warnKey] }}\n </div>\n }\n\n <!-- Deferred VALUE_CHANGED notice for criticalField edits (vs session baseline) -->\n @if (changedCriticalFields().length > 0) {\n <div class=\"ngx-form__warning-banner\" role=\"status\" data-testid=\"critical-change-banner\">\n \uD83D\uDD12 Critical field changed:\n @for (field of changedCriticalFields(); track field.id) {\n <span>{{ resolveFieldLabel(field) }}{{ $last ? '' : ', ' }}</span>\n }\n \u2014 this differs from the value at the start of this session.\n </div>\n }\n\n <!-- Primary Tab bar \u2014 only if visible tabs defined -->\n @if (visibleTabs.length > 0) {\n <div class=\"ngx-form__tabs\" role=\"tablist\" data-testid=\"tab-strip\">\n @for (tab of visibleTabs; track tab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab\"\n [attr.data-testid]=\"'tab-' + tab.id\"\n [class.ngx-form__tab--active]=\"activeTab() === tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n >\n {{ resolveTabLabel(tab) }}\n </button>\n }\n </div>\n }\n\n <!-- Sub-tab bar \u2014 only if active tab has sub-tabs -->\n @if (visibleSubTabs.length > 0) {\n <div class=\"ngx-form__tabs ngx-form__tabs--sub\" role=\"tablist\" data-testid=\"subtab-strip\" style=\"margin-top: -12px; margin-bottom: 12px; font-size: 13px;\">\n @for (subTab of visibleSubTabs; track subTab.id) {\n <button\n type=\"button\"\n class=\"ngx-form__tab ngx-form__tab--sub\"\n [attr.data-testid]=\"'subtab-' + subTab.id\"\n [class.ngx-form__tab--active]=\"(activeSubTabConfig?.id) === subTab.id\"\n (click)=\"setActiveSubTab(subTab.id)\"\n role=\"tab\"\n [attr.aria-selected]=\"(activeSubTabConfig?.id) === subTab.id\"\n >\n {{ resolveTabLabel(subTab) }}\n </button>\n }\n </div>\n }\n\n <!-- Module tab component OR Fields for active tab -->\n @if (activeTabModuleComponent) {\n <div class=\"ngx-form__module-panel\" data-testid=\"module-panel\">\n <ng-container *ngComponentOutlet=\"activeTabModuleComponent; inputs: (activeSubTabConfig || activeTabConfig)?.moduleInputs\"></ng-container>\n </div>\n } @else {\n <!-- Fields for active tab (Responsive 12-column grid) -->\n <div class=\"ngx-form__panel\" data-testid=\"form-panel\">\n @for (field of fieldsForActiveTab; track field.id) {\n <div\n class=\"ngx-form__field-slot\"\n [style.gridColumn]=\"getFieldSpan(field)\"\n [attr.id]=\"'field-container-' + field.id\"\n >\n <ngx-dynamic-field\n [field]=\"field\"\n [control]=\"getControl(field.id, (activeSubTabConfig || activeTabConfig)?.id)!\"\n [config]=\"config\"\n [currentTabId]=\"(activeSubTabConfig || activeTabConfig)?.id\"\n [language]=\"language\"\n [readonly]=\"isFieldReadonly(field)\"\n [userRoles]=\"userRoles\"\n />\n @if (field.criticalField && !readonly) {\n <button\n type=\"button\"\n class=\"ngx-form__lock\"\n [attr.data-testid]=\"'lock-' + field.id\"\n [attr.aria-pressed]=\"!isFieldLocked(field)\"\n [attr.aria-label]=\"\n (isFieldLocked(field) ? 'Unlock ' : 'Lock ') + resolveFieldLabel(field)\n \"\n [title]=\"\n isFieldLocked(field)\n ? 'Critical field \u2014 click to unlock for editing'\n : 'Critical field \u2014 click to lock'\n \"\n (click)=\"toggleFieldLock(field)\"\n >\n {{ isFieldLocked(field) ? '\uD83D\uDD12' : '\uD83D\uDD13' }}\n </button>\n }\n </div>\n }\n </div>\n }\n\n <!-- Submit button \u2014 gated by RBAC and isSaving lock -->\n @if (!readonly && permissions.canEdit) {\n <div class=\"ngx-form__actions\" data-testid=\"form-actions\">\n <button\n type=\"button\"\n class=\"ngx-form__reset\"\n data-testid=\"form-reset\"\n (click)=\"reset()\"\n [disabled]=\"isSaving()\"\n >\n Reset\n </button>\n <button\n type=\"submit\"\n class=\"ngx-form__submit\"\n data-testid=\"form-submit\"\n [disabled]=\"submitBlocked\"\n title=\"Shortcut: Ctrl+S\"\n >\n @if (isSaving() || loading) {\n Saving\u2026\n } @else {\n Save\n }\n </button>\n </div>\n }\n\n </form>\n} @else {\n <!-- permissions.view denied: render nothing of the record.\n Presentational only \u2014 the server must not send data this user may not see. -->\n <div class=\"ngx-form__denied\" role=\"status\" data-testid=\"form-access-denied\">\n You do not have permission to view this record.\n </div>\n}\n", styles: [".ngx-form__panel{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:16px 20px;align-items:start}.ngx-form__field-slot{display:flex;align-items:flex-start;gap:6px;min-width:0}.ngx-form__field-slot ngx-dynamic-field{flex:1;min-width:0}.ngx-form__lock{flex:0 0 auto;margin-top:22px;background:none;border:1px solid transparent;border-radius:6px;cursor:pointer;font-size:14px;line-height:1;padding:4px 6px}.ngx-form__lock:hover{border-color:#d1d5db;background:#f9fafb}@media (max-width: 768px){.ngx-form__panel{grid-template-columns:1fr}.ngx-form__field-slot{grid-column:span 12!important}}\n"] }]
|
|
3955
4156
|
}], propDecorators: { config: [{
|
|
3956
4157
|
type: Input
|
|
3957
4158
|
}], rules: [{
|
|
@@ -3991,6 +4192,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
3991
4192
|
args: ['window:keydown', ['$event']]
|
|
3992
4193
|
}] } });
|
|
3993
4194
|
|
|
4195
|
+
/**
|
|
4196
|
+
* Distinguishes "no tab holds this field" from "the field holds undefined", so the tab walk
|
|
4197
|
+
* can keep searching in the first case and stop in the second.
|
|
4198
|
+
*/
|
|
4199
|
+
const FIELD_NOT_FOUND = Symbol('field-not-found');
|
|
3994
4200
|
/**
|
|
3995
4201
|
* DynamicRecordFormComponent — comprehensive tabbed record view & edit component.
|
|
3996
4202
|
* Supports cross-tab rule evaluation, profile header, summary drawer (showOnMinimize),
|
|
@@ -4340,14 +4546,34 @@ class DynamicRecordFormComponent {
|
|
|
4340
4546
|
controlFor(fieldId) {
|
|
4341
4547
|
return this.dynamicFormComp?.getControl(fieldId) ?? null;
|
|
4342
4548
|
}
|
|
4343
|
-
/**
|
|
4549
|
+
/**
|
|
4550
|
+
* A field's current value, read from the record so it is stable during render.
|
|
4551
|
+
*
|
|
4552
|
+
* Resolved through `getTabData` so it honours exactly the nesting contract the form
|
|
4553
|
+
* patches with — `{ tabId: { fieldId } }`, or the record root when the tab sets
|
|
4554
|
+
* `flatData`. Reading the record flat instead is not a shortcut: it makes the summary
|
|
4555
|
+
* display values the form never loaded, which reads as "the data is there" while every
|
|
4556
|
+
* control behind it is empty.
|
|
4557
|
+
*
|
|
4558
|
+
* Walks sub-tabs as well: a summary, header, or profile field may sit on any tab at any
|
|
4559
|
+
* depth.
|
|
4560
|
+
*/
|
|
4344
4561
|
fieldValue(fieldId) {
|
|
4345
|
-
|
|
4346
|
-
const
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4562
|
+
const visit = (tabs = []) => {
|
|
4563
|
+
for (const tab of tabs) {
|
|
4564
|
+
const data = getTabData(tab.id, this.currentData(), this.config);
|
|
4565
|
+
if (data && fieldId in data)
|
|
4566
|
+
return data[fieldId];
|
|
4567
|
+
if (tab.children) {
|
|
4568
|
+
const found = visit(tab.children);
|
|
4569
|
+
if (found !== FIELD_NOT_FOUND)
|
|
4570
|
+
return found;
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
return FIELD_NOT_FOUND;
|
|
4574
|
+
};
|
|
4575
|
+
const result = visit(this.config?.tabs);
|
|
4576
|
+
return result === FIELD_NOT_FOUND ? undefined : result;
|
|
4351
4577
|
}
|
|
4352
4578
|
isModified = computed(() => {
|
|
4353
4579
|
const orig = this.originalBaseline();
|
|
@@ -4391,7 +4617,7 @@ class DynamicRecordFormComponent {
|
|
|
4391
4617
|
}
|
|
4392
4618
|
/** Summary values render through the shared core formatter, not a local stringifier. */
|
|
4393
4619
|
formatFieldValue(field) {
|
|
4394
|
-
return formatDisplayValue(field.type, field.options, this.
|
|
4620
|
+
return formatDisplayValue(field.type, field.options, this.fieldValue(field.id), this.language);
|
|
4395
4621
|
}
|
|
4396
4622
|
jumpToField(fieldId) {
|
|
4397
4623
|
// Find parent tab for field
|
|
@@ -4411,11 +4637,11 @@ class DynamicRecordFormComponent {
|
|
|
4411
4637
|
}
|
|
4412
4638
|
}
|
|
4413
4639
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DynamicRecordFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4414
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: DynamicRecordFormComponent, isStandalone: true, selector: "ngx-dynamic-record-form", inputs: { config: "config", rules: "rules", initialData: "initialData", userRoles: "userRoles", language: "language", readonly: "readonly", loading: "loading", error: "error", isReadOnly: "isReadOnly", readOnlyFields: "readOnlyFields", viewMode: "viewMode" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", sectionSave: "sectionSave" }, viewQueries: [{ propertyName: "dynamicFormComp", first: true, predicate: DynamicFormComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"ngx-record-editor\">\r\n <!-- Record Header -->\r\n <div class=\"ngx-record-editor__header\">\r\n <div class=\"ngx-record-editor__title-group\">\r\n @if (profileImageUrl) {\r\n <img\r\n class=\"ngx-record-editor__avatar\"\r\n data-testid=\"header-profile-image\"\r\n [src]=\"profileImageUrl\"\r\n [alt]=\"recordTitle\"\r\n />\r\n } @else {\r\n <div class=\"ngx-record-editor__avatar\" data-testid=\"header-avatar-initial\">\r\n {{ avatarLetter }}\r\n </div>\r\n }\r\n <div>\r\n <h2 class=\"ngx-record-editor__title\" data-testid=\"record-title\">\r\n {{ recordTitle }}\r\n </h2>\r\n <span class=\"ngx-record-editor__subtitle\">Entity: {{ config.entity }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- Record-level status switch, outside section editing -->\r\n @if (headerToggleField; as toggleField) {\r\n <label class=\"ngx-record-editor__header-toggle\">\r\n <span>{{ formatFieldLabel(toggleField) }}</span>\r\n <input\r\n type=\"checkbox\"\r\n role=\"switch\"\r\n data-testid=\"header-toggle\"\r\n [checked]=\"headerToggleValue\"\r\n [disabled]=\"recordReadOnly\"\r\n [attr.aria-checked]=\"headerToggleValue\"\r\n (change)=\"toggleHeaderStatus()\"\r\n />\r\n <span class=\"ngx-record-editor__header-toggle-text\">{{ headerToggleValue ? 'Yes' : 'No' }}</span>\r\n </label>\r\n }\r\n </div>\r\n\r\n <!-- Dismissible info banners (rules `info` action) -->\r\n @for (banner of infoBanners; track banner.fieldId) {\r\n <div\r\n class=\"ngx-record-editor__banner ngx-record-editor__banner--info\"\r\n role=\"status\"\r\n [attr.data-testid]=\"'info-banner-' + banner.fieldId\"\r\n >\r\n <span>\u2139\uFE0F {{ banner.message }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"ngx-record-editor__banner-dismiss\"\r\n [attr.data-testid]=\"'dismiss-' + banner.fieldId\"\r\n [attr.aria-label]=\"'Dismiss: ' + banner.message\"\r\n (click)=\"dismissInfoBanner(banner.fieldId)\"\r\n >\u2715</button>\r\n </div>\r\n }\r\n\r\n <!-- Modification baseline banner -->\r\n @if (isModified()) {\r\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--warning\">\r\n \uD83D\uDD12 Record modified from session baseline \u2014 unsaved changes pending.\r\n </div>\r\n }\r\n\r\n <!-- Summary Panel (showOnMinimize fields with interactive jump) -->\r\n @if (summaryFields().length > 0) {\r\n <div class=\"ngx-record-editor__summary-panel\" data-testid=\"summary-panel\">\r\n @for (sf of summaryFields(); track sf.id) {\r\n <div\r\n class=\"ngx-record-editor__summary-item\"\r\n [attr.data-testid]=\"'summary-' + sf.id\"\r\n (click)=\"jumpToField(sf.id)\"\r\n [title]=\"'Click to jump to ' + formatFieldLabel(sf)\"\r\n >\r\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(sf) }}</span>\r\n <span class=\"ngx-record-editor__summary-val\">{{ formatFieldValue(sf) }}</span>\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Section edit bar: one tab is edited at a time, and validated on its own -->\r\n @if (viewMode && !recordReadOnly) {\r\n <div class=\"ngx-record-editor__section-bar\">\r\n @if (isEditingActiveTab) {\r\n <span class=\"ngx-record-editor__section-hint\">Editing this section</span>\r\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-section\" (click)=\"cancelSection()\">\r\n Cancel\r\n </button>\r\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-section\" (click)=\"saveSection()\">\r\n Save section\r\n </button>\r\n } @else {\r\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"edit-section\" (click)=\"editSection()\">\r\n Edit section\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n @if (Object.keys(sectionErrors()).length > 0) {\r\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--error\" role=\"alert\" data-testid=\"section-errors\">\r\n @for (key of Object.keys(sectionErrors()); track key) {\r\n <div>\u26A0\uFE0F {{ sectionErrors()[key] }}</div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Array rows: listed here, edited in the drawer below -->\r\n @for (arrayField of arrayFieldsForActiveTab; track arrayField.id) {\r\n <div class=\"ngx-record-editor__rows\" [attr.data-testid]=\"'rows-' + arrayField.id\">\r\n <div class=\"ngx-record-editor__rows-head\">\r\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(arrayField) }}</span>\r\n @if (!sectionReadOnly) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-form__reset\"\r\n [attr.data-testid]=\"'add-row-' + arrayField.id\"\r\n (click)=\"openAddRow(arrayField)\"\r\n >+ Row</button>\r\n }\r\n </div>\r\n\r\n @for (row of rowsOf(arrayField); track $index) {\r\n <div class=\"ngx-record-editor__row\" [attr.data-testid]=\"'row-' + arrayField.id + '-' + $index\">\r\n <span class=\"ngx-record-editor__row-text\">{{ rowSummary(arrayField, row) }}</span>\r\n @if (!sectionReadOnly) {\r\n <button type=\"button\" class=\"ngx-record-editor__row-btn\"\r\n [attr.data-testid]=\"'edit-row-' + arrayField.id + '-' + $index\"\r\n (click)=\"openEditRow(arrayField, $index)\">Edit</button>\r\n <button type=\"button\" class=\"ngx-record-editor__row-btn\"\r\n [attr.data-testid]=\"'delete-row-' + arrayField.id + '-' + $index\"\r\n (click)=\"deleteRow(arrayField, $index)\">Delete</button>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"ngx-record-editor__row-empty\">No rows yet.</p>\r\n }\r\n </div>\r\n }\r\n\r\n <!--\r\n Row drawer. Deliberately outside the tab panel: building a row's FormGroup inside the\r\n tab view races the tab's own initialisation (the reference hit this with FormArray).\r\n -->\r\n @if (inlineRowField(); as rowField) {\r\n <div class=\"ngx-record-editor__drawer\" data-testid=\"row-drawer\" [formGroup]=\"inlineRowForm!\">\r\n <div class=\"ngx-record-editor__rows-head\">\r\n <strong>{{ inlineRowIndex() === null ? 'Add' : 'Edit' }} {{ formatFieldLabel(rowField) }} row</strong>\r\n </div>\r\n\r\n <div class=\"ngx-form__panel\">\r\n @for (child of rowField.children ?? []; track child.id) {\r\n <ngx-dynamic-field\r\n [field]=\"child\"\r\n [control]=\"inlineRowForm!.get(child.id)!\"\r\n [config]=\"config\"\r\n [language]=\"language\"\r\n [readonly]=\"false\"\r\n [userRoles]=\"userRoles\"\r\n [style.gridColumn]=\"'span 12'\"\r\n />\r\n }\r\n </div>\r\n\r\n <div class=\"ngx-record-editor__section-bar\">\r\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-row\" (click)=\"cancelRow()\">Cancel</button>\r\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-row\" (click)=\"saveRow()\">Save row</button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Main Dynamic Form -->\r\n <ngx-dynamic-form\r\n [config]=\"config\"\r\n [rules]=\"rules\"\r\n [initialData]=\"initialData\"\r\n [originalData]=\"originalBaseline()\"\r\n [userRoles]=\"userRoles\"\r\n [language]=\"language\"\r\n [readonly]=\"sectionReadOnly\"\r\n [readOnlyFields]=\"readOnlyFields\"\r\n [showInfoBanners]=\"false\"\r\n [loading]=\"loading\"\r\n [error]=\"error\"\r\n (formChange)=\"onFormChange($event)\"\r\n (formSubmit)=\"onFormSubmit($event)\"\r\n (formReset)=\"onFormReset()\"\r\n (activeTabChange)=\"onActiveTabChange($event)\"\r\n />\r\n</div>\r\n", styles: [".ngx-record-editor{display:flex;flex-direction:column;gap:16px;background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:20px;box-shadow:0 4px 6px -1px #0000000d}.ngx-record-editor__header{display:flex;align-items:center;justify-content:space-between;padding-bottom:16px;border-bottom:1px solid #f3f4f6}.ngx-record-editor__title-group{display:flex;align-items:center;gap:12px}.ngx-record-editor__avatar{width:48px;height:48px;border-radius:50%;object-fit:cover;background:#e0e7ff;color:#4f46e5;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:18px}.ngx-record-editor__title{font-size:20px;font-weight:700;color:#111827;margin:0}.ngx-record-editor__subtitle{font-size:13px;color:#6b7280}.ngx-record-editor__header-toggle{display:flex;align-items:center;gap:8px;font-size:13px;color:#374151}.ngx-record-editor__header-toggle-text{font-weight:600}.ngx-record-editor__banner{padding:10px 14px;border-radius:8px;font-size:13px;font-weight:500}.ngx-record-editor__banner--info{background:#eff6ff;border:1px solid #dbeafe;color:#1d4ed8;display:flex;align-items:center;justify-content:space-between;gap:12px}.ngx-record-editor__banner-dismiss{background:none;border:none;cursor:pointer;color:inherit;font-size:14px;line-height:1;padding:2px 6px;border-radius:4px}.ngx-record-editor__banner-dismiss:hover{background:#0000000f}.ngx-record-editor__banner--error{background:#fef2f2;border:1px solid #fecaca;color:#b91c1c}.ngx-record-editor__section-bar{display:flex;align-items:center;justify-content:flex-end;gap:8px}.ngx-record-editor__section-hint{margin-right:auto;font-size:12px;color:#6b7280}.ngx-record-editor__banner--warning{background:#fffbeb;border:1px solid #fef3c7;color:#b45309}.ngx-record-editor__rows{border:1px solid #f3f4f6;border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:6px}.ngx-record-editor__rows-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.ngx-record-editor__row{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;background:#f9fafb}.ngx-record-editor__row-text{flex:1;font-size:13px;overflow-wrap:anywhere}.ngx-record-editor__row-btn{background:none;border:1px solid #e5e7eb;border-radius:6px;cursor:pointer;font-size:12px;padding:2px 8px}.ngx-record-editor__row-empty{font-size:12px;color:#6b7280;margin:0}.ngx-record-editor__drawer{border:1px solid #dbeafe;background:#f8fafc;border-radius:8px;padding:14px;display:flex;flex-direction:column;gap:12px}.ngx-record-editor__summary-panel{background:#f9fafb;border:1px solid #f3f4f6;border-radius:8px;padding:12px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}.ngx-record-editor__summary-item{display:flex;flex-direction:column;cursor:pointer;padding:6px 10px;border-radius:6px;transition:background .15s}.ngx-record-editor__summary-item:hover{background:#eff6ff}.ngx-record-editor__summary-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#6b7280;font-weight:600}.ngx-record-editor__summary-val{font-size:14px;font-weight:600;color:#1f2937}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicFormComponent, selector: "ngx-dynamic-form", inputs: ["config", "rules", "initialData", "originalData", "userRoles", "language", "readonly", "readOnlyFields", "showInfoBanners", "loading", "error", "changeDebounceMs", "preview"], outputs: ["formSubmit", "formChange", "formReset", "activeTabChange"] }, { kind: "component", type: DynamicFieldComponent, selector: "ngx-dynamic-field", inputs: ["field", "control", "config", "currentTabId", "language", "readonly", "userRoles"] }] });
|
|
4640
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: DynamicRecordFormComponent, isStandalone: true, selector: "ngx-dynamic-record-form", inputs: { config: "config", rules: "rules", initialData: "initialData", userRoles: "userRoles", language: "language", readonly: "readonly", loading: "loading", error: "error", isReadOnly: "isReadOnly", readOnlyFields: "readOnlyFields", viewMode: "viewMode" }, outputs: { formSubmit: "formSubmit", formChange: "formChange", formReset: "formReset", sectionSave: "sectionSave" }, viewQueries: [{ propertyName: "dynamicFormComp", first: true, predicate: DynamicFormComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"ngx-record-editor\">\n <!-- Record Header -->\n <div class=\"ngx-record-editor__header\">\n <div class=\"ngx-record-editor__title-group\">\n @if (profileImageUrl) {\n <img\n class=\"ngx-record-editor__avatar\"\n data-testid=\"header-profile-image\"\n [src]=\"profileImageUrl\"\n [alt]=\"recordTitle\"\n />\n } @else {\n <div class=\"ngx-record-editor__avatar\" data-testid=\"header-avatar-initial\">\n {{ avatarLetter }}\n </div>\n }\n <div>\n <h2 class=\"ngx-record-editor__title\" data-testid=\"record-title\">\n {{ recordTitle }}\n </h2>\n <span class=\"ngx-record-editor__subtitle\">Entity: {{ config.entity }}</span>\n </div>\n </div>\n\n <!-- Record-level status switch, outside section editing -->\n @if (headerToggleField; as toggleField) {\n <label class=\"ngx-record-editor__header-toggle\">\n <span>{{ formatFieldLabel(toggleField) }}</span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n data-testid=\"header-toggle\"\n [checked]=\"headerToggleValue\"\n [disabled]=\"recordReadOnly\"\n [attr.aria-checked]=\"headerToggleValue\"\n (change)=\"toggleHeaderStatus()\"\n />\n <span class=\"ngx-record-editor__header-toggle-text\">{{ headerToggleValue ? 'Yes' : 'No' }}</span>\n </label>\n }\n </div>\n\n <!-- Dismissible info banners (rules `info` action) -->\n @for (banner of infoBanners; track banner.fieldId) {\n <div\n class=\"ngx-record-editor__banner ngx-record-editor__banner--info\"\n role=\"status\"\n [attr.data-testid]=\"'info-banner-' + banner.fieldId\"\n >\n <span>\u2139\uFE0F {{ banner.message }}</span>\n <button\n type=\"button\"\n class=\"ngx-record-editor__banner-dismiss\"\n [attr.data-testid]=\"'dismiss-' + banner.fieldId\"\n [attr.aria-label]=\"'Dismiss: ' + banner.message\"\n (click)=\"dismissInfoBanner(banner.fieldId)\"\n >\u2715</button>\n </div>\n }\n\n <!-- Modification baseline banner -->\n @if (isModified()) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--warning\">\n \uD83D\uDD12 Record modified from session baseline \u2014 unsaved changes pending.\n </div>\n }\n\n <!-- Summary Panel (showOnMinimize fields with interactive jump) -->\n @if (summaryFields().length > 0) {\n <div class=\"ngx-record-editor__summary-panel\" data-testid=\"summary-panel\">\n @for (sf of summaryFields(); track sf.id) {\n <div\n class=\"ngx-record-editor__summary-item\"\n [attr.data-testid]=\"'summary-' + sf.id\"\n (click)=\"jumpToField(sf.id)\"\n [title]=\"'Click to jump to ' + formatFieldLabel(sf)\"\n >\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(sf) }}</span>\n <span class=\"ngx-record-editor__summary-val\">{{ formatFieldValue(sf) }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Section edit bar: one tab is edited at a time, and validated on its own -->\n @if (viewMode && !recordReadOnly) {\n <div class=\"ngx-record-editor__section-bar\">\n @if (isEditingActiveTab) {\n <span class=\"ngx-record-editor__section-hint\">Editing this section</span>\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-section\" (click)=\"cancelSection()\">\n Cancel\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-section\" (click)=\"saveSection()\">\n Save section\n </button>\n } @else {\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"edit-section\" (click)=\"editSection()\">\n Edit section\n </button>\n }\n </div>\n }\n\n @if (Object.keys(sectionErrors()).length > 0) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--error\" role=\"alert\" data-testid=\"section-errors\">\n @for (key of Object.keys(sectionErrors()); track key) {\n <div>\u26A0\uFE0F {{ sectionErrors()[key] }}</div>\n }\n </div>\n }\n\n <!-- Array rows: listed here, edited in the drawer below -->\n @for (arrayField of arrayFieldsForActiveTab; track arrayField.id) {\n <div class=\"ngx-record-editor__rows\" [attr.data-testid]=\"'rows-' + arrayField.id\">\n <div class=\"ngx-record-editor__rows-head\">\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(arrayField) }}</span>\n @if (!sectionReadOnly) {\n <button\n type=\"button\"\n class=\"ngx-form__reset\"\n [attr.data-testid]=\"'add-row-' + arrayField.id\"\n (click)=\"openAddRow(arrayField)\"\n >+ Row</button>\n }\n </div>\n\n @for (row of rowsOf(arrayField); track $index) {\n <div class=\"ngx-record-editor__row\" [attr.data-testid]=\"'row-' + arrayField.id + '-' + $index\">\n <span class=\"ngx-record-editor__row-text\">{{ rowSummary(arrayField, row) }}</span>\n @if (!sectionReadOnly) {\n <button type=\"button\" class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'edit-row-' + arrayField.id + '-' + $index\"\n (click)=\"openEditRow(arrayField, $index)\">Edit</button>\n <button type=\"button\" class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'delete-row-' + arrayField.id + '-' + $index\"\n (click)=\"deleteRow(arrayField, $index)\">Delete</button>\n }\n </div>\n } @empty {\n <p class=\"ngx-record-editor__row-empty\">No rows yet.</p>\n }\n </div>\n }\n\n <!--\n Row drawer. Deliberately outside the tab panel: building a row's FormGroup inside the\n tab view races the tab's own initialisation (the reference hit this with FormArray).\n -->\n @if (inlineRowField(); as rowField) {\n <div class=\"ngx-record-editor__drawer\" data-testid=\"row-drawer\" [formGroup]=\"inlineRowForm!\">\n <div class=\"ngx-record-editor__rows-head\">\n <strong>{{ inlineRowIndex() === null ? 'Add' : 'Edit' }} {{ formatFieldLabel(rowField) }} row</strong>\n </div>\n\n <div class=\"ngx-form__panel\">\n @for (child of rowField.children ?? []; track child.id) {\n <ngx-dynamic-field\n [field]=\"child\"\n [control]=\"inlineRowForm!.get(child.id)!\"\n [config]=\"config\"\n [language]=\"language\"\n [readonly]=\"false\"\n [userRoles]=\"userRoles\"\n [style.gridColumn]=\"'span 12'\"\n />\n }\n </div>\n\n <div class=\"ngx-record-editor__section-bar\">\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-row\" (click)=\"cancelRow()\">Cancel</button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-row\" (click)=\"saveRow()\">Save row</button>\n </div>\n </div>\n }\n\n <!-- Main Dynamic Form -->\n <ngx-dynamic-form\n [config]=\"config\"\n [rules]=\"rules\"\n [initialData]=\"initialData\"\n [originalData]=\"originalBaseline()\"\n [userRoles]=\"userRoles\"\n [language]=\"language\"\n [readonly]=\"sectionReadOnly\"\n [readOnlyFields]=\"readOnlyFields\"\n [showInfoBanners]=\"false\"\n [loading]=\"loading\"\n [error]=\"error\"\n (formChange)=\"onFormChange($event)\"\n (formSubmit)=\"onFormSubmit($event)\"\n (formReset)=\"onFormReset()\"\n (activeTabChange)=\"onActiveTabChange($event)\"\n />\n</div>\n", styles: [".ngx-record-editor{display:flex;flex-direction:column;gap:16px;background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:20px;box-shadow:0 4px 6px -1px #0000000d}.ngx-record-editor__header{display:flex;align-items:center;justify-content:space-between;padding-bottom:16px;border-bottom:1px solid #f3f4f6}.ngx-record-editor__title-group{display:flex;align-items:center;gap:12px}.ngx-record-editor__avatar{width:48px;height:48px;border-radius:50%;object-fit:cover;background:#e0e7ff;color:#4f46e5;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:18px}.ngx-record-editor__title{font-size:20px;font-weight:700;color:#111827;margin:0}.ngx-record-editor__subtitle{font-size:13px;color:#6b7280}.ngx-record-editor__header-toggle{display:flex;align-items:center;gap:8px;font-size:13px;color:#374151}.ngx-record-editor__header-toggle-text{font-weight:600}.ngx-record-editor__banner{padding:10px 14px;border-radius:8px;font-size:13px;font-weight:500}.ngx-record-editor__banner--info{background:#eff6ff;border:1px solid #dbeafe;color:#1d4ed8;display:flex;align-items:center;justify-content:space-between;gap:12px}.ngx-record-editor__banner-dismiss{background:none;border:none;cursor:pointer;color:inherit;font-size:14px;line-height:1;padding:2px 6px;border-radius:4px}.ngx-record-editor__banner-dismiss:hover{background:#0000000f}.ngx-record-editor__banner--error{background:#fef2f2;border:1px solid #fecaca;color:#b91c1c}.ngx-record-editor__section-bar{display:flex;align-items:center;justify-content:flex-end;gap:8px}.ngx-record-editor__section-hint{margin-right:auto;font-size:12px;color:#6b7280}.ngx-record-editor__banner--warning{background:#fffbeb;border:1px solid #fef3c7;color:#b45309}.ngx-record-editor__rows{border:1px solid #f3f4f6;border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:6px}.ngx-record-editor__rows-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.ngx-record-editor__row{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;background:#f9fafb}.ngx-record-editor__row-text{flex:1;font-size:13px;overflow-wrap:anywhere}.ngx-record-editor__row-btn{background:none;border:1px solid #e5e7eb;border-radius:6px;cursor:pointer;font-size:12px;padding:2px 8px}.ngx-record-editor__row-empty{font-size:12px;color:#6b7280;margin:0}.ngx-record-editor__drawer{border:1px solid #dbeafe;background:#f8fafc;border-radius:8px;padding:14px;display:flex;flex-direction:column;gap:12px}.ngx-record-editor__summary-panel{background:#f9fafb;border:1px solid #f3f4f6;border-radius:8px;padding:12px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}.ngx-record-editor__summary-item{display:flex;flex-direction:column;cursor:pointer;padding:6px 10px;border-radius:6px;transition:background .15s}.ngx-record-editor__summary-item:hover{background:#eff6ff}.ngx-record-editor__summary-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#6b7280;font-weight:600}.ngx-record-editor__summary-val{font-size:14px;font-weight:600;color:#1f2937}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: DynamicFormComponent, selector: "ngx-dynamic-form", inputs: ["config", "rules", "initialData", "originalData", "userRoles", "language", "readonly", "readOnlyFields", "showInfoBanners", "loading", "error", "changeDebounceMs", "preview"], outputs: ["formSubmit", "formChange", "formReset", "activeTabChange"] }, { kind: "component", type: DynamicFieldComponent, selector: "ngx-dynamic-field", inputs: ["field", "control", "config", "currentTabId", "language", "readonly", "userRoles"] }] });
|
|
4415
4641
|
}
|
|
4416
4642
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DynamicRecordFormComponent, decorators: [{
|
|
4417
4643
|
type: Component,
|
|
4418
|
-
args: [{ selector: 'ngx-dynamic-record-form', standalone: true, imports: [CommonModule, ReactiveFormsModule, DynamicFormComponent, DynamicFieldComponent], template: "<div class=\"ngx-record-editor\">\r\n <!-- Record Header -->\r\n <div class=\"ngx-record-editor__header\">\r\n <div class=\"ngx-record-editor__title-group\">\r\n @if (profileImageUrl) {\r\n <img\r\n class=\"ngx-record-editor__avatar\"\r\n data-testid=\"header-profile-image\"\r\n [src]=\"profileImageUrl\"\r\n [alt]=\"recordTitle\"\r\n />\r\n } @else {\r\n <div class=\"ngx-record-editor__avatar\" data-testid=\"header-avatar-initial\">\r\n {{ avatarLetter }}\r\n </div>\r\n }\r\n <div>\r\n <h2 class=\"ngx-record-editor__title\" data-testid=\"record-title\">\r\n {{ recordTitle }}\r\n </h2>\r\n <span class=\"ngx-record-editor__subtitle\">Entity: {{ config.entity }}</span>\r\n </div>\r\n </div>\r\n\r\n <!-- Record-level status switch, outside section editing -->\r\n @if (headerToggleField; as toggleField) {\r\n <label class=\"ngx-record-editor__header-toggle\">\r\n <span>{{ formatFieldLabel(toggleField) }}</span>\r\n <input\r\n type=\"checkbox\"\r\n role=\"switch\"\r\n data-testid=\"header-toggle\"\r\n [checked]=\"headerToggleValue\"\r\n [disabled]=\"recordReadOnly\"\r\n [attr.aria-checked]=\"headerToggleValue\"\r\n (change)=\"toggleHeaderStatus()\"\r\n />\r\n <span class=\"ngx-record-editor__header-toggle-text\">{{ headerToggleValue ? 'Yes' : 'No' }}</span>\r\n </label>\r\n }\r\n </div>\r\n\r\n <!-- Dismissible info banners (rules `info` action) -->\r\n @for (banner of infoBanners; track banner.fieldId) {\r\n <div\r\n class=\"ngx-record-editor__banner ngx-record-editor__banner--info\"\r\n role=\"status\"\r\n [attr.data-testid]=\"'info-banner-' + banner.fieldId\"\r\n >\r\n <span>\u2139\uFE0F {{ banner.message }}</span>\r\n <button\r\n type=\"button\"\r\n class=\"ngx-record-editor__banner-dismiss\"\r\n [attr.data-testid]=\"'dismiss-' + banner.fieldId\"\r\n [attr.aria-label]=\"'Dismiss: ' + banner.message\"\r\n (click)=\"dismissInfoBanner(banner.fieldId)\"\r\n >\u2715</button>\r\n </div>\r\n }\r\n\r\n <!-- Modification baseline banner -->\r\n @if (isModified()) {\r\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--warning\">\r\n \uD83D\uDD12 Record modified from session baseline \u2014 unsaved changes pending.\r\n </div>\r\n }\r\n\r\n <!-- Summary Panel (showOnMinimize fields with interactive jump) -->\r\n @if (summaryFields().length > 0) {\r\n <div class=\"ngx-record-editor__summary-panel\" data-testid=\"summary-panel\">\r\n @for (sf of summaryFields(); track sf.id) {\r\n <div\r\n class=\"ngx-record-editor__summary-item\"\r\n [attr.data-testid]=\"'summary-' + sf.id\"\r\n (click)=\"jumpToField(sf.id)\"\r\n [title]=\"'Click to jump to ' + formatFieldLabel(sf)\"\r\n >\r\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(sf) }}</span>\r\n <span class=\"ngx-record-editor__summary-val\">{{ formatFieldValue(sf) }}</span>\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Section edit bar: one tab is edited at a time, and validated on its own -->\r\n @if (viewMode && !recordReadOnly) {\r\n <div class=\"ngx-record-editor__section-bar\">\r\n @if (isEditingActiveTab) {\r\n <span class=\"ngx-record-editor__section-hint\">Editing this section</span>\r\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-section\" (click)=\"cancelSection()\">\r\n Cancel\r\n </button>\r\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-section\" (click)=\"saveSection()\">\r\n Save section\r\n </button>\r\n } @else {\r\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"edit-section\" (click)=\"editSection()\">\r\n Edit section\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n @if (Object.keys(sectionErrors()).length > 0) {\r\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--error\" role=\"alert\" data-testid=\"section-errors\">\r\n @for (key of Object.keys(sectionErrors()); track key) {\r\n <div>\u26A0\uFE0F {{ sectionErrors()[key] }}</div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Array rows: listed here, edited in the drawer below -->\r\n @for (arrayField of arrayFieldsForActiveTab; track arrayField.id) {\r\n <div class=\"ngx-record-editor__rows\" [attr.data-testid]=\"'rows-' + arrayField.id\">\r\n <div class=\"ngx-record-editor__rows-head\">\r\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(arrayField) }}</span>\r\n @if (!sectionReadOnly) {\r\n <button\r\n type=\"button\"\r\n class=\"ngx-form__reset\"\r\n [attr.data-testid]=\"'add-row-' + arrayField.id\"\r\n (click)=\"openAddRow(arrayField)\"\r\n >+ Row</button>\r\n }\r\n </div>\r\n\r\n @for (row of rowsOf(arrayField); track $index) {\r\n <div class=\"ngx-record-editor__row\" [attr.data-testid]=\"'row-' + arrayField.id + '-' + $index\">\r\n <span class=\"ngx-record-editor__row-text\">{{ rowSummary(arrayField, row) }}</span>\r\n @if (!sectionReadOnly) {\r\n <button type=\"button\" class=\"ngx-record-editor__row-btn\"\r\n [attr.data-testid]=\"'edit-row-' + arrayField.id + '-' + $index\"\r\n (click)=\"openEditRow(arrayField, $index)\">Edit</button>\r\n <button type=\"button\" class=\"ngx-record-editor__row-btn\"\r\n [attr.data-testid]=\"'delete-row-' + arrayField.id + '-' + $index\"\r\n (click)=\"deleteRow(arrayField, $index)\">Delete</button>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"ngx-record-editor__row-empty\">No rows yet.</p>\r\n }\r\n </div>\r\n }\r\n\r\n <!--\r\n Row drawer. Deliberately outside the tab panel: building a row's FormGroup inside the\r\n tab view races the tab's own initialisation (the reference hit this with FormArray).\r\n -->\r\n @if (inlineRowField(); as rowField) {\r\n <div class=\"ngx-record-editor__drawer\" data-testid=\"row-drawer\" [formGroup]=\"inlineRowForm!\">\r\n <div class=\"ngx-record-editor__rows-head\">\r\n <strong>{{ inlineRowIndex() === null ? 'Add' : 'Edit' }} {{ formatFieldLabel(rowField) }} row</strong>\r\n </div>\r\n\r\n <div class=\"ngx-form__panel\">\r\n @for (child of rowField.children ?? []; track child.id) {\r\n <ngx-dynamic-field\r\n [field]=\"child\"\r\n [control]=\"inlineRowForm!.get(child.id)!\"\r\n [config]=\"config\"\r\n [language]=\"language\"\r\n [readonly]=\"false\"\r\n [userRoles]=\"userRoles\"\r\n [style.gridColumn]=\"'span 12'\"\r\n />\r\n }\r\n </div>\r\n\r\n <div class=\"ngx-record-editor__section-bar\">\r\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-row\" (click)=\"cancelRow()\">Cancel</button>\r\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-row\" (click)=\"saveRow()\">Save row</button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Main Dynamic Form -->\r\n <ngx-dynamic-form\r\n [config]=\"config\"\r\n [rules]=\"rules\"\r\n [initialData]=\"initialData\"\r\n [originalData]=\"originalBaseline()\"\r\n [userRoles]=\"userRoles\"\r\n [language]=\"language\"\r\n [readonly]=\"sectionReadOnly\"\r\n [readOnlyFields]=\"readOnlyFields\"\r\n [showInfoBanners]=\"false\"\r\n [loading]=\"loading\"\r\n [error]=\"error\"\r\n (formChange)=\"onFormChange($event)\"\r\n (formSubmit)=\"onFormSubmit($event)\"\r\n (formReset)=\"onFormReset()\"\r\n (activeTabChange)=\"onActiveTabChange($event)\"\r\n />\r\n</div>\r\n", styles: [".ngx-record-editor{display:flex;flex-direction:column;gap:16px;background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:20px;box-shadow:0 4px 6px -1px #0000000d}.ngx-record-editor__header{display:flex;align-items:center;justify-content:space-between;padding-bottom:16px;border-bottom:1px solid #f3f4f6}.ngx-record-editor__title-group{display:flex;align-items:center;gap:12px}.ngx-record-editor__avatar{width:48px;height:48px;border-radius:50%;object-fit:cover;background:#e0e7ff;color:#4f46e5;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:18px}.ngx-record-editor__title{font-size:20px;font-weight:700;color:#111827;margin:0}.ngx-record-editor__subtitle{font-size:13px;color:#6b7280}.ngx-record-editor__header-toggle{display:flex;align-items:center;gap:8px;font-size:13px;color:#374151}.ngx-record-editor__header-toggle-text{font-weight:600}.ngx-record-editor__banner{padding:10px 14px;border-radius:8px;font-size:13px;font-weight:500}.ngx-record-editor__banner--info{background:#eff6ff;border:1px solid #dbeafe;color:#1d4ed8;display:flex;align-items:center;justify-content:space-between;gap:12px}.ngx-record-editor__banner-dismiss{background:none;border:none;cursor:pointer;color:inherit;font-size:14px;line-height:1;padding:2px 6px;border-radius:4px}.ngx-record-editor__banner-dismiss:hover{background:#0000000f}.ngx-record-editor__banner--error{background:#fef2f2;border:1px solid #fecaca;color:#b91c1c}.ngx-record-editor__section-bar{display:flex;align-items:center;justify-content:flex-end;gap:8px}.ngx-record-editor__section-hint{margin-right:auto;font-size:12px;color:#6b7280}.ngx-record-editor__banner--warning{background:#fffbeb;border:1px solid #fef3c7;color:#b45309}.ngx-record-editor__rows{border:1px solid #f3f4f6;border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:6px}.ngx-record-editor__rows-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.ngx-record-editor__row{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;background:#f9fafb}.ngx-record-editor__row-text{flex:1;font-size:13px;overflow-wrap:anywhere}.ngx-record-editor__row-btn{background:none;border:1px solid #e5e7eb;border-radius:6px;cursor:pointer;font-size:12px;padding:2px 8px}.ngx-record-editor__row-empty{font-size:12px;color:#6b7280;margin:0}.ngx-record-editor__drawer{border:1px solid #dbeafe;background:#f8fafc;border-radius:8px;padding:14px;display:flex;flex-direction:column;gap:12px}.ngx-record-editor__summary-panel{background:#f9fafb;border:1px solid #f3f4f6;border-radius:8px;padding:12px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}.ngx-record-editor__summary-item{display:flex;flex-direction:column;cursor:pointer;padding:6px 10px;border-radius:6px;transition:background .15s}.ngx-record-editor__summary-item:hover{background:#eff6ff}.ngx-record-editor__summary-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#6b7280;font-weight:600}.ngx-record-editor__summary-val{font-size:14px;font-weight:600;color:#1f2937}\n"] }]
|
|
4644
|
+
args: [{ selector: 'ngx-dynamic-record-form', standalone: true, imports: [CommonModule, ReactiveFormsModule, DynamicFormComponent, DynamicFieldComponent], template: "<div class=\"ngx-record-editor\">\n <!-- Record Header -->\n <div class=\"ngx-record-editor__header\">\n <div class=\"ngx-record-editor__title-group\">\n @if (profileImageUrl) {\n <img\n class=\"ngx-record-editor__avatar\"\n data-testid=\"header-profile-image\"\n [src]=\"profileImageUrl\"\n [alt]=\"recordTitle\"\n />\n } @else {\n <div class=\"ngx-record-editor__avatar\" data-testid=\"header-avatar-initial\">\n {{ avatarLetter }}\n </div>\n }\n <div>\n <h2 class=\"ngx-record-editor__title\" data-testid=\"record-title\">\n {{ recordTitle }}\n </h2>\n <span class=\"ngx-record-editor__subtitle\">Entity: {{ config.entity }}</span>\n </div>\n </div>\n\n <!-- Record-level status switch, outside section editing -->\n @if (headerToggleField; as toggleField) {\n <label class=\"ngx-record-editor__header-toggle\">\n <span>{{ formatFieldLabel(toggleField) }}</span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n data-testid=\"header-toggle\"\n [checked]=\"headerToggleValue\"\n [disabled]=\"recordReadOnly\"\n [attr.aria-checked]=\"headerToggleValue\"\n (change)=\"toggleHeaderStatus()\"\n />\n <span class=\"ngx-record-editor__header-toggle-text\">{{ headerToggleValue ? 'Yes' : 'No' }}</span>\n </label>\n }\n </div>\n\n <!-- Dismissible info banners (rules `info` action) -->\n @for (banner of infoBanners; track banner.fieldId) {\n <div\n class=\"ngx-record-editor__banner ngx-record-editor__banner--info\"\n role=\"status\"\n [attr.data-testid]=\"'info-banner-' + banner.fieldId\"\n >\n <span>\u2139\uFE0F {{ banner.message }}</span>\n <button\n type=\"button\"\n class=\"ngx-record-editor__banner-dismiss\"\n [attr.data-testid]=\"'dismiss-' + banner.fieldId\"\n [attr.aria-label]=\"'Dismiss: ' + banner.message\"\n (click)=\"dismissInfoBanner(banner.fieldId)\"\n >\u2715</button>\n </div>\n }\n\n <!-- Modification baseline banner -->\n @if (isModified()) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--warning\">\n \uD83D\uDD12 Record modified from session baseline \u2014 unsaved changes pending.\n </div>\n }\n\n <!-- Summary Panel (showOnMinimize fields with interactive jump) -->\n @if (summaryFields().length > 0) {\n <div class=\"ngx-record-editor__summary-panel\" data-testid=\"summary-panel\">\n @for (sf of summaryFields(); track sf.id) {\n <div\n class=\"ngx-record-editor__summary-item\"\n [attr.data-testid]=\"'summary-' + sf.id\"\n (click)=\"jumpToField(sf.id)\"\n [title]=\"'Click to jump to ' + formatFieldLabel(sf)\"\n >\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(sf) }}</span>\n <span class=\"ngx-record-editor__summary-val\">{{ formatFieldValue(sf) }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Section edit bar: one tab is edited at a time, and validated on its own -->\n @if (viewMode && !recordReadOnly) {\n <div class=\"ngx-record-editor__section-bar\">\n @if (isEditingActiveTab) {\n <span class=\"ngx-record-editor__section-hint\">Editing this section</span>\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-section\" (click)=\"cancelSection()\">\n Cancel\n </button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-section\" (click)=\"saveSection()\">\n Save section\n </button>\n } @else {\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"edit-section\" (click)=\"editSection()\">\n Edit section\n </button>\n }\n </div>\n }\n\n @if (Object.keys(sectionErrors()).length > 0) {\n <div class=\"ngx-record-editor__banner ngx-record-editor__banner--error\" role=\"alert\" data-testid=\"section-errors\">\n @for (key of Object.keys(sectionErrors()); track key) {\n <div>\u26A0\uFE0F {{ sectionErrors()[key] }}</div>\n }\n </div>\n }\n\n <!-- Array rows: listed here, edited in the drawer below -->\n @for (arrayField of arrayFieldsForActiveTab; track arrayField.id) {\n <div class=\"ngx-record-editor__rows\" [attr.data-testid]=\"'rows-' + arrayField.id\">\n <div class=\"ngx-record-editor__rows-head\">\n <span class=\"ngx-record-editor__summary-label\">{{ formatFieldLabel(arrayField) }}</span>\n @if (!sectionReadOnly) {\n <button\n type=\"button\"\n class=\"ngx-form__reset\"\n [attr.data-testid]=\"'add-row-' + arrayField.id\"\n (click)=\"openAddRow(arrayField)\"\n >+ Row</button>\n }\n </div>\n\n @for (row of rowsOf(arrayField); track $index) {\n <div class=\"ngx-record-editor__row\" [attr.data-testid]=\"'row-' + arrayField.id + '-' + $index\">\n <span class=\"ngx-record-editor__row-text\">{{ rowSummary(arrayField, row) }}</span>\n @if (!sectionReadOnly) {\n <button type=\"button\" class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'edit-row-' + arrayField.id + '-' + $index\"\n (click)=\"openEditRow(arrayField, $index)\">Edit</button>\n <button type=\"button\" class=\"ngx-record-editor__row-btn\"\n [attr.data-testid]=\"'delete-row-' + arrayField.id + '-' + $index\"\n (click)=\"deleteRow(arrayField, $index)\">Delete</button>\n }\n </div>\n } @empty {\n <p class=\"ngx-record-editor__row-empty\">No rows yet.</p>\n }\n </div>\n }\n\n <!--\n Row drawer. Deliberately outside the tab panel: building a row's FormGroup inside the\n tab view races the tab's own initialisation (the reference hit this with FormArray).\n -->\n @if (inlineRowField(); as rowField) {\n <div class=\"ngx-record-editor__drawer\" data-testid=\"row-drawer\" [formGroup]=\"inlineRowForm!\">\n <div class=\"ngx-record-editor__rows-head\">\n <strong>{{ inlineRowIndex() === null ? 'Add' : 'Edit' }} {{ formatFieldLabel(rowField) }} row</strong>\n </div>\n\n <div class=\"ngx-form__panel\">\n @for (child of rowField.children ?? []; track child.id) {\n <ngx-dynamic-field\n [field]=\"child\"\n [control]=\"inlineRowForm!.get(child.id)!\"\n [config]=\"config\"\n [language]=\"language\"\n [readonly]=\"false\"\n [userRoles]=\"userRoles\"\n [style.gridColumn]=\"'span 12'\"\n />\n }\n </div>\n\n <div class=\"ngx-record-editor__section-bar\">\n <button type=\"button\" class=\"ngx-form__reset\" data-testid=\"cancel-row\" (click)=\"cancelRow()\">Cancel</button>\n <button type=\"button\" class=\"ngx-form__submit\" data-testid=\"save-row\" (click)=\"saveRow()\">Save row</button>\n </div>\n </div>\n }\n\n <!-- Main Dynamic Form -->\n <ngx-dynamic-form\n [config]=\"config\"\n [rules]=\"rules\"\n [initialData]=\"initialData\"\n [originalData]=\"originalBaseline()\"\n [userRoles]=\"userRoles\"\n [language]=\"language\"\n [readonly]=\"sectionReadOnly\"\n [readOnlyFields]=\"readOnlyFields\"\n [showInfoBanners]=\"false\"\n [loading]=\"loading\"\n [error]=\"error\"\n (formChange)=\"onFormChange($event)\"\n (formSubmit)=\"onFormSubmit($event)\"\n (formReset)=\"onFormReset()\"\n (activeTabChange)=\"onActiveTabChange($event)\"\n />\n</div>\n", styles: [".ngx-record-editor{display:flex;flex-direction:column;gap:16px;background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:20px;box-shadow:0 4px 6px -1px #0000000d}.ngx-record-editor__header{display:flex;align-items:center;justify-content:space-between;padding-bottom:16px;border-bottom:1px solid #f3f4f6}.ngx-record-editor__title-group{display:flex;align-items:center;gap:12px}.ngx-record-editor__avatar{width:48px;height:48px;border-radius:50%;object-fit:cover;background:#e0e7ff;color:#4f46e5;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:18px}.ngx-record-editor__title{font-size:20px;font-weight:700;color:#111827;margin:0}.ngx-record-editor__subtitle{font-size:13px;color:#6b7280}.ngx-record-editor__header-toggle{display:flex;align-items:center;gap:8px;font-size:13px;color:#374151}.ngx-record-editor__header-toggle-text{font-weight:600}.ngx-record-editor__banner{padding:10px 14px;border-radius:8px;font-size:13px;font-weight:500}.ngx-record-editor__banner--info{background:#eff6ff;border:1px solid #dbeafe;color:#1d4ed8;display:flex;align-items:center;justify-content:space-between;gap:12px}.ngx-record-editor__banner-dismiss{background:none;border:none;cursor:pointer;color:inherit;font-size:14px;line-height:1;padding:2px 6px;border-radius:4px}.ngx-record-editor__banner-dismiss:hover{background:#0000000f}.ngx-record-editor__banner--error{background:#fef2f2;border:1px solid #fecaca;color:#b91c1c}.ngx-record-editor__section-bar{display:flex;align-items:center;justify-content:flex-end;gap:8px}.ngx-record-editor__section-hint{margin-right:auto;font-size:12px;color:#6b7280}.ngx-record-editor__banner--warning{background:#fffbeb;border:1px solid #fef3c7;color:#b45309}.ngx-record-editor__rows{border:1px solid #f3f4f6;border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:6px}.ngx-record-editor__rows-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.ngx-record-editor__row{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;background:#f9fafb}.ngx-record-editor__row-text{flex:1;font-size:13px;overflow-wrap:anywhere}.ngx-record-editor__row-btn{background:none;border:1px solid #e5e7eb;border-radius:6px;cursor:pointer;font-size:12px;padding:2px 8px}.ngx-record-editor__row-empty{font-size:12px;color:#6b7280;margin:0}.ngx-record-editor__drawer{border:1px solid #dbeafe;background:#f8fafc;border-radius:8px;padding:14px;display:flex;flex-direction:column;gap:12px}.ngx-record-editor__summary-panel{background:#f9fafb;border:1px solid #f3f4f6;border-radius:8px;padding:12px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}.ngx-record-editor__summary-item{display:flex;flex-direction:column;cursor:pointer;padding:6px 10px;border-radius:6px;transition:background .15s}.ngx-record-editor__summary-item:hover{background:#eff6ff}.ngx-record-editor__summary-label{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#6b7280;font-weight:600}.ngx-record-editor__summary-val{font-size:14px;font-weight:600;color:#1f2937}\n"] }]
|
|
4419
4645
|
}], propDecorators: { config: [{
|
|
4420
4646
|
type: Input
|
|
4421
4647
|
}], rules: [{
|
|
@@ -4457,5 +4683,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImpo
|
|
|
4457
4683
|
* Generated bundle index. Do not edit.
|
|
4458
4684
|
*/
|
|
4459
4685
|
|
|
4460
|
-
export { COMMON_MODULES_REGISTRY, CONFIG_SOURCE, CascadeDataService, CheckboxFieldComponent, ConfigSourceService, DateFieldComponent, DropdownFieldComponent, DynamicFieldComponent, DynamicFormComponent, DynamicRecordFormComponent, ENTITY_REF_CACHE_STORE, ENTITY_REF_REGISTRY, EntityRefFieldComponent, EntityRefRegistryService, EntityRefSelectionService, EntityReferenceService, FIELD_TYPE_REGISTRY, FIELD_TYPE_SETS, FieldRegistryService, HOOK_REGISTRY, HookRegistryService, InMemoryEntityRefCacheStore, LOOKUP_REGISTRY, LookupRegistryService, MASKED_ROLES, MultiSelectFieldComponent, NumberFieldComponent, RbacService, RulesEvaluationService, SYSTEM_DEFAULT_CAN_EDIT, TextFieldComponent, TextareaFieldComponent, UPLOAD_HANDLER, VALIDATOR_REGISTRY, ValidatorRegistryService, builtInFieldTypes, provideBuiltInFieldTypes, provideFieldTypes, provideNgxDynamicEntity, refreshChoiceOptions };
|
|
4686
|
+
export { ArrayFieldComponent, BooleanFieldComponent, COMMON_MODULES_REGISTRY, CONFIG_SOURCE, CascadeDataService, CheckboxFieldComponent, ConfigSourceService, CurrencyFieldComponent, DateFieldComponent, DropdownFieldComponent, DynamicFieldComponent, DynamicFormComponent, DynamicRecordFormComponent, ENTITY_REF_CACHE_STORE, ENTITY_REF_REGISTRY, EmailFieldComponent, EntityRefFieldComponent, EntityRefRegistryService, EntityRefSelectionService, EntityReferenceService, FIELD_TYPE_REGISTRY, FIELD_TYPE_SETS, FieldRegistryService, FileFieldComponent, GroupFieldComponent, HOOK_REGISTRY, HookRegistryService, ImageFieldComponent, InMemoryEntityRefCacheStore, LOOKUP_REGISTRY, LookupRegistryService, MASKED_ROLES, MonthYearFieldComponent, MultiSelectFieldComponent, NumberFieldComponent, PasswordFieldComponent, RECORD_MIGRATIONS, RadioFieldComponent, RbacService, RulesEvaluationService, SYSTEM_DEFAULT_CAN_EDIT, TextFieldComponent, TextareaFieldComponent, UPLOAD_HANDLER, VALIDATOR_REGISTRY, ValidatorRegistryService, builtInFieldTypes, provideBuiltInFieldTypes, provideFieldTypes, provideNgxDynamicEntity, refreshChoiceOptions };
|
|
4461
4687
|
//# sourceMappingURL=ngx-dynamic-entity.mjs.map
|