@ngx-json-forms/primeng 1.0.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.
@@ -0,0 +1,650 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, output, inject, signal, computed, effect, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import * as i1 from '@angular/forms';
4
+ import { FormGroup, FormArray, ReactiveFormsModule } from '@angular/forms';
5
+ import { NgTemplateOutlet, NgComponentOutlet, NgOptimizedImage, NgClass } from '@angular/common';
6
+ import * as i2 from 'primeng/inputtext';
7
+ import { InputTextModule } from 'primeng/inputtext';
8
+ import * as i3 from 'primeng/inputgroup';
9
+ import { InputGroupModule } from 'primeng/inputgroup';
10
+ import * as i4 from 'primeng/inputgroupaddon';
11
+ import { InputGroupAddonModule } from 'primeng/inputgroupaddon';
12
+ import * as i5 from 'primeng/floatlabel';
13
+ import { FloatLabelModule } from 'primeng/floatlabel';
14
+ import * as i6 from 'primeng/select';
15
+ import { SelectModule } from 'primeng/select';
16
+ import * as i7 from 'primeng/multiselect';
17
+ import { MultiSelectModule } from 'primeng/multiselect';
18
+ import * as i8 from 'primeng/cascadeselect';
19
+ import { CascadeSelectModule } from 'primeng/cascadeselect';
20
+ import * as i9 from 'primeng/autocomplete';
21
+ import { AutoCompleteModule } from 'primeng/autocomplete';
22
+ import * as i10 from 'primeng/fileupload';
23
+ import { FileUploadModule } from 'primeng/fileupload';
24
+ import * as i11 from 'primeng/button';
25
+ import { ButtonModule } from 'primeng/button';
26
+ import { DialogModule } from 'primeng/dialog';
27
+ import * as i12 from 'primeng/divider';
28
+ import { DividerModule } from 'primeng/divider';
29
+ import * as i13 from 'primeng/password';
30
+ import { PasswordModule } from 'primeng/password';
31
+ import * as i14 from 'primeng/datepicker';
32
+ import { DatePickerModule } from 'primeng/datepicker';
33
+ import * as i15 from 'primeng/checkbox';
34
+ import { CheckboxModule } from 'primeng/checkbox';
35
+ import * as i16 from 'primeng/toggleswitch';
36
+ import { ToggleSwitchModule } from 'primeng/toggleswitch';
37
+ import * as i17 from 'primeng/textarea';
38
+ import { TextareaModule } from 'primeng/textarea';
39
+ import * as i18 from 'primeng/slider';
40
+ import { SliderModule } from 'primeng/slider';
41
+ import * as i19 from 'primeng/rating';
42
+ import { RatingModule } from 'primeng/rating';
43
+ import * as i20 from 'primeng/colorpicker';
44
+ import { ColorPickerModule } from 'primeng/colorpicker';
45
+ import * as i21 from 'primeng/radiobutton';
46
+ import { RadioButtonModule } from 'primeng/radiobutton';
47
+ import * as i22 from 'primeng/keyfilter';
48
+ import { KeyFilterModule } from 'primeng/keyfilter';
49
+ import * as i23 from 'primeng/editor';
50
+ import { EditorModule } from 'primeng/editor';
51
+ import { FormEngineService, ImageUploadService, FieldRegistry } from '@ngx-json-forms/core';
52
+
53
+ class NgxJsonFormComponent {
54
+ // ─── Inputs / Outputs ────────────────────────────────────────────────────
55
+ formTitle = input('', ...(ngDevMode ? [{ debugName: "formTitle" }] : /* istanbul ignore next */ []));
56
+ fieldsInput = input([], ...(ngDevMode ? [{ debugName: "fieldsInput" }] : /* istanbul ignore next */ []));
57
+ /** Optional schema input — supersedes `fieldsInput` when provided. */
58
+ schema = input(null, ...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
59
+ /** Only render fields in the given step indices (used by the stepper wrapper). */
60
+ stepFields = input(null, ...(ngDevMode ? [{ debugName: "stepFields" }] : /* istanbul ignore next */ []));
61
+ formSubmit = output();
62
+ formChange = output();
63
+ // ─── Services ────────────────────────────────────────────────────────────
64
+ formService = inject(FormEngineService);
65
+ uploadService = inject(ImageUploadService);
66
+ fieldRegistry = inject(FieldRegistry);
67
+ // ─── Internal State ──────────────────────────────────────────────────────
68
+ fields = signal([], ...(ngDevMode ? [{ debugName: "fields" }] : /* istanbul ignore next */ []));
69
+ formGroup = signal(new FormGroup({}), ...(ngDevMode ? [{ debugName: "formGroup" }] : /* istanbul ignore next */ []));
70
+ ready = signal(false, ...(ngDevMode ? [{ debugName: "ready" }] : /* istanbul ignore next */ []));
71
+ /** Async-loaded options keyed by formControlName */
72
+ asyncOptions = signal({}, ...(ngDevMode ? [{ debugName: "asyncOptions" }] : /* istanbul ignore next */ []));
73
+ formStatusTick = signal(0, ...(ngDevMode ? [{ debugName: "formStatusTick" }] : /* istanbul ignore next */ []));
74
+ focusedMap = signal({}, ...(ngDevMode ? [{ debugName: "focusedMap" }] : /* istanbul ignore next */ []));
75
+ /** Subscriptions for async option re-loads; disposed on every rebuild. */
76
+ optionLoaderSubs = [];
77
+ // ─── Derived ─────────────────────────────────────────────────────────────
78
+ visibleFields = computed(() => {
79
+ this.formStatusTick();
80
+ this.formService.patchTick();
81
+ const values = this.formGroup().getRawValue();
82
+ const source = this.stepFields() ?? this.fields();
83
+ return source.filter((f) => {
84
+ if (f.config.attributes.visible === false)
85
+ return false;
86
+ return this.formService.evaluateShowWhen(f, values);
87
+ });
88
+ }, ...(ngDevMode ? [{ debugName: "visibleFields" }] : /* istanbul ignore next */ []));
89
+ /**
90
+ * Track patchTick so .valid (a plain object property) re-reads after every
91
+ * form change. Without this, the submit-button disabled binding stays stuck
92
+ * on the FormGroup's initial (invalid) state.
93
+ */
94
+ formValid = computed(() => {
95
+ this.formService.patchTick();
96
+ this.formStatusTick();
97
+ return this.formGroup().valid;
98
+ }, ...(ngDevMode ? [{ debugName: "formValid" }] : /* istanbul ignore next */ []));
99
+ placeholderVisibilityMap = {};
100
+ /**
101
+ * Resolve the placeholder string to render for a field. Safe for nested
102
+ * fields (repeater itemFields, group groupFields) whose formControlName
103
+ * isn't pre-registered in `placeholderVisibilityMap` — those fall back to
104
+ * the raw `field.placeholder` since they don't participate in floatLabel.
105
+ */
106
+ placeholderFor(field) {
107
+ const name = field.formControlName;
108
+ if (!name)
109
+ return field.placeholder ?? null;
110
+ const sig = this.placeholderVisibilityMap[name];
111
+ if (!sig)
112
+ return field.placeholder ?? null;
113
+ return sig() ? (field.placeholder ?? null) : null;
114
+ }
115
+ errorMap = computed(() => {
116
+ this.formStatusTick();
117
+ this.formService.patchTick();
118
+ const errors = {};
119
+ const group = this.formGroup();
120
+ if (!group)
121
+ return errors;
122
+ for (const field of this.fields()) {
123
+ if (!field.formControlName)
124
+ continue;
125
+ const control = group.get(field.formControlName);
126
+ errors[field.formControlName] = this.formService.resolveError(field, control);
127
+ }
128
+ return errors;
129
+ }, ...(ngDevMode ? [{ debugName: "errorMap" }] : /* istanbul ignore next */ []));
130
+ // ─── Lifecycle ───────────────────────────────────────────────────────────
131
+ constructor() {
132
+ effect(() => {
133
+ this.ready.set(false);
134
+ const schemaIn = this.schema();
135
+ const incoming = schemaIn?.fields
136
+ ? schemaIn.fields
137
+ : schemaIn?.steps
138
+ ? schemaIn.steps.flatMap((s) => s.fields)
139
+ : this.fieldsInput();
140
+ if (!incoming.length)
141
+ return;
142
+ const sorted = [...incoming].sort((a, b) => {
143
+ const oa = a.layout?.order ?? Number.MAX_SAFE_INTEGER;
144
+ const ob = b.layout?.order ?? Number.MAX_SAFE_INTEGER;
145
+ return oa - ob;
146
+ });
147
+ this.fields.set(sorted);
148
+ this.buildForm(sorted, schemaIn ?? undefined);
149
+ });
150
+ effect(() => {
151
+ this.formService.patchTick();
152
+ if (!this.ready())
153
+ return;
154
+ this.fields.set(this.formService.fields());
155
+ this.uploadService.syncPreviews(this.fields(), this.formGroup());
156
+ });
157
+ }
158
+ // ─── Form Builder ────────────────────────────────────────────────────────
159
+ buildForm(fields, schema) {
160
+ // Tear down anything tied to the previous form group before we rebuild.
161
+ this.disposeOptionLoaderSubs();
162
+ this.placeholderVisibilityMap = {};
163
+ const group = this.formService.buildFormGroup(fields);
164
+ this.formGroup.set(group);
165
+ // Register synchronously so prior subs are disposed BEFORE we add new
166
+ // computed-field listeners below — otherwise register() would wipe them.
167
+ this.formService.register(group, fields, schema);
168
+ for (const field of fields) {
169
+ if (!field.formControlName)
170
+ continue;
171
+ this.placeholderVisibilityMap[field.formControlName] = computed(() => {
172
+ if (!field.floatLabel)
173
+ return true;
174
+ const control = this.formGroup()?.get(field.formControlName);
175
+ if (!control)
176
+ return false;
177
+ const v = control.value;
178
+ const hasValue = v !== null && v !== undefined && v !== '' && (!Array.isArray(v) || v.length > 0);
179
+ return this.focusedMap()[field.formControlName] === true && !hasValue;
180
+ });
181
+ }
182
+ if (schema?.crossFieldValidators?.length) {
183
+ this.formService.applyCrossFieldValidators(group, schema.crossFieldValidators);
184
+ }
185
+ this.formService.setupComputedFields(group, fields);
186
+ // Bootstrap async options for select-like fields
187
+ for (const f of fields) {
188
+ const a = f.config.attributes;
189
+ if (a.optionsLoader && f.formControlName) {
190
+ void this.loadAsyncOptions(f);
191
+ for (const dep of a.optionsDependsOn ?? []) {
192
+ const depCtrl = group.get(dep);
193
+ if (!depCtrl)
194
+ continue;
195
+ this.optionLoaderSubs.push(depCtrl.valueChanges.subscribe(() => void this.loadAsyncOptions(f)));
196
+ }
197
+ }
198
+ }
199
+ queueMicrotask(() => {
200
+ this.ready.set(true);
201
+ this.uploadService.syncPreviews(this.fields(), this.formGroup());
202
+ });
203
+ }
204
+ disposeOptionLoaderSubs() {
205
+ for (const s of this.optionLoaderSubs)
206
+ s.unsubscribe();
207
+ this.optionLoaderSubs = [];
208
+ }
209
+ async loadAsyncOptions(field) {
210
+ const a = field.config.attributes;
211
+ if (!a.optionsLoader || !field.formControlName)
212
+ return;
213
+ const formValue = this.formGroup().getRawValue();
214
+ const context = {};
215
+ for (const dep of a.optionsDependsOn ?? [])
216
+ context[dep] = formValue[dep];
217
+ this.formService.updateFieldAttributes(field.formControlName, { loading: true });
218
+ const opts = await this.formService.resolveDependentOptions(a.optionsLoader, context, formValue);
219
+ this.asyncOptions.update((m) => ({ ...m, [field.formControlName]: opts }));
220
+ this.formService.updateFieldAttributes(field.formControlName, { loading: false, options: opts });
221
+ }
222
+ // ─── Field Registry resolver (used by custom inputType) ──────────────────
223
+ customRenderer(field) {
224
+ return this.fieldRegistry.getRenderer(field.config.attributes.inputType);
225
+ }
226
+ customRendererInputs(field) {
227
+ return { field, formGroup: this.formGroup() };
228
+ }
229
+ // ─── Date helpers ────────────────────────────────────────────────────────
230
+ minDateFor(field) {
231
+ const a = field.config.attributes;
232
+ if (a.minDate)
233
+ return a.minDate;
234
+ if (a.minToday) {
235
+ const d = new Date();
236
+ d.setDate(d.getDate() + (a.minOffsetDays ?? 0));
237
+ return d;
238
+ }
239
+ return undefined;
240
+ }
241
+ maxDateFor(field) {
242
+ const a = field.config.attributes;
243
+ if (a.maxDate)
244
+ return a.maxDate;
245
+ if (a.maxToday) {
246
+ const d = new Date();
247
+ d.setDate(d.getDate() + (a.maxOffsetDays ?? 0));
248
+ return d;
249
+ }
250
+ return undefined;
251
+ }
252
+ // ─── Effective options resolver ──────────────────────────────────────────
253
+ optionsFor(field) {
254
+ const a = field.config.attributes;
255
+ if (field.formControlName && this.asyncOptions()[field.formControlName]) {
256
+ return this.asyncOptions()[field.formControlName];
257
+ }
258
+ return a.options ?? [];
259
+ }
260
+ // ─── Repeater helpers ────────────────────────────────────────────────────
261
+ getArray(name) {
262
+ const c = this.formGroup().get(name);
263
+ return c instanceof FormArray ? c : null;
264
+ }
265
+ addRepeaterRow(field) {
266
+ if (!field.formControlName)
267
+ return;
268
+ const arr = this.getArray(field.formControlName);
269
+ if (!arr)
270
+ return;
271
+ if (field.config.attributes.maxRows && arr.length >= field.config.attributes.maxRows)
272
+ return;
273
+ this.formService.addArrayItem(field.formControlName);
274
+ this.triggerTick();
275
+ }
276
+ removeRepeaterRow(field, i) {
277
+ if (!field.formControlName)
278
+ return;
279
+ const arr = this.getArray(field.formControlName);
280
+ if (!arr)
281
+ return;
282
+ if (field.config.attributes.minRows && arr.length <= field.config.attributes.minRows)
283
+ return;
284
+ this.formService.removeArrayItem(field.formControlName, i);
285
+ this.triggerTick();
286
+ }
287
+ rowGroup(field, i) {
288
+ return this.getArray(field.formControlName)?.at(i);
289
+ }
290
+ /**
291
+ * DOM id for a field's input element. Scoped by row index when the field
292
+ * lives inside a repeater so the same `formControlName` across rows
293
+ * doesn't produce duplicate ids (HTML spec violation + breaks
294
+ * <label for>, aria-describedby, and document.querySelector lookups).
295
+ */
296
+ inputId(field, suffix) {
297
+ const base = (field.formControlName ?? '') + 'Id';
298
+ return suffix !== undefined && suffix !== null ? `${base}_${suffix}` : base;
299
+ }
300
+ /**
301
+ * Resolve the validation message for a single field inside a repeater row.
302
+ * Mirrors errorMap() but for FormGroup rows that aren't tracked in the
303
+ * top-level fields signal.
304
+ */
305
+ resolveRowError(row, sub) {
306
+ this.formService.patchTick();
307
+ if (!sub.formControlName)
308
+ return null;
309
+ const ctrl = row.get(sub.formControlName);
310
+ return this.formService.resolveError(sub, ctrl);
311
+ }
312
+ // ─── Events ──────────────────────────────────────────────────────────────
313
+ /** Central event handler. Accepts `unknown` because PrimeNG emits custom event objects. */
314
+ async onEvent(field, event, eventType) {
315
+ this.triggerTick();
316
+ const rawType = eventType ?? event?.type;
317
+ let type = rawType;
318
+ if (type === 'focus')
319
+ this.focusedMap.update((m) => ({ ...m, [field.formControlName]: true }));
320
+ if (type === 'blur')
321
+ this.focusedMap.update((m) => ({ ...m, [field.formControlName]: false }));
322
+ if (type === 'uploadHandler') {
323
+ await this.uploadService.handleUpload(field, event, this.formGroup(), this.formService, () => this.emitChange(field, 'uploadHandler', event));
324
+ return;
325
+ }
326
+ if (type === 'inputKeydown' && field.config.attributes.inputType === 'autocomplete') {
327
+ const ke = event;
328
+ if (ke.key === 'Enter') {
329
+ const inputValue = ke.target.value?.trim();
330
+ if (inputValue) {
331
+ const tag = {
332
+ [field.config.attributes.optionLabel]: inputValue,
333
+ [field.config.attributes.optionValue]: inputValue,
334
+ };
335
+ const existing = [...(field.config.attributes.suggestions ?? []), tag];
336
+ this.formService.updateFieldAttributes(field.formControlName, { suggestions: existing });
337
+ this.formGroup().get(field.formControlName)?.setValue(existing);
338
+ type = 'add';
339
+ }
340
+ }
341
+ }
342
+ if (type === 'unselect' && field.config.attributes.inputType === 'autocomplete') {
343
+ const evtValue = event.value;
344
+ const remaining = (field.config.attributes.suggestions ?? []).filter((v) => evtValue !== v);
345
+ this.formService.updateFieldAttributes(field.formControlName, { suggestions: remaining });
346
+ this.formGroup().get(field.formControlName)?.setValue(remaining);
347
+ }
348
+ const allowed = field.config.attributes.acceptedEvents ?? [];
349
+ if (!allowed.includes(type))
350
+ return;
351
+ const payload = this.buildPayload(field, type, event);
352
+ if (type === 'click') {
353
+ const role = field.config.attributes.buttonRole;
354
+ if (role === 'reset') {
355
+ this.formService.reset();
356
+ this.formChange.emit(payload);
357
+ return;
358
+ }
359
+ if (role === 'cancel' || role === 'custom') {
360
+ this.formChange.emit(payload);
361
+ return;
362
+ }
363
+ if (!this.formValid())
364
+ this.formGroup().markAllAsTouched();
365
+ this.formSubmit.emit({ ...payload, values: this.formService.buildSubmitPayload() });
366
+ }
367
+ else {
368
+ this.formChange.emit(payload);
369
+ }
370
+ }
371
+ removeImage(field, image) {
372
+ this.uploadService.removeImage(field, image, this.formGroup(), this.formService, () => this.emitChange(field, 'uploadHandler', new Event('remove')));
373
+ }
374
+ // ─── Helpers ─────────────────────────────────────────────────────────────
375
+ buildPayload(field, type, originalEvent) {
376
+ return {
377
+ field,
378
+ values: this.formGroup().getRawValue(),
379
+ valid: this.formValid(),
380
+ type,
381
+ originalEvent,
382
+ };
383
+ }
384
+ emitChange(field, type, event) {
385
+ this.formChange.emit(this.buildPayload(field, type, event));
386
+ }
387
+ triggerTick() {
388
+ this.formStatusTick.update((v) => v + 1);
389
+ }
390
+ getControl(name) {
391
+ return this.formGroup().get(name);
392
+ }
393
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgxJsonFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
394
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: NgxJsonFormComponent, isStandalone: true, selector: "ngx-json-form", inputs: { formTitle: { classPropertyName: "formTitle", publicName: "formTitle", isSignal: true, isRequired: false, transformFunction: null }, fieldsInput: { classPropertyName: "fieldsInput", publicName: "fieldsInput", isSignal: true, isRequired: false, transformFunction: null }, schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: false, transformFunction: null }, stepFields: { classPropertyName: "stepFields", publicName: "stepFields", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formSubmit: "formSubmit", formChange: "formChange" }, ngImport: i0, template: "@if (formTitle().length) {\n <div class=\"ngx-form-title\">{{ formTitle() }}</div>\n}\n\n@if (ready()) {\n <form [formGroup]=\"formGroup()\" class=\"ngx-form-grid\">\n @for (field of visibleFields(); track field.formControlName ?? $index) {\n <div\n [class]=\"\n 'ngx-col-' +\n (field.layout?.columnSpan ?? 12) +\n ' ' +\n (field.layout?.wrapperClass ?? '')\n \"\n [style]=\"field.layout?.wrapperStyle ?? {}\"\n [style.display]=\"field.config.attributes.isHidden ? 'none' : 'block'\"\n >\n <!-- Float Label wrapper -->\n @if (field.floatLabel) {\n <p-floatlabel [variant]=\"field.floatVariant ?? 'on'\">\n <div class=\"ngx-field-inner\">\n <ng-container\n [ngTemplateOutlet]=\"fieldTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: field, formGroup: formGroup() }\"\n />\n </div>\n <label\n [attr.for]=\"field.formControlName + 'Id'\"\n [attr.id]=\"field.formControlName + 'Label'\"\n >\n @if (field.labelIconPos === 'left') {\n <i [class]=\"field.labelIcon\"></i>\n }\n {{ field.label }}\n @if (field.labelIconPos === 'right') {\n <i [class]=\"field.labelIcon\"></i>\n }\n @if (field.validations?.rules?.required) {\n <span class=\"ngx-required\">*</span>\n }\n </label>\n </p-floatlabel>\n }\n\n <!-- Toggle card layout -->\n @else if (\n field.config.attributes.cardLayout &&\n field.config.attributes.inputType === 'toggle'\n ) {\n <div\n class=\"ngx-toggle-card\"\n [style.--toggle-color]=\"field.config.attributes.cardIconColor\"\n >\n <div\n class=\"ngx-toggle-card-icon\"\n [style.background]=\"\n field.config.attributes.cardIconBg ??\n 'var(--p-primary-50, #eff6ff)'\n \"\n [style.color]=\"\n field.config.attributes.cardIconColor ??\n 'var(--p-primary-500, #3b82f6)'\n \"\n >\n <i [class]=\"field.config.attributes.cardIcon ?? 'pi pi-bell'\"></i>\n </div>\n <div class=\"ngx-toggle-card-body\">\n <span class=\"ngx-toggle-card-label\">\n {{ field.label }}\n @if (field.validations?.rules?.required) {\n <span class=\"ngx-required\">*</span>\n }\n </span>\n @if (field.config.attributes.info?.length) {\n <span\n class=\"ngx-toggle-card-desc\"\n [innerHTML]=\"field.config.attributes.info\"\n ></span>\n }\n </div>\n <p-toggleswitch\n [formControlName]=\"field.formControlName!\"\n [inputId]=\"field.formControlName + 'Id'\"\n (onChange)=\"onEvent(field, $event, 'change')\"\n />\n </div>\n <!--\n Always reserve one line of space for the error so the row height\n doesn't change when validation fires (otherwise sibling fields\n jump down when the error appears).\n -->\n @if (field.validations?.rules) {\n <small\n class=\"ngx-error\"\n [class.ngx-error--placeholder]=\"!errorMap()[field.formControlName!]\"\n [attr.id]=\"field.formControlName + 'Err'\"\n >{{ errorMap()[field.formControlName!] || '\u00A0' }}</small>\n }\n }\n\n <!-- Normal label wrapper -->\n @else {\n @if (field.label && field.label.length) {\n <label\n class=\"ngx-field-label\"\n [attr.for]=\"field.formControlName + 'Id'\"\n [attr.id]=\"field.formControlName + 'Label'\"\n >\n @if (field.labelIconPos === 'left') {\n <i [class]=\"field.labelIcon\"></i>\n }\n {{ field.label }}\n @if (field.labelIconPos === 'right') {\n <i [class]=\"field.labelIcon\"></i>\n }\n @if (field.validations?.rules?.required) {\n <span class=\"ngx-required\">*</span>\n }\n </label>\n }\n <div class=\"ngx-field-inner\">\n <ng-container\n [ngTemplateOutlet]=\"fieldTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: field, formGroup: formGroup() }\"\n />\n </div>\n\n <!--\n Validation error \u2014 always reserve one line of space (placeholder\n class makes it invisible when there's no error) so triggering a\n required/pattern message doesn't push the row below it down.\n -->\n @if (field.validations?.rules) {\n <small\n class=\"ngx-error\"\n [class.ngx-error--placeholder]=\"!errorMap()[field.formControlName!]\"\n [attr.id]=\"field.formControlName + 'Err'\"\n >{{ errorMap()[field.formControlName!] || '\u00A0' }}</small>\n }\n\n <!-- Info / hint -->\n @if (field.config.attributes.info?.length) {\n <small\n class=\"ngx-hint\"\n [attr.id]=\"field.formControlName + 'Hint'\"\n [innerHTML]=\"field.config.attributes.info\"\n ></small>\n }\n }\n </div>\n }\n </form>\n}\n\n<!-- \u2500\u2500\u2500 Field Renderer Template \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n<!--\n `formGroup` is passed in via the template context so each rendering site\n binds the right group: the root FormGroup for top-level fields, the row's\n FormGroup for repeater rows. Hard-coding `formGroup()` here would override\n row-level bindings and route nested formControlName lookups to the root,\n which is the bug we're fixing.\n\n Known issue: during initial CD passes Angular logs NG01050 spam because\n the FormGroup directive on this ng-container materialises after children\n formControlName directives query their parent ControlContainer. Bindings\n recover and form values flow correctly afterwards. Cleaner fix would be\n to avoid the ngTemplateOutlet + context + nested formGroup combination\n altogether (e.g. inline the @switch at each call site).\n-->\n<ng-template #fieldTpl let-field let-fg=\"formGroup\" let-idSuffix=\"idSuffix\">\n <ng-container [formGroup]=\"fg\">\n @switch (field.config.attributes.inputType) {\n <!-- \u2500\u2500 Custom field (registry) \u2500\u2500 -->\n @case (customRenderer(field) ? field.config.attributes.inputType : '__never__') {\n <ng-container\n *ngComponentOutlet=\"customRenderer(field)!; inputs: customRendererInputs(field)\"\n />\n }\n\n <!-- \u2500\u2500 Group (nested FormGroup) \u2500\u2500 -->\n @case ('group') {\n <fieldset\n class=\"ngx-field-group\"\n [attr.formGroupName]=\"field.formControlName\"\n >\n @for (sub of field.config.attributes.groupFields ?? []; track sub.formControlName ?? $index) {\n <div class=\"ngx-field-group-row\">\n <ng-container\n [ngTemplateOutlet]=\"fieldTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: sub, formGroup: formGroup() }\"\n />\n </div>\n }\n </fieldset>\n }\n\n <!-- \u2500\u2500 Repeater (FormArray) \u2500\u2500 -->\n @case ('repeater') {\n <div class=\"ngx-repeater\">\n @for (row of getArray(field.formControlName!)?.controls ?? []; track $index) {\n @let rowIndex = $index;\n <div class=\"ngx-repeater-row\" [formGroup]=\"$any(row)\">\n <div class=\"ngx-repeater-row-fields\">\n @for (sub of field.config.attributes.itemFields ?? []; track sub.formControlName ?? $index) {\n <div\n [class]=\"\n 'ngx-col-' + (sub.layout?.columnSpan ?? 12)\n \"\n >\n @if (sub.label) {\n <label\n class=\"ngx-field-label\"\n [attr.for]=\"inputId(sub, rowIndex)\"\n >\n {{ sub.label }}\n @if (sub.validations?.rules?.required) {\n <span class=\"ngx-required\">*</span>\n }\n </label>\n }\n <ng-container\n [ngTemplateOutlet]=\"fieldTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: sub, formGroup: $any(row), idSuffix: rowIndex }\"\n />\n @if (sub.validations?.rules && sub.formControlName) {\n <small\n class=\"ngx-error\"\n [class.ngx-error--placeholder]=\"!resolveRowError($any(row), sub)\"\n >{{ resolveRowError($any(row), sub) || '\u00A0' }}</small>\n }\n </div>\n }\n </div>\n <button\n type=\"button\"\n class=\"ngx-repeater-remove\"\n pButton\n [icon]=\"'pi pi-trash'\"\n [rounded]=\"true\"\n [text]=\"true\"\n (click)=\"removeRepeaterRow(field, $index)\"\n ></button>\n </div>\n }\n <button\n pButton\n type=\"button\"\n class=\"ngx-repeater-add\"\n [icon]=\"'pi pi-plus'\"\n [label]=\"field.config.attributes.addLabel ?? 'Add row'\"\n [text]=\"true\"\n (click)=\"addRepeaterRow(field)\"\n ></button>\n </div>\n }\n\n <!-- \u2500\u2500 Dependent / Cascading Dropdown \u2500\u2500 -->\n @case ('dependentDropdown') {\n <p-cascadeselect\n [id]=\"inputId(field, idSuffix)\"\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [options]=\"$any(optionsFor(field))\"\n [optionLabel]=\"field.config.attributes.optionLabel ?? 'label'\"\n [optionValue]=\"field.config.attributes.optionValue ?? 'value'\"\n [optionGroupLabel]=\"field.config.attributes.optionGroupLabel ?? 'label'\"\n [optionGroupChildren]=\"\n $any(field.config.attributes.optionGroupChildren ?? ['children'])\n \"\n [placeholder]=\"field.placeholder ?? ''\"\n appendTo=\"body\"\n (onChange)=\"onEvent(field, $event, 'change')\"\n (onShow)=\"onEvent(field, $event, 'show')\"\n (onHide)=\"onEvent(field, $event, 'hide')\"\n />\n }\n\n <!-- \u2500\u2500 Editor (rich text) \u2500\u2500 -->\n @case ('editor') {\n <p-editor\n [formControlName]=\"field.formControlName!\"\n [style]=\"{ height: (field.config.attributes['height'] ?? '180px') }\"\n (onTextChange)=\"onEvent(field, $event, 'change')\"\n ></p-editor>\n }\n\n <!-- \u2500\u2500 Confirm password \u2500\u2500 -->\n @case ('confirmPassword') {\n <p-password\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [feedback]=\"false\"\n [toggleMask]=\"field.config.attributes.toggleMask ?? true\"\n [placeholder]=\"field.placeholder ?? ''\"\n appendTo=\"body\"\n (onBlur)=\"onEvent(field, $event, 'blur')\"\n />\n }\n\n <!-- \u2500\u2500 File Upload \u2500\u2500 -->\n @case ('fileUpload') {\n <div>\n <div\n [class]=\"field.config.attributes.isInline ? 'ngx-file-inline' : ''\"\n >\n <p-fileUpload\n mode=\"basic\"\n customUpload\n [id]=\"inputId(field, idSuffix)\"\n [multiple]=\"field.config.attributes.multiple\"\n [chooseIcon]=\"field.config.attributes.chooseIcon ?? 'pi pi-upload'\"\n [accept]=\"field.config.attributes.accept ?? 'image/*'\"\n [maxFileSize]=\"field.config.attributes.maxFileSize ?? 1000000\"\n [auto]=\"true\"\n [chooseLabel]=\"field.config.attributes.chooseLabel ?? 'Upload'\"\n (uploadHandler)=\"onEvent(field, $event, 'uploadHandler')\"\n />\n\n @if (uploadService.imagePreviews()[field.formControlName!]?.length) {\n <div class=\"ngx-image-list\">\n @for (\n image of uploadService.imagePreviews()[field.formControlName!];\n let i = $index;\n track i\n ) {\n <div class=\"ngx-image-card\">\n <i\n class=\"pi pi-times ngx-image-remove\"\n (click)=\"removeImage(field, image)\"\n ></i>\n <div class=\"ngx-image-wrapper\">\n @if (!uploadService.isBase64(image)) {\n <img\n fill\n sizes=\"100\"\n [ngSrc]=\"image\"\n (load)=\"uploadService.onLoad(image)\"\n (error)=\"uploadService.onLoad(image)\"\n [class.loading]=\"uploadService.isLoading(image)\"\n [class.loaded]=\"!uploadService.isLoading(image)\"\n class=\"ngx-preview-img\"\n alt=\"preview\"\n />\n } @else {\n <img\n fill\n sizes=\"100\"\n [src]=\"image\"\n (load)=\"uploadService.onLoad(image)\"\n (error)=\"uploadService.onLoad(image)\"\n [class.loading]=\"uploadService.isLoading(image)\"\n [class.loaded]=\"!uploadService.isLoading(image)\"\n class=\"ngx-preview-img\"\n alt=\"preview\"\n />\n }\n @if (uploadService.isLoading(image)) {\n <div class=\"ngx-img-skeleton\"></div>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n\n <!-- \u2500\u2500 Static Text \u2500\u2500 -->\n @case ('staticText') {\n <span\n (click)=\"onEvent(field, $event, 'staticTextClick')\"\n [innerHTML]=\"field.config.attributes.value\"\n ></span>\n }\n\n <!-- \u2500\u2500 Divider \u2500\u2500 -->\n @case ('divider') {\n <p-divider\n [layout]=\"field.config.attributes.dividerLayout\"\n [type]=\"field.config.attributes.dividerType\"\n [align]=\"field.config.attributes.dividerAlign\"\n >\n @if (field.config.attributes.value) {\n <span [innerHTML]=\"field.config.attributes.value\"></span>\n }\n </p-divider>\n }\n\n <!-- \u2500\u2500 Button \u2500\u2500 -->\n @case ('button') {\n <div class=\"ngx-btn-wrap\">\n <button\n pButton\n type=\"button\"\n [id]=\"inputId(field, idSuffix)\"\n [label]=\"field.btnLabel ?? ''\"\n [icon]=\"field.config.attributes.icon ?? ''\"\n [iconPos]=\"field.config.attributes.iconPosition ?? 'left'\"\n [tabindex]=\"field.tabIndex ?? 0\"\n [disabled]=\"\n field.config.attributes.buttonRole === 'submit' ||\n field.config.attributes.type === 'submit'\n ? !formValid()\n : !!field.config.attributes.disabled\n \"\n [class.p-button-outlined]=\"\n field.config.attributes.variant === 'secondary'\n \"\n [class.p-button-text]=\"field.config.attributes.text === true\"\n [class.p-button-rounded]=\"field.config.attributes.rounded === true\"\n [class.p-button-sm]=\"field.config.attributes.size === 'sm'\"\n [class.p-button-lg]=\"field.config.attributes.size === 'lg'\"\n (click)=\"onEvent(field, $event)\"\n ></button>\n </div>\n }\n\n <!-- \u2500\u2500 Everything else inside p-inputgroup \u2500\u2500 -->\n @default {\n <p-inputgroup>\n @if (\n field.config.attributes.fieldPos === 'left' &&\n field.config.attributes.fieldIcon\n ) {\n <p-inputgroup-addon>\n <i [class]=\"field.config.attributes.fieldIcon\">\n {{ field.config.attributes.fieldIconText ?? '' }}\n </i>\n </p-inputgroup-addon>\n }\n\n @switch (field.config.attributes.inputType) {\n <!-- text / email / number / url -->\n @case ('text') {\n <input\n pInputText\n [id]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [type]=\"field.config.attributes.type ?? 'text'\"\n [tabindex]=\"field.tabIndex ?? 0\"\n [attr.placeholder]=\"placeholderFor(field)\"\n [attr.aria-describedby]=\"\n (errorMap()[field.formControlName!] ? field.formControlName + 'Err ' : '') +\n (field.config.attributes.info ? field.formControlName + 'Hint' : '')\n \"\n [pKeyFilter]=\"field.config.attributes.keyfilter\"\n (change)=\"onEvent(field, $event)\"\n (input)=\"onEvent(field, $event)\"\n (focus)=\"onEvent(field, $event)\"\n (blur)=\"onEvent(field, $event)\"\n (keyup)=\"onEvent(field, $event)\"\n (keydown)=\"onEvent(field, $event)\"\n (keypress)=\"onEvent(field, $event)\"\n />\n }\n\n <!-- password -->\n @case ('password') {\n <p-password\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [feedback]=\"field.config.attributes.feedback\"\n [toggleMask]=\"field.config.attributes.toggleMask\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n appendTo=\"body\"\n (onBlur)=\"onEvent(field, $event, 'blur')\"\n (onFocus)=\"onEvent(field, $event, 'focus')\"\n >\n @if (field.config.attributes.templateHeader) {\n <ng-container\n [ngTemplateOutlet]=\"field.config.attributes.templateHeader\"\n />\n }\n @if (field.config.attributes.templateFooter) {\n <ng-container\n [ngTemplateOutlet]=\"field.config.attributes.templateFooter\"\n />\n }\n </p-password>\n }\n\n <!-- textarea -->\n @case ('textarea') {\n <textarea\n pTextarea\n [id]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [rows]=\"field.config.attributes.rows ?? 3\"\n [cols]=\"field.config.attributes.cols ?? 30\"\n [autoResize]=\"field.config.attributes.autoResize\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n (change)=\"onEvent(field, $event)\"\n (input)=\"onEvent(field, $event)\"\n (focus)=\"onEvent(field, $event)\"\n (blur)=\"onEvent(field, $event)\"\n >\n </textarea>\n }\n\n <!-- select -->\n @case ('select') {\n <p-select\n [id]=\"inputId(field, idSuffix)\"\n [inputId]=\"inputId(field, idSuffix)\"\n [attr.aria-labelledby]=\"field.formControlName + 'Label'\"\n [formControlName]=\"field.formControlName!\"\n [options]=\"$any(optionsFor(field))\"\n [optionLabel]=\"field.config.attributes.optionLabel\"\n [optionValue]=\"field.config.attributes.optionValue\"\n [placeholder]=\"placeholderFor(field) ?? ''\"\n [showClear]=\"field.config.attributes.showClear\"\n [filter]=\"field.config.attributes.filter\"\n [filterBy]=\"field.config.attributes.filterBy\"\n [filterPlaceholder]=\"field.config.attributes.filterPlaceholder\"\n [loading]=\"field.config.attributes.loading\"\n [virtualScroll]=\"field.config.attributes.virtualScroll\"\n [virtualScrollItemSize]=\"\n field.config.attributes.virtualScrollItemSize\n \"\n [lazy]=\"field.config.attributes.lazy\"\n [emptyMessage]=\"field.config.attributes.emptyMessage\"\n appendTo=\"body\"\n (onChange)=\"onEvent(field, $event, 'change')\"\n (onFocus)=\"onEvent(field, $event, 'focus')\"\n (onBlur)=\"onEvent(field, $event, 'blur')\"\n (onShow)=\"onEvent(field, $event, 'show')\"\n (onHide)=\"onEvent(field, $event, 'hide')\"\n (onClear)=\"onEvent(field, $event, 'clear')\"\n (onLazyLoad)=\"onEvent(field, $event, 'lazyLoad')\"\n >\n @if (\n field.config.attributes.isTemplate &&\n field.config.attributes.template\n ) {\n <ng-container\n [ngTemplateOutlet]=\"field.config.attributes.template\"\n />\n }\n </p-select>\n }\n\n <!-- multiSelect -->\n @case ('multiSelect') {\n <p-multiselect\n [id]=\"inputId(field, idSuffix)\"\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [options]=\"$any(optionsFor(field))\"\n [optionLabel]=\"field.config.attributes.optionLabel\"\n [optionValue]=\"field.config.attributes.optionValue\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n [filter]=\"field.config.attributes.filter\"\n [showClear]=\"field.config.attributes.showClear\"\n [selectionLimit]=\"field.config.attributes.selectionLimit\"\n [display]=\"field.config.attributes.display\"\n [loading]=\"field.config.attributes.loading\"\n [virtualScroll]=\"field.config.attributes.virtualScroll\"\n [virtualScrollItemSize]=\"\n field.config.attributes.virtualScrollItemSize\n \"\n [scrollHeight]=\"field.config.attributes.scrollHeight\"\n appendTo=\"body\"\n (onChange)=\"onEvent(field, $event, 'change')\"\n (onFocus)=\"onEvent(field, $event, 'focus')\"\n (onBlur)=\"onEvent(field, $event, 'blur')\"\n (onClear)=\"onEvent(field, $event, 'clear')\"\n (onHide)=\"onEvent(field, $event, 'hide')\"\n (onRemove)=\"onEvent(field, $event, 'remove')\"\n (onSelectAll)=\"onEvent(field, $event, 'select')\"\n >\n </p-multiselect>\n }\n\n <!-- autocomplete -->\n @case ('autocomplete') {\n <p-autocomplete\n [id]=\"inputId(field, idSuffix)\"\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n [suggestions]=\"field.config.attributes.suggestions\"\n [optionLabel]=\"field.config.attributes.optionLabel\"\n [multiple]=\"field.config.attributes.multiple\"\n [forceSelection]=\"field.config.attributes.forceSelection\"\n [dropdown]=\"field.config.attributes.dropdown\"\n [showClear]=\"field.config.attributes.showClear\"\n [completeOnFocus]=\"field.config.attributes.completeOnFocus\"\n [minLength]=\"field.config.attributes.minLength ?? 1\"\n [delay]=\"field.config.attributes.delay ?? 300\"\n [virtualScroll]=\"field.config.attributes.virtualScroll\"\n [virtualScrollItemSize]=\"\n field.config.attributes.virtualScrollItemSize\n \"\n [scrollHeight]=\"field.config.attributes.scrollHeight\"\n appendTo=\"body\"\n (completeMethod)=\"onEvent(field, $event, 'complete')\"\n (onSelect)=\"onEvent(field, $event, 'select')\"\n (onUnselect)=\"onEvent(field, $event, 'unselect')\"\n (onAdd)=\"onEvent(field, $event, 'add')\"\n (onFocus)=\"onEvent(field, $event, 'focus')\"\n (onBlur)=\"onEvent(field, $event, 'blur')\"\n (onClear)=\"onEvent(field, $event, 'clear')\"\n (onInputKeydown)=\"onEvent(field, $event, 'inputKeydown')\"\n >\n </p-autocomplete>\n }\n\n <!-- toggle -->\n @case ('toggle') {\n <p-toggleswitch\n [formControlName]=\"field.formControlName!\"\n [inputId]=\"inputId(field, idSuffix)\"\n [attr.aria-labelledby]=\"field.formControlName + 'Label'\"\n (onChange)=\"onEvent(field, $event, 'change')\"\n />\n }\n\n <!-- checkbox -->\n @case ('checkbox') {\n <p-checkbox\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [name]=\"field.config.attributes.name ?? field.formControlName!\"\n [value]=\"field.config.attributes.value\"\n (onChange)=\"onEvent(field, $event, 'change')\"\n />\n }\n\n <!-- radio -->\n @case ('radio') {\n @for (opt of $any(optionsFor(field)); track $index) {\n <div class=\"ngx-radio-item\">\n <p-radiobutton\n [inputId]=\"field.formControlName + '_' + $index\"\n [formControlName]=\"field.formControlName!\"\n [name]=\"field.formControlName!\"\n [value]=\"\n opt[field.config.attributes.optionValue ?? 'value']\n \"\n (onClick)=\"onEvent(field, $event, 'change')\"\n />\n <label [for]=\"field.formControlName + '_' + $index\">\n {{ opt[field.config.attributes.optionLabel ?? 'label'] }}\n </label>\n </div>\n }\n }\n\n <!-- datePicker / time / month / year -->\n @case ('datePicker') {\n <p-datepicker\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n [showIcon]=\"field.config.attributes.showIcon\"\n [minDate]=\"minDateFor(field)!\"\n [maxDate]=\"maxDateFor(field)!\"\n [selectionMode]=\"$any(field.config.attributes.selectionMode)\"\n [readonlyInput]=\"field.config.attributes.readonlyInput\"\n [showButtonBar]=\"field.config.attributes.showButtonBar\"\n [showTime]=\"field.config.attributes.showTime\"\n [hourFormat]=\"field.config.attributes.hourFormat\"\n [timeOnly]=\"field.config.attributes.timeOnly\"\n [view]=\"$any(field.config.attributes.view)\"\n [dateFormat]=\"field.config.attributes.dateFormat\"\n [numberOfMonths]=\"field.config.attributes.numberOfMonths ?? 1\"\n [showClear]=\"field.config.attributes.showClear\"\n appendTo=\"body\"\n (onSelect)=\"onEvent(field, $event, 'select')\"\n (onBlur)=\"onEvent(field, $event, 'blur')\"\n (onFocus)=\"onEvent(field, $event, 'focus')\"\n (onClear)=\"onEvent(field, $event, 'clear')\"\n >\n </p-datepicker>\n }\n\n @case ('time') {\n <p-datepicker\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n [showIcon]=\"true\"\n [iconDisplay]=\"'input'\"\n [timeOnly]=\"true\"\n [showTime]=\"true\"\n [hourFormat]=\"field.config.attributes.hourFormat ?? '24'\"\n appendTo=\"body\"\n (onSelect)=\"onEvent(field, $event, 'select')\"\n />\n }\n\n @case ('month') {\n <p-datepicker\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n [showIcon]=\"true\"\n [view]=\"'month'\"\n [dateFormat]=\"field.config.attributes.dateFormat ?? 'mm/yy'\"\n [readonlyInput]=\"true\"\n appendTo=\"body\"\n (onSelect)=\"onEvent(field, $event, 'select')\"\n />\n }\n\n @case ('year') {\n <p-datepicker\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n [showIcon]=\"true\"\n [view]=\"'year'\"\n [dateFormat]=\"field.config.attributes.dateFormat ?? 'yy'\"\n [readonlyInput]=\"true\"\n appendTo=\"body\"\n (onSelect)=\"onEvent(field, $event, 'select')\"\n />\n }\n\n <!-- slider -->\n @case ('slider') {\n <p-slider\n [formControlName]=\"field.formControlName!\"\n [min]=\"field.config.attributes.min ?? 0\"\n [max]=\"field.config.attributes.max ?? 100\"\n [step]=\"field.config.attributes.step ?? 1\"\n [range]=\"field.config.attributes.range\"\n [orientation]=\"\n field.config.attributes.orientation ?? 'horizontal'\n \"\n (onChange)=\"onEvent(field, $event, 'change')\"\n />\n }\n\n <!-- rating -->\n @case ('rating') {\n <p-rating\n [formControlName]=\"field.formControlName!\"\n [stars]=\"field.config.attributes.stars ?? 5\"\n (onRate)=\"onEvent(field, $event, 'change')\"\n />\n }\n\n <!-- colorPicker -->\n @case ('colorPicker') {\n <p-colorpicker\n [formControlName]=\"field.formControlName!\"\n [inline]=\"field.config.attributes.inline\"\n (onChange)=\"onEvent(field, $event, 'change')\"\n />\n }\n }\n\n @if (\n field.config.attributes.fieldPos === 'right' &&\n field.config.attributes.fieldIcon\n ) {\n <p-inputgroup-addon>\n <i [class]=\"field.config.attributes.fieldIcon\">\n {{ field.config.attributes.fieldIconText ?? '' }}\n </i>\n </p-inputgroup-addon>\n }\n </p-inputgroup>\n }\n }\n </ng-container>\n</ng-template>\n", styles: [".ngx-form-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:1.25rem}.ngx-col-1{grid-column:span 1;min-width:0}.ngx-col-2{grid-column:span 2;min-width:0}.ngx-col-3{grid-column:span 3;min-width:0}.ngx-col-4{grid-column:span 4;min-width:0}.ngx-col-5{grid-column:span 5;min-width:0}.ngx-col-6{grid-column:span 6;min-width:0}.ngx-col-7{grid-column:span 7;min-width:0}.ngx-col-8{grid-column:span 8;min-width:0}.ngx-col-9{grid-column:span 9;min-width:0}.ngx-col-10{grid-column:span 10;min-width:0}.ngx-col-11{grid-column:span 11;min-width:0}.ngx-col-12{grid-column:span 12;min-width:0}@media(max-width:640px){.ngx-col-1,.ngx-col-2,.ngx-col-3,.ngx-col-4,.ngx-col-5,.ngx-col-6,.ngx-col-7,.ngx-col-8,.ngx-col-9,.ngx-col-10,.ngx-col-11{grid-column:span 12}}.ngx-form-title{font-size:1.25rem;font-weight:700;margin-bottom:1.25rem;color:var(--p-text-color, #111827)}.ngx-field-label{display:block;font-size:.775rem;font-weight:600;color:var(--p-text-muted-color, #4b5563);margin-bottom:.3rem;letter-spacing:.01em}.ngx-required{color:var(--p-red-500, #ef4444);margin-left:.15rem}.ngx-field-inner{margin-top:.2rem;width:100%}.ngx-field-inner .p-inputgroup,.ngx-field-inner .p-select,.ngx-field-inner .p-multiselect,.ngx-field-inner .p-autocomplete,.ngx-field-inner .p-datepicker,.ngx-field-inner .p-inputtext,.ngx-field-inner .p-textarea,.ngx-field-inner .p-password{width:100%}:host ::ng-deep .p-inputgroup{border:1px solid var(--p-surface-border, #d1d5db);border-radius:8px;background:#fff;transition:all .2s;display:flex;align-items:stretch}:host ::ng-deep .p-inputgroup p-inputgroup-addon,:host ::ng-deep .p-inputgroup .p-inputgroup-addon{background:transparent!important;border:none!important;color:#9ca3af;padding:.75rem .5rem .75rem 1rem;display:flex;align-items:center}:host ::ng-deep .p-inputgroup p-inputgroup-addon i,:host ::ng-deep .p-inputgroup .p-inputgroup-addon i{font-size:1.15rem}:host ::ng-deep .p-inputgroup:has(textarea) p-inputgroup-addon,:host ::ng-deep .p-inputgroup:has(textarea) .p-inputgroup-addon{align-items:flex-start;padding-top:1rem}:host ::ng-deep .p-inputgroup:has(input:focus),:host ::ng-deep .p-inputgroup:has(textarea:focus),:host ::ng-deep .p-inputgroup:has(.p-focus){border-color:#3b82f6;box-shadow:0 0 0 2px #3b82f633}:host ::ng-deep .p-inputgroup>.p-inputtext,:host ::ng-deep .p-inputgroup>.p-textarea,:host ::ng-deep .p-inputgroup p-select>.p-select,:host ::ng-deep .p-inputgroup p-multiselect>.p-multiselect,:host ::ng-deep .p-inputgroup p-autocomplete>.p-autocomplete,:host ::ng-deep .p-inputgroup p-datepicker>.p-datepicker{border:none!important;background:transparent!important;box-shadow:none!important;flex:1 1 auto;min-width:0}:host ::ng-deep .p-inputgroup:has(p-select),:host ::ng-deep .p-inputgroup:has(p-multiselect){position:relative}:host ::ng-deep .p-inputgroup:has(p-select) p-inputgroup-addon,:host ::ng-deep .p-inputgroup:has(p-multiselect) p-inputgroup-addon{position:absolute;z-index:2;border:none!important;background:transparent!important;height:100%;width:2.5rem;display:flex;align-items:center;justify-content:center}:host ::ng-deep .p-inputgroup:has(p-select) p-inputgroup-addon:first-child,:host ::ng-deep .p-inputgroup:has(p-multiselect) p-inputgroup-addon:first-child{left:0}:host ::ng-deep .p-inputgroup:has(p-select) p-select,:host ::ng-deep .p-inputgroup:has(p-select) p-multiselect,:host ::ng-deep .p-inputgroup:has(p-multiselect) p-select,:host ::ng-deep .p-inputgroup:has(p-multiselect) p-multiselect{width:100%;flex:1 1 100%}:host ::ng-deep .p-inputgroup:has(p-inputgroup-addon:first-child):has(p-select) .p-select-label,:host ::ng-deep .p-inputgroup:has(p-inputgroup-addon:first-child):has(p-select) .p-multiselect-label,:host ::ng-deep .p-inputgroup:has(p-inputgroup-addon:first-child):has(p-multiselect) .p-select-label,:host ::ng-deep .p-inputgroup:has(p-inputgroup-addon:first-child):has(p-multiselect) .p-multiselect-label{padding-left:2.75rem!important}:host ::ng-deep .ngx-toggle-card{display:flex;align-items:center;justify-content:space-between;padding:.9rem 1rem;border:1px solid var(--p-surface-border, #e5e7eb);border-radius:8px;background:#fff;margin-bottom:.75rem}:host ::ng-deep .ngx-toggle-card .ngx-toggle-card-icon{width:36px;height:36px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:1.1rem;margin-right:1rem}:host ::ng-deep .ngx-toggle-card .ngx-toggle-card-body{flex:1;min-width:0;padding-right:1.5rem}:host ::ng-deep .ngx-toggle-card .ngx-toggle-card-body .ngx-toggle-label{font-weight:600;color:var(--gray-900);margin-bottom:.2rem;font-size:.95rem}:host ::ng-deep .ngx-toggle-card .ngx-toggle-card-body .ngx-toggle-desc{color:var(--gray-500);font-size:.8rem;line-height:1.4}:host ::ng-deep .ngx-toggle-card p-toggleswitch,:host ::ng-deep .ngx-toggle-card .p-toggleswitch{flex:0 0 auto!important;width:44px!important;height:24px!important;display:block!important;position:relative!important;box-sizing:border-box!important}:host ::ng-deep .ngx-toggle-card .p-toggleswitch-slider{position:absolute!important;inset:0!important;width:100%!important;height:100%!important;border-radius:24px!important;transition:background-color .2s,box-shadow .2s!important;background:var(--p-surface-300, #cbd5e1)!important}:host ::ng-deep .ngx-toggle-card .p-toggleswitch-handle{position:absolute!important;top:50%!important;left:3px!important;margin-top:-9px!important;width:18px!important;height:18px!important;border-radius:50%!important;background:#fff!important;transition:transform .2s!important;box-shadow:0 1px 3px #0003!important}:host ::ng-deep .ngx-toggle-card .p-toggleswitch.p-toggleswitch-checked .p-toggleswitch-handle{transform:translate(20px)!important}:host ::ng-deep .ngx-toggle-card .p-toggleswitch.p-toggleswitch-checked .p-toggleswitch-slider{background:var(--toggle-color, var(--p-primary-500))!important}::ng-deep .p-select-overlay,::ng-deep .p-multiselect-overlay,::ng-deep .p-autocomplete-overlay{background:#fff!important;box-shadow:0 4px 15px #00000014,0 1px 3px #0000000d!important;border:1px solid var(--p-surface-border, #e5e7eb)!important;border-radius:8px!important;margin-top:6px!important;padding:.25rem!important}::ng-deep .p-select-overlay .p-select-header,::ng-deep .p-select-overlay .p-multiselect-header,::ng-deep .p-multiselect-overlay .p-select-header,::ng-deep .p-multiselect-overlay .p-multiselect-header,::ng-deep .p-autocomplete-overlay .p-select-header,::ng-deep .p-autocomplete-overlay .p-multiselect-header{padding:.5rem!important;margin-bottom:.25rem}::ng-deep .p-select-overlay .p-select-filter-container .p-inputtext,::ng-deep .p-select-overlay .p-select-filter,::ng-deep .p-select-overlay .p-multiselect-filter-container .p-inputtext,::ng-deep .p-select-overlay .p-multiselect-filter,::ng-deep .p-multiselect-overlay .p-select-filter-container .p-inputtext,::ng-deep .p-multiselect-overlay .p-select-filter,::ng-deep .p-multiselect-overlay .p-multiselect-filter-container .p-inputtext,::ng-deep .p-multiselect-overlay .p-multiselect-filter,::ng-deep .p-autocomplete-overlay .p-select-filter-container .p-inputtext,::ng-deep .p-autocomplete-overlay .p-select-filter,::ng-deep .p-autocomplete-overlay .p-multiselect-filter-container .p-inputtext,::ng-deep .p-autocomplete-overlay .p-multiselect-filter{padding:.5rem .75rem!important}::ng-deep .p-select-overlay .p-select-list,::ng-deep .p-select-overlay .p-multiselect-list,::ng-deep .p-multiselect-overlay .p-select-list,::ng-deep .p-multiselect-overlay .p-multiselect-list,::ng-deep .p-autocomplete-overlay .p-select-list,::ng-deep .p-autocomplete-overlay .p-multiselect-list{padding:0!important;display:flex;flex-direction:column;gap:2px}::ng-deep .p-select-overlay .p-select-option,::ng-deep .p-select-overlay .p-multiselect-option,::ng-deep .p-multiselect-overlay .p-select-option,::ng-deep .p-multiselect-overlay .p-multiselect-option,::ng-deep .p-autocomplete-overlay .p-select-option,::ng-deep .p-autocomplete-overlay .p-multiselect-option{border-radius:6px!important;margin:0!important;padding:.5rem .75rem!important;display:flex;align-items:center}::ng-deep .p-select-overlay .p-select-option:hover,::ng-deep .p-select-overlay .p-multiselect-option:hover,::ng-deep .p-multiselect-overlay .p-select-option:hover,::ng-deep .p-multiselect-overlay .p-multiselect-option:hover,::ng-deep .p-autocomplete-overlay .p-select-option:hover,::ng-deep .p-autocomplete-overlay .p-multiselect-option:hover{background:var(--p-surface-hover, #f3f4f6)!important}::ng-deep .p-select-overlay .p-select-option[data-p-highlight=true],::ng-deep .p-select-overlay .p-select-option.p-select-option-selected,::ng-deep .p-select-overlay .p-multiselect-option[data-p-highlight=true],::ng-deep .p-select-overlay .p-multiselect-option.p-select-option-selected,::ng-deep .p-multiselect-overlay .p-select-option[data-p-highlight=true],::ng-deep .p-multiselect-overlay .p-select-option.p-select-option-selected,::ng-deep .p-multiselect-overlay .p-multiselect-option[data-p-highlight=true],::ng-deep .p-multiselect-overlay .p-multiselect-option.p-select-option-selected,::ng-deep .p-autocomplete-overlay .p-select-option[data-p-highlight=true],::ng-deep .p-autocomplete-overlay .p-select-option.p-select-option-selected,::ng-deep .p-autocomplete-overlay .p-multiselect-option[data-p-highlight=true],::ng-deep .p-autocomplete-overlay .p-multiselect-option.p-select-option-selected{background:#f1f5f9!important;color:var(--p-primary-700, #334155)!important;font-weight:500}.ngx-error{display:block;color:var(--p-red-500, #ef4444);font-size:.72rem;line-height:1.4;min-height:1.008rem;margin-top:.25rem;font-weight:500}.ngx-error--placeholder{visibility:hidden}.ngx-hint{display:block;font-size:.72rem;margin-top:.25rem;color:var(--p-text-muted-color, #9ca3af);line-height:1.4}.ngx-btn-wrap{display:flex;align-items:flex-end;justify-content:stretch;width:100%;height:100%;padding-top:1.5rem}.ngx-btn-wrap button{width:100%;justify-content:center;font-weight:600}.ngx-file-inline{display:flex;flex-wrap:wrap;gap:.5rem}.ngx-image-list{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:.75rem}.ngx-image-card{position:relative}.ngx-image-remove{position:absolute;top:0;right:0;z-index:2;width:22px;height:22px;display:flex;align-items:center;justify-content:center;border-radius:50%;cursor:pointer;background:#fff;color:#111;box-shadow:0 2px 6px #0003;transform:translate(40%,-40%);font-size:.65rem;transition:background .15s,color .15s}.ngx-image-remove:hover{background:#fee2e2;color:#dc2626}.ngx-image-wrapper{position:relative;width:80px;height:80px;border-radius:8px;overflow:hidden;border:1px solid var(--p-surface-border, #e5e7eb)}.ngx-preview-img{object-fit:cover;transition:opacity .25s ease}.ngx-preview-img.loading{opacity:0}.ngx-preview-img.loaded{opacity:1}.ngx-img-skeleton{position:absolute;inset:0;background:linear-gradient(90deg,#f0f0f0 25%,#e0e0e0,#f0f0f0 75%);background-size:200% 100%;animation:shimmer 1.4s infinite;border-radius:8px}@keyframes shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.ngx-toggle-card{display:flex;align-items:center;gap:.85rem;padding:.9rem 1rem;border:1px solid var(--p-surface-border, #e5e7eb);border-radius:10px;background:#fff;width:100%;transition:border-color .2s,box-shadow .2s,background .2s}.ngx-toggle-card:has(.p-toggleswitch.p-toggleswitch-checked){border-color:var(--toggle-color, var(--p-primary-500, #3b82f6));box-shadow:0 0 0 3px color-mix(in srgb,var(--toggle-color, #3b82f6) 12%,transparent)}.ngx-toggle-card-icon{width:42px;height:42px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:1rem;flex-shrink:0}.ngx-toggle-card-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:.15rem}.ngx-toggle-card-label{font-size:.84rem;font-weight:600;color:var(--p-text-color, #111827);line-height:1.3}.ngx-toggle-card-desc{font-size:.72rem;color:var(--p-text-muted-color, #6b7280);line-height:1.4}.ngx-radio-item{display:flex;align-items:center;gap:.5rem;margin-bottom:.35rem;font-size:.875rem;color:var(--p-text-color, #374151)}.ngx-field-group{border:1px solid var(--p-surface-border, #e5e7eb);border-radius:10px;padding:1rem 1rem .5rem;display:flex;flex-direction:column;gap:.75rem}.ngx-field-group-row{display:block}.ngx-repeater{display:flex;flex-direction:column;gap:.75rem}.ngx-repeater-row{display:grid;grid-template-columns:1fr auto;gap:.5rem;align-items:start;padding:.75rem;border:1px solid var(--p-surface-border, #e5e7eb);border-radius:10px;background:var(--p-surface-50, #f9fafb)}.ngx-repeater-row-fields{display:grid;grid-template-columns:repeat(12,1fr);gap:.75rem;min-width:0}.ngx-repeater-remove{margin-top:.2rem}.ngx-repeater-add{align-self:flex-start}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }, { kind: "ngmodule", type:
395
+ // PrimeNG
396
+ InputTextModule }, { kind: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: InputGroupModule }, { kind: "component", type: i3.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["styleClass"] }, { kind: "ngmodule", type: InputGroupAddonModule }, { kind: "component", type: i4.InputGroupAddon, selector: "p-inputgroup-addon, p-inputGroupAddon", inputs: ["style", "styleClass"] }, { kind: "ngmodule", type: FloatLabelModule }, { kind: "component", type: i5.FloatLabel, selector: "p-floatlabel, p-floatLabel, p-float-label", inputs: ["variant"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: MultiSelectModule }, { kind: "component", type: i7.MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "styleClass", "panelStyle", "panelStyleClass", "inputId", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "dataKey", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "placeholder", "options", "filterValue", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus", "highlightOnSelect", "size", "variant", "fluid", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "ngmodule", type: CascadeSelectModule }, { kind: "component", type: i8.CascadeSelect, selector: "p-cascadeSelect, p-cascadeselect, p-cascade-select", inputs: ["id", "searchMessage", "emptyMessage", "selectionMessage", "emptySearchMessage", "emptySelectionMessage", "searchLocale", "optionDisabled", "focusOnHover", "selectOnFocus", "autoOptionFocus", "styleClass", "options", "optionLabel", "optionValue", "optionGroupLabel", "optionGroupChildren", "placeholder", "value", "dataKey", "inputId", "tabindex", "ariaLabelledBy", "inputLabel", "ariaLabel", "showClear", "panelStyleClass", "panelStyle", "overlayOptions", "autofocus", "loading", "loadingIcon", "breakpoint", "size", "variant", "fluid", "appendTo", "motionOptions"], outputs: ["onChange", "onGroupChange", "onShow", "onHide", "onClear", "onBeforeShow", "onBeforeHide", "onFocus", "onBlur"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i9.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "ngmodule", type: FileUploadModule }, { kind: "component", type: i10.FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i11.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i12.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }, { kind: "ngmodule", type: PasswordModule }, { kind: "component", type: i13.Password, selector: "p-password", inputs: ["ariaLabel", "ariaLabelledBy", "label", "promptLabel", "mediumRegex", "strongRegex", "weakLabel", "mediumLabel", "maxLength", "strongLabel", "inputId", "feedback", "toggleMask", "inputStyleClass", "styleClass", "inputStyle", "showTransitionOptions", "hideTransitionOptions", "autocomplete", "placeholder", "showClear", "autofocus", "tabindex", "appendTo", "motionOptions", "overlayOptions"], outputs: ["onFocus", "onBlur", "onClear"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i14.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "styleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "icon", "readonlyInput", "shortYearCutoff", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "minDate", "maxDate", "disabledDates", "disabledDays", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "view", "defaultDate", "appendTo", "motionOptions"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i15.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "component", type: i16.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["styleClass", "tabindex", "inputId", "readonly", "trueValue", "falseValue", "ariaLabel", "size", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i17.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: SliderModule }, { kind: "component", type: i18.Slider, selector: "p-slider", inputs: ["animate", "min", "max", "orientation", "step", "range", "styleClass", "ariaLabel", "ariaLabelledBy", "tabindex", "autofocus"], outputs: ["onChange", "onSlideEnd"] }, { kind: "ngmodule", type: RatingModule }, { kind: "component", type: i19.Rating, selector: "p-rating", inputs: ["readonly", "stars", "iconOnClass", "iconOnStyle", "iconOffClass", "iconOffStyle", "autofocus"], outputs: ["onRate", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ColorPickerModule }, { kind: "component", type: i20.ColorPicker, selector: "p-colorPicker, p-colorpicker, p-color-picker", inputs: ["styleClass", "showTransitionOptions", "hideTransitionOptions", "inline", "format", "tabindex", "inputId", "autoZIndex", "autofocus", "defaultColor", "appendTo", "overlayOptions", "motionOptions"], outputs: ["onChange", "onShow", "onHide"] }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: i21.RadioButton, selector: "p-radioButton, p-radiobutton, p-radio-button", inputs: ["value", "tabindex", "inputId", "ariaLabelledBy", "ariaLabel", "styleClass", "autofocus", "binary", "variant", "size"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: KeyFilterModule }, { kind: "directive", type: i22.KeyFilter, selector: "[pKeyFilter]", inputs: ["pValidateOnly", "pKeyFilter"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: EditorModule }, { kind: "component", type: i23.Editor, selector: "p-editor", inputs: ["style", "styleClass", "placeholder", "formats", "modules", "bounds", "scrollingContainer", "debug", "readonly"], outputs: ["onInit", "onTextChange", "onSelectionChange", "onEditorChange", "onFocus", "onBlur"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
397
+ }
398
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgxJsonFormComponent, decorators: [{
399
+ type: Component,
400
+ args: [{ selector: 'ngx-json-form', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
401
+ ReactiveFormsModule,
402
+ NgTemplateOutlet,
403
+ NgComponentOutlet,
404
+ NgOptimizedImage,
405
+ // PrimeNG
406
+ InputTextModule,
407
+ InputGroupModule,
408
+ InputGroupAddonModule,
409
+ FloatLabelModule,
410
+ SelectModule,
411
+ MultiSelectModule,
412
+ CascadeSelectModule,
413
+ AutoCompleteModule,
414
+ FileUploadModule,
415
+ ButtonModule,
416
+ DialogModule,
417
+ DividerModule,
418
+ PasswordModule,
419
+ DatePickerModule,
420
+ CheckboxModule,
421
+ ToggleSwitchModule,
422
+ TextareaModule,
423
+ SliderModule,
424
+ RatingModule,
425
+ ColorPickerModule,
426
+ RadioButtonModule,
427
+ KeyFilterModule,
428
+ EditorModule,
429
+ ], template: "@if (formTitle().length) {\n <div class=\"ngx-form-title\">{{ formTitle() }}</div>\n}\n\n@if (ready()) {\n <form [formGroup]=\"formGroup()\" class=\"ngx-form-grid\">\n @for (field of visibleFields(); track field.formControlName ?? $index) {\n <div\n [class]=\"\n 'ngx-col-' +\n (field.layout?.columnSpan ?? 12) +\n ' ' +\n (field.layout?.wrapperClass ?? '')\n \"\n [style]=\"field.layout?.wrapperStyle ?? {}\"\n [style.display]=\"field.config.attributes.isHidden ? 'none' : 'block'\"\n >\n <!-- Float Label wrapper -->\n @if (field.floatLabel) {\n <p-floatlabel [variant]=\"field.floatVariant ?? 'on'\">\n <div class=\"ngx-field-inner\">\n <ng-container\n [ngTemplateOutlet]=\"fieldTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: field, formGroup: formGroup() }\"\n />\n </div>\n <label\n [attr.for]=\"field.formControlName + 'Id'\"\n [attr.id]=\"field.formControlName + 'Label'\"\n >\n @if (field.labelIconPos === 'left') {\n <i [class]=\"field.labelIcon\"></i>\n }\n {{ field.label }}\n @if (field.labelIconPos === 'right') {\n <i [class]=\"field.labelIcon\"></i>\n }\n @if (field.validations?.rules?.required) {\n <span class=\"ngx-required\">*</span>\n }\n </label>\n </p-floatlabel>\n }\n\n <!-- Toggle card layout -->\n @else if (\n field.config.attributes.cardLayout &&\n field.config.attributes.inputType === 'toggle'\n ) {\n <div\n class=\"ngx-toggle-card\"\n [style.--toggle-color]=\"field.config.attributes.cardIconColor\"\n >\n <div\n class=\"ngx-toggle-card-icon\"\n [style.background]=\"\n field.config.attributes.cardIconBg ??\n 'var(--p-primary-50, #eff6ff)'\n \"\n [style.color]=\"\n field.config.attributes.cardIconColor ??\n 'var(--p-primary-500, #3b82f6)'\n \"\n >\n <i [class]=\"field.config.attributes.cardIcon ?? 'pi pi-bell'\"></i>\n </div>\n <div class=\"ngx-toggle-card-body\">\n <span class=\"ngx-toggle-card-label\">\n {{ field.label }}\n @if (field.validations?.rules?.required) {\n <span class=\"ngx-required\">*</span>\n }\n </span>\n @if (field.config.attributes.info?.length) {\n <span\n class=\"ngx-toggle-card-desc\"\n [innerHTML]=\"field.config.attributes.info\"\n ></span>\n }\n </div>\n <p-toggleswitch\n [formControlName]=\"field.formControlName!\"\n [inputId]=\"field.formControlName + 'Id'\"\n (onChange)=\"onEvent(field, $event, 'change')\"\n />\n </div>\n <!--\n Always reserve one line of space for the error so the row height\n doesn't change when validation fires (otherwise sibling fields\n jump down when the error appears).\n -->\n @if (field.validations?.rules) {\n <small\n class=\"ngx-error\"\n [class.ngx-error--placeholder]=\"!errorMap()[field.formControlName!]\"\n [attr.id]=\"field.formControlName + 'Err'\"\n >{{ errorMap()[field.formControlName!] || '\u00A0' }}</small>\n }\n }\n\n <!-- Normal label wrapper -->\n @else {\n @if (field.label && field.label.length) {\n <label\n class=\"ngx-field-label\"\n [attr.for]=\"field.formControlName + 'Id'\"\n [attr.id]=\"field.formControlName + 'Label'\"\n >\n @if (field.labelIconPos === 'left') {\n <i [class]=\"field.labelIcon\"></i>\n }\n {{ field.label }}\n @if (field.labelIconPos === 'right') {\n <i [class]=\"field.labelIcon\"></i>\n }\n @if (field.validations?.rules?.required) {\n <span class=\"ngx-required\">*</span>\n }\n </label>\n }\n <div class=\"ngx-field-inner\">\n <ng-container\n [ngTemplateOutlet]=\"fieldTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: field, formGroup: formGroup() }\"\n />\n </div>\n\n <!--\n Validation error \u2014 always reserve one line of space (placeholder\n class makes it invisible when there's no error) so triggering a\n required/pattern message doesn't push the row below it down.\n -->\n @if (field.validations?.rules) {\n <small\n class=\"ngx-error\"\n [class.ngx-error--placeholder]=\"!errorMap()[field.formControlName!]\"\n [attr.id]=\"field.formControlName + 'Err'\"\n >{{ errorMap()[field.formControlName!] || '\u00A0' }}</small>\n }\n\n <!-- Info / hint -->\n @if (field.config.attributes.info?.length) {\n <small\n class=\"ngx-hint\"\n [attr.id]=\"field.formControlName + 'Hint'\"\n [innerHTML]=\"field.config.attributes.info\"\n ></small>\n }\n }\n </div>\n }\n </form>\n}\n\n<!-- \u2500\u2500\u2500 Field Renderer Template \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n<!--\n `formGroup` is passed in via the template context so each rendering site\n binds the right group: the root FormGroup for top-level fields, the row's\n FormGroup for repeater rows. Hard-coding `formGroup()` here would override\n row-level bindings and route nested formControlName lookups to the root,\n which is the bug we're fixing.\n\n Known issue: during initial CD passes Angular logs NG01050 spam because\n the FormGroup directive on this ng-container materialises after children\n formControlName directives query their parent ControlContainer. Bindings\n recover and form values flow correctly afterwards. Cleaner fix would be\n to avoid the ngTemplateOutlet + context + nested formGroup combination\n altogether (e.g. inline the @switch at each call site).\n-->\n<ng-template #fieldTpl let-field let-fg=\"formGroup\" let-idSuffix=\"idSuffix\">\n <ng-container [formGroup]=\"fg\">\n @switch (field.config.attributes.inputType) {\n <!-- \u2500\u2500 Custom field (registry) \u2500\u2500 -->\n @case (customRenderer(field) ? field.config.attributes.inputType : '__never__') {\n <ng-container\n *ngComponentOutlet=\"customRenderer(field)!; inputs: customRendererInputs(field)\"\n />\n }\n\n <!-- \u2500\u2500 Group (nested FormGroup) \u2500\u2500 -->\n @case ('group') {\n <fieldset\n class=\"ngx-field-group\"\n [attr.formGroupName]=\"field.formControlName\"\n >\n @for (sub of field.config.attributes.groupFields ?? []; track sub.formControlName ?? $index) {\n <div class=\"ngx-field-group-row\">\n <ng-container\n [ngTemplateOutlet]=\"fieldTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: sub, formGroup: formGroup() }\"\n />\n </div>\n }\n </fieldset>\n }\n\n <!-- \u2500\u2500 Repeater (FormArray) \u2500\u2500 -->\n @case ('repeater') {\n <div class=\"ngx-repeater\">\n @for (row of getArray(field.formControlName!)?.controls ?? []; track $index) {\n @let rowIndex = $index;\n <div class=\"ngx-repeater-row\" [formGroup]=\"$any(row)\">\n <div class=\"ngx-repeater-row-fields\">\n @for (sub of field.config.attributes.itemFields ?? []; track sub.formControlName ?? $index) {\n <div\n [class]=\"\n 'ngx-col-' + (sub.layout?.columnSpan ?? 12)\n \"\n >\n @if (sub.label) {\n <label\n class=\"ngx-field-label\"\n [attr.for]=\"inputId(sub, rowIndex)\"\n >\n {{ sub.label }}\n @if (sub.validations?.rules?.required) {\n <span class=\"ngx-required\">*</span>\n }\n </label>\n }\n <ng-container\n [ngTemplateOutlet]=\"fieldTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: sub, formGroup: $any(row), idSuffix: rowIndex }\"\n />\n @if (sub.validations?.rules && sub.formControlName) {\n <small\n class=\"ngx-error\"\n [class.ngx-error--placeholder]=\"!resolveRowError($any(row), sub)\"\n >{{ resolveRowError($any(row), sub) || '\u00A0' }}</small>\n }\n </div>\n }\n </div>\n <button\n type=\"button\"\n class=\"ngx-repeater-remove\"\n pButton\n [icon]=\"'pi pi-trash'\"\n [rounded]=\"true\"\n [text]=\"true\"\n (click)=\"removeRepeaterRow(field, $index)\"\n ></button>\n </div>\n }\n <button\n pButton\n type=\"button\"\n class=\"ngx-repeater-add\"\n [icon]=\"'pi pi-plus'\"\n [label]=\"field.config.attributes.addLabel ?? 'Add row'\"\n [text]=\"true\"\n (click)=\"addRepeaterRow(field)\"\n ></button>\n </div>\n }\n\n <!-- \u2500\u2500 Dependent / Cascading Dropdown \u2500\u2500 -->\n @case ('dependentDropdown') {\n <p-cascadeselect\n [id]=\"inputId(field, idSuffix)\"\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [options]=\"$any(optionsFor(field))\"\n [optionLabel]=\"field.config.attributes.optionLabel ?? 'label'\"\n [optionValue]=\"field.config.attributes.optionValue ?? 'value'\"\n [optionGroupLabel]=\"field.config.attributes.optionGroupLabel ?? 'label'\"\n [optionGroupChildren]=\"\n $any(field.config.attributes.optionGroupChildren ?? ['children'])\n \"\n [placeholder]=\"field.placeholder ?? ''\"\n appendTo=\"body\"\n (onChange)=\"onEvent(field, $event, 'change')\"\n (onShow)=\"onEvent(field, $event, 'show')\"\n (onHide)=\"onEvent(field, $event, 'hide')\"\n />\n }\n\n <!-- \u2500\u2500 Editor (rich text) \u2500\u2500 -->\n @case ('editor') {\n <p-editor\n [formControlName]=\"field.formControlName!\"\n [style]=\"{ height: (field.config.attributes['height'] ?? '180px') }\"\n (onTextChange)=\"onEvent(field, $event, 'change')\"\n ></p-editor>\n }\n\n <!-- \u2500\u2500 Confirm password \u2500\u2500 -->\n @case ('confirmPassword') {\n <p-password\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [feedback]=\"false\"\n [toggleMask]=\"field.config.attributes.toggleMask ?? true\"\n [placeholder]=\"field.placeholder ?? ''\"\n appendTo=\"body\"\n (onBlur)=\"onEvent(field, $event, 'blur')\"\n />\n }\n\n <!-- \u2500\u2500 File Upload \u2500\u2500 -->\n @case ('fileUpload') {\n <div>\n <div\n [class]=\"field.config.attributes.isInline ? 'ngx-file-inline' : ''\"\n >\n <p-fileUpload\n mode=\"basic\"\n customUpload\n [id]=\"inputId(field, idSuffix)\"\n [multiple]=\"field.config.attributes.multiple\"\n [chooseIcon]=\"field.config.attributes.chooseIcon ?? 'pi pi-upload'\"\n [accept]=\"field.config.attributes.accept ?? 'image/*'\"\n [maxFileSize]=\"field.config.attributes.maxFileSize ?? 1000000\"\n [auto]=\"true\"\n [chooseLabel]=\"field.config.attributes.chooseLabel ?? 'Upload'\"\n (uploadHandler)=\"onEvent(field, $event, 'uploadHandler')\"\n />\n\n @if (uploadService.imagePreviews()[field.formControlName!]?.length) {\n <div class=\"ngx-image-list\">\n @for (\n image of uploadService.imagePreviews()[field.formControlName!];\n let i = $index;\n track i\n ) {\n <div class=\"ngx-image-card\">\n <i\n class=\"pi pi-times ngx-image-remove\"\n (click)=\"removeImage(field, image)\"\n ></i>\n <div class=\"ngx-image-wrapper\">\n @if (!uploadService.isBase64(image)) {\n <img\n fill\n sizes=\"100\"\n [ngSrc]=\"image\"\n (load)=\"uploadService.onLoad(image)\"\n (error)=\"uploadService.onLoad(image)\"\n [class.loading]=\"uploadService.isLoading(image)\"\n [class.loaded]=\"!uploadService.isLoading(image)\"\n class=\"ngx-preview-img\"\n alt=\"preview\"\n />\n } @else {\n <img\n fill\n sizes=\"100\"\n [src]=\"image\"\n (load)=\"uploadService.onLoad(image)\"\n (error)=\"uploadService.onLoad(image)\"\n [class.loading]=\"uploadService.isLoading(image)\"\n [class.loaded]=\"!uploadService.isLoading(image)\"\n class=\"ngx-preview-img\"\n alt=\"preview\"\n />\n }\n @if (uploadService.isLoading(image)) {\n <div class=\"ngx-img-skeleton\"></div>\n }\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n\n <!-- \u2500\u2500 Static Text \u2500\u2500 -->\n @case ('staticText') {\n <span\n (click)=\"onEvent(field, $event, 'staticTextClick')\"\n [innerHTML]=\"field.config.attributes.value\"\n ></span>\n }\n\n <!-- \u2500\u2500 Divider \u2500\u2500 -->\n @case ('divider') {\n <p-divider\n [layout]=\"field.config.attributes.dividerLayout\"\n [type]=\"field.config.attributes.dividerType\"\n [align]=\"field.config.attributes.dividerAlign\"\n >\n @if (field.config.attributes.value) {\n <span [innerHTML]=\"field.config.attributes.value\"></span>\n }\n </p-divider>\n }\n\n <!-- \u2500\u2500 Button \u2500\u2500 -->\n @case ('button') {\n <div class=\"ngx-btn-wrap\">\n <button\n pButton\n type=\"button\"\n [id]=\"inputId(field, idSuffix)\"\n [label]=\"field.btnLabel ?? ''\"\n [icon]=\"field.config.attributes.icon ?? ''\"\n [iconPos]=\"field.config.attributes.iconPosition ?? 'left'\"\n [tabindex]=\"field.tabIndex ?? 0\"\n [disabled]=\"\n field.config.attributes.buttonRole === 'submit' ||\n field.config.attributes.type === 'submit'\n ? !formValid()\n : !!field.config.attributes.disabled\n \"\n [class.p-button-outlined]=\"\n field.config.attributes.variant === 'secondary'\n \"\n [class.p-button-text]=\"field.config.attributes.text === true\"\n [class.p-button-rounded]=\"field.config.attributes.rounded === true\"\n [class.p-button-sm]=\"field.config.attributes.size === 'sm'\"\n [class.p-button-lg]=\"field.config.attributes.size === 'lg'\"\n (click)=\"onEvent(field, $event)\"\n ></button>\n </div>\n }\n\n <!-- \u2500\u2500 Everything else inside p-inputgroup \u2500\u2500 -->\n @default {\n <p-inputgroup>\n @if (\n field.config.attributes.fieldPos === 'left' &&\n field.config.attributes.fieldIcon\n ) {\n <p-inputgroup-addon>\n <i [class]=\"field.config.attributes.fieldIcon\">\n {{ field.config.attributes.fieldIconText ?? '' }}\n </i>\n </p-inputgroup-addon>\n }\n\n @switch (field.config.attributes.inputType) {\n <!-- text / email / number / url -->\n @case ('text') {\n <input\n pInputText\n [id]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [type]=\"field.config.attributes.type ?? 'text'\"\n [tabindex]=\"field.tabIndex ?? 0\"\n [attr.placeholder]=\"placeholderFor(field)\"\n [attr.aria-describedby]=\"\n (errorMap()[field.formControlName!] ? field.formControlName + 'Err ' : '') +\n (field.config.attributes.info ? field.formControlName + 'Hint' : '')\n \"\n [pKeyFilter]=\"field.config.attributes.keyfilter\"\n (change)=\"onEvent(field, $event)\"\n (input)=\"onEvent(field, $event)\"\n (focus)=\"onEvent(field, $event)\"\n (blur)=\"onEvent(field, $event)\"\n (keyup)=\"onEvent(field, $event)\"\n (keydown)=\"onEvent(field, $event)\"\n (keypress)=\"onEvent(field, $event)\"\n />\n }\n\n <!-- password -->\n @case ('password') {\n <p-password\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [feedback]=\"field.config.attributes.feedback\"\n [toggleMask]=\"field.config.attributes.toggleMask\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n appendTo=\"body\"\n (onBlur)=\"onEvent(field, $event, 'blur')\"\n (onFocus)=\"onEvent(field, $event, 'focus')\"\n >\n @if (field.config.attributes.templateHeader) {\n <ng-container\n [ngTemplateOutlet]=\"field.config.attributes.templateHeader\"\n />\n }\n @if (field.config.attributes.templateFooter) {\n <ng-container\n [ngTemplateOutlet]=\"field.config.attributes.templateFooter\"\n />\n }\n </p-password>\n }\n\n <!-- textarea -->\n @case ('textarea') {\n <textarea\n pTextarea\n [id]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [rows]=\"field.config.attributes.rows ?? 3\"\n [cols]=\"field.config.attributes.cols ?? 30\"\n [autoResize]=\"field.config.attributes.autoResize\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n (change)=\"onEvent(field, $event)\"\n (input)=\"onEvent(field, $event)\"\n (focus)=\"onEvent(field, $event)\"\n (blur)=\"onEvent(field, $event)\"\n >\n </textarea>\n }\n\n <!-- select -->\n @case ('select') {\n <p-select\n [id]=\"inputId(field, idSuffix)\"\n [inputId]=\"inputId(field, idSuffix)\"\n [attr.aria-labelledby]=\"field.formControlName + 'Label'\"\n [formControlName]=\"field.formControlName!\"\n [options]=\"$any(optionsFor(field))\"\n [optionLabel]=\"field.config.attributes.optionLabel\"\n [optionValue]=\"field.config.attributes.optionValue\"\n [placeholder]=\"placeholderFor(field) ?? ''\"\n [showClear]=\"field.config.attributes.showClear\"\n [filter]=\"field.config.attributes.filter\"\n [filterBy]=\"field.config.attributes.filterBy\"\n [filterPlaceholder]=\"field.config.attributes.filterPlaceholder\"\n [loading]=\"field.config.attributes.loading\"\n [virtualScroll]=\"field.config.attributes.virtualScroll\"\n [virtualScrollItemSize]=\"\n field.config.attributes.virtualScrollItemSize\n \"\n [lazy]=\"field.config.attributes.lazy\"\n [emptyMessage]=\"field.config.attributes.emptyMessage\"\n appendTo=\"body\"\n (onChange)=\"onEvent(field, $event, 'change')\"\n (onFocus)=\"onEvent(field, $event, 'focus')\"\n (onBlur)=\"onEvent(field, $event, 'blur')\"\n (onShow)=\"onEvent(field, $event, 'show')\"\n (onHide)=\"onEvent(field, $event, 'hide')\"\n (onClear)=\"onEvent(field, $event, 'clear')\"\n (onLazyLoad)=\"onEvent(field, $event, 'lazyLoad')\"\n >\n @if (\n field.config.attributes.isTemplate &&\n field.config.attributes.template\n ) {\n <ng-container\n [ngTemplateOutlet]=\"field.config.attributes.template\"\n />\n }\n </p-select>\n }\n\n <!-- multiSelect -->\n @case ('multiSelect') {\n <p-multiselect\n [id]=\"inputId(field, idSuffix)\"\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [options]=\"$any(optionsFor(field))\"\n [optionLabel]=\"field.config.attributes.optionLabel\"\n [optionValue]=\"field.config.attributes.optionValue\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n [filter]=\"field.config.attributes.filter\"\n [showClear]=\"field.config.attributes.showClear\"\n [selectionLimit]=\"field.config.attributes.selectionLimit\"\n [display]=\"field.config.attributes.display\"\n [loading]=\"field.config.attributes.loading\"\n [virtualScroll]=\"field.config.attributes.virtualScroll\"\n [virtualScrollItemSize]=\"\n field.config.attributes.virtualScrollItemSize\n \"\n [scrollHeight]=\"field.config.attributes.scrollHeight\"\n appendTo=\"body\"\n (onChange)=\"onEvent(field, $event, 'change')\"\n (onFocus)=\"onEvent(field, $event, 'focus')\"\n (onBlur)=\"onEvent(field, $event, 'blur')\"\n (onClear)=\"onEvent(field, $event, 'clear')\"\n (onHide)=\"onEvent(field, $event, 'hide')\"\n (onRemove)=\"onEvent(field, $event, 'remove')\"\n (onSelectAll)=\"onEvent(field, $event, 'select')\"\n >\n </p-multiselect>\n }\n\n <!-- autocomplete -->\n @case ('autocomplete') {\n <p-autocomplete\n [id]=\"inputId(field, idSuffix)\"\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n [suggestions]=\"field.config.attributes.suggestions\"\n [optionLabel]=\"field.config.attributes.optionLabel\"\n [multiple]=\"field.config.attributes.multiple\"\n [forceSelection]=\"field.config.attributes.forceSelection\"\n [dropdown]=\"field.config.attributes.dropdown\"\n [showClear]=\"field.config.attributes.showClear\"\n [completeOnFocus]=\"field.config.attributes.completeOnFocus\"\n [minLength]=\"field.config.attributes.minLength ?? 1\"\n [delay]=\"field.config.attributes.delay ?? 300\"\n [virtualScroll]=\"field.config.attributes.virtualScroll\"\n [virtualScrollItemSize]=\"\n field.config.attributes.virtualScrollItemSize\n \"\n [scrollHeight]=\"field.config.attributes.scrollHeight\"\n appendTo=\"body\"\n (completeMethod)=\"onEvent(field, $event, 'complete')\"\n (onSelect)=\"onEvent(field, $event, 'select')\"\n (onUnselect)=\"onEvent(field, $event, 'unselect')\"\n (onAdd)=\"onEvent(field, $event, 'add')\"\n (onFocus)=\"onEvent(field, $event, 'focus')\"\n (onBlur)=\"onEvent(field, $event, 'blur')\"\n (onClear)=\"onEvent(field, $event, 'clear')\"\n (onInputKeydown)=\"onEvent(field, $event, 'inputKeydown')\"\n >\n </p-autocomplete>\n }\n\n <!-- toggle -->\n @case ('toggle') {\n <p-toggleswitch\n [formControlName]=\"field.formControlName!\"\n [inputId]=\"inputId(field, idSuffix)\"\n [attr.aria-labelledby]=\"field.formControlName + 'Label'\"\n (onChange)=\"onEvent(field, $event, 'change')\"\n />\n }\n\n <!-- checkbox -->\n @case ('checkbox') {\n <p-checkbox\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [name]=\"field.config.attributes.name ?? field.formControlName!\"\n [value]=\"field.config.attributes.value\"\n (onChange)=\"onEvent(field, $event, 'change')\"\n />\n }\n\n <!-- radio -->\n @case ('radio') {\n @for (opt of $any(optionsFor(field)); track $index) {\n <div class=\"ngx-radio-item\">\n <p-radiobutton\n [inputId]=\"field.formControlName + '_' + $index\"\n [formControlName]=\"field.formControlName!\"\n [name]=\"field.formControlName!\"\n [value]=\"\n opt[field.config.attributes.optionValue ?? 'value']\n \"\n (onClick)=\"onEvent(field, $event, 'change')\"\n />\n <label [for]=\"field.formControlName + '_' + $index\">\n {{ opt[field.config.attributes.optionLabel ?? 'label'] }}\n </label>\n </div>\n }\n }\n\n <!-- datePicker / time / month / year -->\n @case ('datePicker') {\n <p-datepicker\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n [showIcon]=\"field.config.attributes.showIcon\"\n [minDate]=\"minDateFor(field)!\"\n [maxDate]=\"maxDateFor(field)!\"\n [selectionMode]=\"$any(field.config.attributes.selectionMode)\"\n [readonlyInput]=\"field.config.attributes.readonlyInput\"\n [showButtonBar]=\"field.config.attributes.showButtonBar\"\n [showTime]=\"field.config.attributes.showTime\"\n [hourFormat]=\"field.config.attributes.hourFormat\"\n [timeOnly]=\"field.config.attributes.timeOnly\"\n [view]=\"$any(field.config.attributes.view)\"\n [dateFormat]=\"field.config.attributes.dateFormat\"\n [numberOfMonths]=\"field.config.attributes.numberOfMonths ?? 1\"\n [showClear]=\"field.config.attributes.showClear\"\n appendTo=\"body\"\n (onSelect)=\"onEvent(field, $event, 'select')\"\n (onBlur)=\"onEvent(field, $event, 'blur')\"\n (onFocus)=\"onEvent(field, $event, 'focus')\"\n (onClear)=\"onEvent(field, $event, 'clear')\"\n >\n </p-datepicker>\n }\n\n @case ('time') {\n <p-datepicker\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n [showIcon]=\"true\"\n [iconDisplay]=\"'input'\"\n [timeOnly]=\"true\"\n [showTime]=\"true\"\n [hourFormat]=\"field.config.attributes.hourFormat ?? '24'\"\n appendTo=\"body\"\n (onSelect)=\"onEvent(field, $event, 'select')\"\n />\n }\n\n @case ('month') {\n <p-datepicker\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n [showIcon]=\"true\"\n [view]=\"'month'\"\n [dateFormat]=\"field.config.attributes.dateFormat ?? 'mm/yy'\"\n [readonlyInput]=\"true\"\n appendTo=\"body\"\n (onSelect)=\"onEvent(field, $event, 'select')\"\n />\n }\n\n @case ('year') {\n <p-datepicker\n [inputId]=\"inputId(field, idSuffix)\"\n [formControlName]=\"field.formControlName!\"\n [placeholder]=\"field.config.attributes.placeholder ?? ''\"\n [showIcon]=\"true\"\n [view]=\"'year'\"\n [dateFormat]=\"field.config.attributes.dateFormat ?? 'yy'\"\n [readonlyInput]=\"true\"\n appendTo=\"body\"\n (onSelect)=\"onEvent(field, $event, 'select')\"\n />\n }\n\n <!-- slider -->\n @case ('slider') {\n <p-slider\n [formControlName]=\"field.formControlName!\"\n [min]=\"field.config.attributes.min ?? 0\"\n [max]=\"field.config.attributes.max ?? 100\"\n [step]=\"field.config.attributes.step ?? 1\"\n [range]=\"field.config.attributes.range\"\n [orientation]=\"\n field.config.attributes.orientation ?? 'horizontal'\n \"\n (onChange)=\"onEvent(field, $event, 'change')\"\n />\n }\n\n <!-- rating -->\n @case ('rating') {\n <p-rating\n [formControlName]=\"field.formControlName!\"\n [stars]=\"field.config.attributes.stars ?? 5\"\n (onRate)=\"onEvent(field, $event, 'change')\"\n />\n }\n\n <!-- colorPicker -->\n @case ('colorPicker') {\n <p-colorpicker\n [formControlName]=\"field.formControlName!\"\n [inline]=\"field.config.attributes.inline\"\n (onChange)=\"onEvent(field, $event, 'change')\"\n />\n }\n }\n\n @if (\n field.config.attributes.fieldPos === 'right' &&\n field.config.attributes.fieldIcon\n ) {\n <p-inputgroup-addon>\n <i [class]=\"field.config.attributes.fieldIcon\">\n {{ field.config.attributes.fieldIconText ?? '' }}\n </i>\n </p-inputgroup-addon>\n }\n </p-inputgroup>\n }\n }\n </ng-container>\n</ng-template>\n", styles: [".ngx-form-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:1.25rem}.ngx-col-1{grid-column:span 1;min-width:0}.ngx-col-2{grid-column:span 2;min-width:0}.ngx-col-3{grid-column:span 3;min-width:0}.ngx-col-4{grid-column:span 4;min-width:0}.ngx-col-5{grid-column:span 5;min-width:0}.ngx-col-6{grid-column:span 6;min-width:0}.ngx-col-7{grid-column:span 7;min-width:0}.ngx-col-8{grid-column:span 8;min-width:0}.ngx-col-9{grid-column:span 9;min-width:0}.ngx-col-10{grid-column:span 10;min-width:0}.ngx-col-11{grid-column:span 11;min-width:0}.ngx-col-12{grid-column:span 12;min-width:0}@media(max-width:640px){.ngx-col-1,.ngx-col-2,.ngx-col-3,.ngx-col-4,.ngx-col-5,.ngx-col-6,.ngx-col-7,.ngx-col-8,.ngx-col-9,.ngx-col-10,.ngx-col-11{grid-column:span 12}}.ngx-form-title{font-size:1.25rem;font-weight:700;margin-bottom:1.25rem;color:var(--p-text-color, #111827)}.ngx-field-label{display:block;font-size:.775rem;font-weight:600;color:var(--p-text-muted-color, #4b5563);margin-bottom:.3rem;letter-spacing:.01em}.ngx-required{color:var(--p-red-500, #ef4444);margin-left:.15rem}.ngx-field-inner{margin-top:.2rem;width:100%}.ngx-field-inner .p-inputgroup,.ngx-field-inner .p-select,.ngx-field-inner .p-multiselect,.ngx-field-inner .p-autocomplete,.ngx-field-inner .p-datepicker,.ngx-field-inner .p-inputtext,.ngx-field-inner .p-textarea,.ngx-field-inner .p-password{width:100%}:host ::ng-deep .p-inputgroup{border:1px solid var(--p-surface-border, #d1d5db);border-radius:8px;background:#fff;transition:all .2s;display:flex;align-items:stretch}:host ::ng-deep .p-inputgroup p-inputgroup-addon,:host ::ng-deep .p-inputgroup .p-inputgroup-addon{background:transparent!important;border:none!important;color:#9ca3af;padding:.75rem .5rem .75rem 1rem;display:flex;align-items:center}:host ::ng-deep .p-inputgroup p-inputgroup-addon i,:host ::ng-deep .p-inputgroup .p-inputgroup-addon i{font-size:1.15rem}:host ::ng-deep .p-inputgroup:has(textarea) p-inputgroup-addon,:host ::ng-deep .p-inputgroup:has(textarea) .p-inputgroup-addon{align-items:flex-start;padding-top:1rem}:host ::ng-deep .p-inputgroup:has(input:focus),:host ::ng-deep .p-inputgroup:has(textarea:focus),:host ::ng-deep .p-inputgroup:has(.p-focus){border-color:#3b82f6;box-shadow:0 0 0 2px #3b82f633}:host ::ng-deep .p-inputgroup>.p-inputtext,:host ::ng-deep .p-inputgroup>.p-textarea,:host ::ng-deep .p-inputgroup p-select>.p-select,:host ::ng-deep .p-inputgroup p-multiselect>.p-multiselect,:host ::ng-deep .p-inputgroup p-autocomplete>.p-autocomplete,:host ::ng-deep .p-inputgroup p-datepicker>.p-datepicker{border:none!important;background:transparent!important;box-shadow:none!important;flex:1 1 auto;min-width:0}:host ::ng-deep .p-inputgroup:has(p-select),:host ::ng-deep .p-inputgroup:has(p-multiselect){position:relative}:host ::ng-deep .p-inputgroup:has(p-select) p-inputgroup-addon,:host ::ng-deep .p-inputgroup:has(p-multiselect) p-inputgroup-addon{position:absolute;z-index:2;border:none!important;background:transparent!important;height:100%;width:2.5rem;display:flex;align-items:center;justify-content:center}:host ::ng-deep .p-inputgroup:has(p-select) p-inputgroup-addon:first-child,:host ::ng-deep .p-inputgroup:has(p-multiselect) p-inputgroup-addon:first-child{left:0}:host ::ng-deep .p-inputgroup:has(p-select) p-select,:host ::ng-deep .p-inputgroup:has(p-select) p-multiselect,:host ::ng-deep .p-inputgroup:has(p-multiselect) p-select,:host ::ng-deep .p-inputgroup:has(p-multiselect) p-multiselect{width:100%;flex:1 1 100%}:host ::ng-deep .p-inputgroup:has(p-inputgroup-addon:first-child):has(p-select) .p-select-label,:host ::ng-deep .p-inputgroup:has(p-inputgroup-addon:first-child):has(p-select) .p-multiselect-label,:host ::ng-deep .p-inputgroup:has(p-inputgroup-addon:first-child):has(p-multiselect) .p-select-label,:host ::ng-deep .p-inputgroup:has(p-inputgroup-addon:first-child):has(p-multiselect) .p-multiselect-label{padding-left:2.75rem!important}:host ::ng-deep .ngx-toggle-card{display:flex;align-items:center;justify-content:space-between;padding:.9rem 1rem;border:1px solid var(--p-surface-border, #e5e7eb);border-radius:8px;background:#fff;margin-bottom:.75rem}:host ::ng-deep .ngx-toggle-card .ngx-toggle-card-icon{width:36px;height:36px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:1.1rem;margin-right:1rem}:host ::ng-deep .ngx-toggle-card .ngx-toggle-card-body{flex:1;min-width:0;padding-right:1.5rem}:host ::ng-deep .ngx-toggle-card .ngx-toggle-card-body .ngx-toggle-label{font-weight:600;color:var(--gray-900);margin-bottom:.2rem;font-size:.95rem}:host ::ng-deep .ngx-toggle-card .ngx-toggle-card-body .ngx-toggle-desc{color:var(--gray-500);font-size:.8rem;line-height:1.4}:host ::ng-deep .ngx-toggle-card p-toggleswitch,:host ::ng-deep .ngx-toggle-card .p-toggleswitch{flex:0 0 auto!important;width:44px!important;height:24px!important;display:block!important;position:relative!important;box-sizing:border-box!important}:host ::ng-deep .ngx-toggle-card .p-toggleswitch-slider{position:absolute!important;inset:0!important;width:100%!important;height:100%!important;border-radius:24px!important;transition:background-color .2s,box-shadow .2s!important;background:var(--p-surface-300, #cbd5e1)!important}:host ::ng-deep .ngx-toggle-card .p-toggleswitch-handle{position:absolute!important;top:50%!important;left:3px!important;margin-top:-9px!important;width:18px!important;height:18px!important;border-radius:50%!important;background:#fff!important;transition:transform .2s!important;box-shadow:0 1px 3px #0003!important}:host ::ng-deep .ngx-toggle-card .p-toggleswitch.p-toggleswitch-checked .p-toggleswitch-handle{transform:translate(20px)!important}:host ::ng-deep .ngx-toggle-card .p-toggleswitch.p-toggleswitch-checked .p-toggleswitch-slider{background:var(--toggle-color, var(--p-primary-500))!important}::ng-deep .p-select-overlay,::ng-deep .p-multiselect-overlay,::ng-deep .p-autocomplete-overlay{background:#fff!important;box-shadow:0 4px 15px #00000014,0 1px 3px #0000000d!important;border:1px solid var(--p-surface-border, #e5e7eb)!important;border-radius:8px!important;margin-top:6px!important;padding:.25rem!important}::ng-deep .p-select-overlay .p-select-header,::ng-deep .p-select-overlay .p-multiselect-header,::ng-deep .p-multiselect-overlay .p-select-header,::ng-deep .p-multiselect-overlay .p-multiselect-header,::ng-deep .p-autocomplete-overlay .p-select-header,::ng-deep .p-autocomplete-overlay .p-multiselect-header{padding:.5rem!important;margin-bottom:.25rem}::ng-deep .p-select-overlay .p-select-filter-container .p-inputtext,::ng-deep .p-select-overlay .p-select-filter,::ng-deep .p-select-overlay .p-multiselect-filter-container .p-inputtext,::ng-deep .p-select-overlay .p-multiselect-filter,::ng-deep .p-multiselect-overlay .p-select-filter-container .p-inputtext,::ng-deep .p-multiselect-overlay .p-select-filter,::ng-deep .p-multiselect-overlay .p-multiselect-filter-container .p-inputtext,::ng-deep .p-multiselect-overlay .p-multiselect-filter,::ng-deep .p-autocomplete-overlay .p-select-filter-container .p-inputtext,::ng-deep .p-autocomplete-overlay .p-select-filter,::ng-deep .p-autocomplete-overlay .p-multiselect-filter-container .p-inputtext,::ng-deep .p-autocomplete-overlay .p-multiselect-filter{padding:.5rem .75rem!important}::ng-deep .p-select-overlay .p-select-list,::ng-deep .p-select-overlay .p-multiselect-list,::ng-deep .p-multiselect-overlay .p-select-list,::ng-deep .p-multiselect-overlay .p-multiselect-list,::ng-deep .p-autocomplete-overlay .p-select-list,::ng-deep .p-autocomplete-overlay .p-multiselect-list{padding:0!important;display:flex;flex-direction:column;gap:2px}::ng-deep .p-select-overlay .p-select-option,::ng-deep .p-select-overlay .p-multiselect-option,::ng-deep .p-multiselect-overlay .p-select-option,::ng-deep .p-multiselect-overlay .p-multiselect-option,::ng-deep .p-autocomplete-overlay .p-select-option,::ng-deep .p-autocomplete-overlay .p-multiselect-option{border-radius:6px!important;margin:0!important;padding:.5rem .75rem!important;display:flex;align-items:center}::ng-deep .p-select-overlay .p-select-option:hover,::ng-deep .p-select-overlay .p-multiselect-option:hover,::ng-deep .p-multiselect-overlay .p-select-option:hover,::ng-deep .p-multiselect-overlay .p-multiselect-option:hover,::ng-deep .p-autocomplete-overlay .p-select-option:hover,::ng-deep .p-autocomplete-overlay .p-multiselect-option:hover{background:var(--p-surface-hover, #f3f4f6)!important}::ng-deep .p-select-overlay .p-select-option[data-p-highlight=true],::ng-deep .p-select-overlay .p-select-option.p-select-option-selected,::ng-deep .p-select-overlay .p-multiselect-option[data-p-highlight=true],::ng-deep .p-select-overlay .p-multiselect-option.p-select-option-selected,::ng-deep .p-multiselect-overlay .p-select-option[data-p-highlight=true],::ng-deep .p-multiselect-overlay .p-select-option.p-select-option-selected,::ng-deep .p-multiselect-overlay .p-multiselect-option[data-p-highlight=true],::ng-deep .p-multiselect-overlay .p-multiselect-option.p-select-option-selected,::ng-deep .p-autocomplete-overlay .p-select-option[data-p-highlight=true],::ng-deep .p-autocomplete-overlay .p-select-option.p-select-option-selected,::ng-deep .p-autocomplete-overlay .p-multiselect-option[data-p-highlight=true],::ng-deep .p-autocomplete-overlay .p-multiselect-option.p-select-option-selected{background:#f1f5f9!important;color:var(--p-primary-700, #334155)!important;font-weight:500}.ngx-error{display:block;color:var(--p-red-500, #ef4444);font-size:.72rem;line-height:1.4;min-height:1.008rem;margin-top:.25rem;font-weight:500}.ngx-error--placeholder{visibility:hidden}.ngx-hint{display:block;font-size:.72rem;margin-top:.25rem;color:var(--p-text-muted-color, #9ca3af);line-height:1.4}.ngx-btn-wrap{display:flex;align-items:flex-end;justify-content:stretch;width:100%;height:100%;padding-top:1.5rem}.ngx-btn-wrap button{width:100%;justify-content:center;font-weight:600}.ngx-file-inline{display:flex;flex-wrap:wrap;gap:.5rem}.ngx-image-list{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:.75rem}.ngx-image-card{position:relative}.ngx-image-remove{position:absolute;top:0;right:0;z-index:2;width:22px;height:22px;display:flex;align-items:center;justify-content:center;border-radius:50%;cursor:pointer;background:#fff;color:#111;box-shadow:0 2px 6px #0003;transform:translate(40%,-40%);font-size:.65rem;transition:background .15s,color .15s}.ngx-image-remove:hover{background:#fee2e2;color:#dc2626}.ngx-image-wrapper{position:relative;width:80px;height:80px;border-radius:8px;overflow:hidden;border:1px solid var(--p-surface-border, #e5e7eb)}.ngx-preview-img{object-fit:cover;transition:opacity .25s ease}.ngx-preview-img.loading{opacity:0}.ngx-preview-img.loaded{opacity:1}.ngx-img-skeleton{position:absolute;inset:0;background:linear-gradient(90deg,#f0f0f0 25%,#e0e0e0,#f0f0f0 75%);background-size:200% 100%;animation:shimmer 1.4s infinite;border-radius:8px}@keyframes shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.ngx-toggle-card{display:flex;align-items:center;gap:.85rem;padding:.9rem 1rem;border:1px solid var(--p-surface-border, #e5e7eb);border-radius:10px;background:#fff;width:100%;transition:border-color .2s,box-shadow .2s,background .2s}.ngx-toggle-card:has(.p-toggleswitch.p-toggleswitch-checked){border-color:var(--toggle-color, var(--p-primary-500, #3b82f6));box-shadow:0 0 0 3px color-mix(in srgb,var(--toggle-color, #3b82f6) 12%,transparent)}.ngx-toggle-card-icon{width:42px;height:42px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:1rem;flex-shrink:0}.ngx-toggle-card-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:.15rem}.ngx-toggle-card-label{font-size:.84rem;font-weight:600;color:var(--p-text-color, #111827);line-height:1.3}.ngx-toggle-card-desc{font-size:.72rem;color:var(--p-text-muted-color, #6b7280);line-height:1.4}.ngx-radio-item{display:flex;align-items:center;gap:.5rem;margin-bottom:.35rem;font-size:.875rem;color:var(--p-text-color, #374151)}.ngx-field-group{border:1px solid var(--p-surface-border, #e5e7eb);border-radius:10px;padding:1rem 1rem .5rem;display:flex;flex-direction:column;gap:.75rem}.ngx-field-group-row{display:block}.ngx-repeater{display:flex;flex-direction:column;gap:.75rem}.ngx-repeater-row{display:grid;grid-template-columns:1fr auto;gap:.5rem;align-items:start;padding:.75rem;border:1px solid var(--p-surface-border, #e5e7eb);border-radius:10px;background:var(--p-surface-50, #f9fafb)}.ngx-repeater-row-fields{display:grid;grid-template-columns:repeat(12,1fr);gap:.75rem;min-width:0}.ngx-repeater-remove{margin-top:.2rem}.ngx-repeater-add{align-self:flex-start}\n"] }]
430
+ }], ctorParameters: () => [], propDecorators: { formTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "formTitle", required: false }] }], fieldsInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldsInput", required: false }] }], schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: false }] }], stepFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "stepFields", required: false }] }], formSubmit: [{ type: i0.Output, args: ["formSubmit"] }], formChange: [{ type: i0.Output, args: ["formChange"] }] } });
431
+
432
+ /**
433
+ * Wizard-style wrapper that drives `<ngx-json-form>` step-by-step using the
434
+ * `FormSchema.steps` definition. Renders a header strip with step indicators,
435
+ * the current step's fields, and Back / Next / Submit controls.
436
+ */
437
+ class NgxJsonFormStepperComponent {
438
+ schema = input.required(...(ngDevMode ? [{ debugName: "schema" }] : /* istanbul ignore next */ []));
439
+ submitLabel = input('Submit', ...(ngDevMode ? [{ debugName: "submitLabel" }] : /* istanbul ignore next */ []));
440
+ formSubmit = output();
441
+ formChange = output();
442
+ formService = inject(FormEngineService);
443
+ activeIndex = computed(() => this.formService.activeStepIndex(), ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
444
+ activeStep = computed(() => this.formService.activeStep(), ...(ngDevMode ? [{ debugName: "activeStep" }] : /* istanbul ignore next */ []));
445
+ isLast = computed(() => {
446
+ const steps = this.schema().steps ?? [];
447
+ return this.activeIndex() === steps.length - 1;
448
+ }, ...(ngDevMode ? [{ debugName: "isLast" }] : /* istanbul ignore next */ []));
449
+ constructor() {
450
+ // Reset the wizard whenever the schema input changes
451
+ effect(() => {
452
+ this.schema();
453
+ this.formService.goToStep(0);
454
+ });
455
+ }
456
+ back() {
457
+ this.formService.prevStep();
458
+ }
459
+ next() {
460
+ this.formService.nextStep();
461
+ }
462
+ jumpTo(i) {
463
+ if (i <= this.activeIndex())
464
+ this.formService.goToStep(i);
465
+ }
466
+ submit() {
467
+ if (!this.formService.markAllAsTouchedAndValidate())
468
+ return;
469
+ const values = this.formService.buildSubmitPayload();
470
+ this.formSubmit.emit({
471
+ field: { config: { attributes: { inputType: 'button', buttonRole: 'submit' } } },
472
+ values,
473
+ valid: true,
474
+ type: 'submit',
475
+ });
476
+ }
477
+ /** Pass-through when the inner form emits formSubmit (e.g. via an inline button) */
478
+ onSubmit(e) {
479
+ this.formSubmit.emit(e);
480
+ }
481
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgxJsonFormStepperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
482
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: NgxJsonFormStepperComponent, isStandalone: true, selector: "ngx-json-form-stepper", inputs: { schema: { classPropertyName: "schema", publicName: "schema", isSignal: true, isRequired: true, transformFunction: null }, submitLabel: { classPropertyName: "submitLabel", publicName: "submitLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { formSubmit: "formSubmit", formChange: "formChange" }, ngImport: i0, template: `
483
+ @if (schema().steps?.length) {
484
+ <div class="ngx-stepper">
485
+ <!-- Step indicators -->
486
+ <ol class="ngx-stepper-bar">
487
+ @for (step of schema().steps; track step.id) {
488
+ <li
489
+ class="ngx-stepper-item"
490
+ [ngClass]="{
491
+ active: $index === activeIndex(),
492
+ done: $index < activeIndex()
493
+ }"
494
+ (click)="jumpTo($index)"
495
+ >
496
+ <span class="ngx-stepper-num">
497
+ @if ($index < activeIndex()) {
498
+ <i class="pi pi-check"></i>
499
+ } @else {
500
+ {{ $index + 1 }}
501
+ }
502
+ </span>
503
+ <span class="ngx-stepper-label">
504
+ @if (step.icon) {
505
+ <i [class]="step.icon"></i>
506
+ }
507
+ {{ step.title }}
508
+ </span>
509
+ </li>
510
+ }
511
+ </ol>
512
+
513
+ <!-- Step body -->
514
+ @if (activeStep(); as step) {
515
+ @if (step.description) {
516
+ <p class="ngx-stepper-desc">{{ step.description }}</p>
517
+ }
518
+ <ngx-json-form
519
+ [schema]="schema()"
520
+ [stepFields]="step.fields"
521
+ (formChange)="formChange.emit($event)"
522
+ (formSubmit)="onSubmit($event)"
523
+ />
524
+ }
525
+
526
+ <!-- Controls -->
527
+ <div class="ngx-stepper-actions">
528
+ <button
529
+ pButton
530
+ type="button"
531
+ class="p-button-text"
532
+ label="Back"
533
+ icon="pi pi-arrow-left"
534
+ [disabled]="activeIndex() === 0"
535
+ (click)="back()"
536
+ ></button>
537
+ @if (!isLast()) {
538
+ <button
539
+ pButton
540
+ type="button"
541
+ label="Next"
542
+ icon="pi pi-arrow-right"
543
+ iconPos="right"
544
+ (click)="next()"
545
+ ></button>
546
+ } @else {
547
+ <button
548
+ pButton
549
+ type="button"
550
+ [label]="submitLabel()"
551
+ icon="pi pi-check"
552
+ iconPos="right"
553
+ [disabled]="!formService.formValid()"
554
+ (click)="submit()"
555
+ ></button>
556
+ }
557
+ </div>
558
+ </div>
559
+ }
560
+ `, isInline: true, styles: [".ngx-stepper{display:flex;flex-direction:column;gap:1.25rem}.ngx-stepper-bar{list-style:none;margin:0;padding:0;display:flex;gap:.5rem;align-items:stretch;border-bottom:1px solid var(--p-surface-border, #e5e7eb);padding-bottom:1rem;overflow-x:auto}.ngx-stepper-item{flex:1 1 0;min-width:0;display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;border-radius:8px;cursor:pointer;color:var(--p-text-muted-color, #6b7280);font-size:.85rem;transition:background .15s,color .15s;-webkit-user-select:none;user-select:none}.ngx-stepper-item:hover{background:var(--p-surface-hover, #f3f4f6)}.ngx-stepper-item.active{color:var(--p-primary-700, #1d4ed8);background:var(--p-primary-50, #eff6ff)}.ngx-stepper-item.active .ngx-stepper-num{background:var(--p-primary-500, #3b82f6);color:#fff}.ngx-stepper-item.done{color:var(--p-primary-600, #2563eb)}.ngx-stepper-item.done .ngx-stepper-num{background:var(--p-primary-100, #dbeafe);color:var(--p-primary-700, #1d4ed8)}.ngx-stepper-num{flex:0 0 auto;width:26px;height:26px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:var(--p-surface-200, #e5e7eb);color:var(--p-text-muted-color, #6b7280);font-size:.78rem;font-weight:600}.ngx-stepper-label{display:inline-flex;align-items:center;gap:.4rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ngx-stepper-desc{margin:0;font-size:.85rem;color:var(--p-text-muted-color, #6b7280)}.ngx-stepper-actions{display:flex;justify-content:space-between;border-top:1px solid var(--p-surface-border, #e5e7eb);padding-top:1rem}\n"], dependencies: [{ kind: "component", type: NgxJsonFormComponent, selector: "ngx-json-form", inputs: ["formTitle", "fieldsInput", "schema", "stepFields"], outputs: ["formSubmit", "formChange"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i11.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
561
+ }
562
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: NgxJsonFormStepperComponent, decorators: [{
563
+ type: Component,
564
+ args: [{ selector: 'ngx-json-form-stepper', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgxJsonFormComponent, ButtonModule, NgClass], template: `
565
+ @if (schema().steps?.length) {
566
+ <div class="ngx-stepper">
567
+ <!-- Step indicators -->
568
+ <ol class="ngx-stepper-bar">
569
+ @for (step of schema().steps; track step.id) {
570
+ <li
571
+ class="ngx-stepper-item"
572
+ [ngClass]="{
573
+ active: $index === activeIndex(),
574
+ done: $index < activeIndex()
575
+ }"
576
+ (click)="jumpTo($index)"
577
+ >
578
+ <span class="ngx-stepper-num">
579
+ @if ($index < activeIndex()) {
580
+ <i class="pi pi-check"></i>
581
+ } @else {
582
+ {{ $index + 1 }}
583
+ }
584
+ </span>
585
+ <span class="ngx-stepper-label">
586
+ @if (step.icon) {
587
+ <i [class]="step.icon"></i>
588
+ }
589
+ {{ step.title }}
590
+ </span>
591
+ </li>
592
+ }
593
+ </ol>
594
+
595
+ <!-- Step body -->
596
+ @if (activeStep(); as step) {
597
+ @if (step.description) {
598
+ <p class="ngx-stepper-desc">{{ step.description }}</p>
599
+ }
600
+ <ngx-json-form
601
+ [schema]="schema()"
602
+ [stepFields]="step.fields"
603
+ (formChange)="formChange.emit($event)"
604
+ (formSubmit)="onSubmit($event)"
605
+ />
606
+ }
607
+
608
+ <!-- Controls -->
609
+ <div class="ngx-stepper-actions">
610
+ <button
611
+ pButton
612
+ type="button"
613
+ class="p-button-text"
614
+ label="Back"
615
+ icon="pi pi-arrow-left"
616
+ [disabled]="activeIndex() === 0"
617
+ (click)="back()"
618
+ ></button>
619
+ @if (!isLast()) {
620
+ <button
621
+ pButton
622
+ type="button"
623
+ label="Next"
624
+ icon="pi pi-arrow-right"
625
+ iconPos="right"
626
+ (click)="next()"
627
+ ></button>
628
+ } @else {
629
+ <button
630
+ pButton
631
+ type="button"
632
+ [label]="submitLabel()"
633
+ icon="pi pi-check"
634
+ iconPos="right"
635
+ [disabled]="!formService.formValid()"
636
+ (click)="submit()"
637
+ ></button>
638
+ }
639
+ </div>
640
+ </div>
641
+ }
642
+ `, styles: [".ngx-stepper{display:flex;flex-direction:column;gap:1.25rem}.ngx-stepper-bar{list-style:none;margin:0;padding:0;display:flex;gap:.5rem;align-items:stretch;border-bottom:1px solid var(--p-surface-border, #e5e7eb);padding-bottom:1rem;overflow-x:auto}.ngx-stepper-item{flex:1 1 0;min-width:0;display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;border-radius:8px;cursor:pointer;color:var(--p-text-muted-color, #6b7280);font-size:.85rem;transition:background .15s,color .15s;-webkit-user-select:none;user-select:none}.ngx-stepper-item:hover{background:var(--p-surface-hover, #f3f4f6)}.ngx-stepper-item.active{color:var(--p-primary-700, #1d4ed8);background:var(--p-primary-50, #eff6ff)}.ngx-stepper-item.active .ngx-stepper-num{background:var(--p-primary-500, #3b82f6);color:#fff}.ngx-stepper-item.done{color:var(--p-primary-600, #2563eb)}.ngx-stepper-item.done .ngx-stepper-num{background:var(--p-primary-100, #dbeafe);color:var(--p-primary-700, #1d4ed8)}.ngx-stepper-num{flex:0 0 auto;width:26px;height:26px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:var(--p-surface-200, #e5e7eb);color:var(--p-text-muted-color, #6b7280);font-size:.78rem;font-weight:600}.ngx-stepper-label{display:inline-flex;align-items:center;gap:.4rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ngx-stepper-desc{margin:0;font-size:.85rem;color:var(--p-text-muted-color, #6b7280)}.ngx-stepper-actions{display:flex;justify-content:space-between;border-top:1px solid var(--p-surface-border, #e5e7eb);padding-top:1rem}\n"] }]
643
+ }], ctorParameters: () => [], propDecorators: { schema: [{ type: i0.Input, args: [{ isSignal: true, alias: "schema", required: true }] }], submitLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitLabel", required: false }] }], formSubmit: [{ type: i0.Output, args: ["formSubmit"] }], formChange: [{ type: i0.Output, args: ["formChange"] }] } });
644
+
645
+ /**
646
+ * Generated bundle index. Do not edit.
647
+ */
648
+
649
+ export { NgxJsonFormComponent, NgxJsonFormStepperComponent };
650
+ //# sourceMappingURL=ngx-json-forms-primeng.mjs.map