@praxisui/manual-form 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1592 @@
1
+ import { ensureIds, DynamicFormService, CONFIG_STORAGE, FieldControlType } from '@praxisui/core';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, Optional, Inject, Injectable, input, output, ChangeDetectionStrategy, Component, Input, InjectionToken, isDevMode, ChangeDetectorRef, DestroyRef, ContentChildren, Directive, HostListener } from '@angular/core';
4
+ import { BehaviorSubject, debounceTime } from 'rxjs';
5
+ import * as i1 from '@angular/common';
6
+ import { CommonModule } from '@angular/common';
7
+ import * as i2 from '@angular/forms';
8
+ import { FormGroupDirective, FormGroup, FormControl, Validators, ReactiveFormsModule, FormControlName, ControlContainer, FormsModule } from '@angular/forms';
9
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
10
+ import * as i3 from '@praxisui/settings-panel';
11
+ import { SettingsPanelService, SETTINGS_PANEL_DATA, SETTINGS_PANEL_REF } from '@praxisui/settings-panel';
12
+
13
+ function deepClone(value) {
14
+ if (typeof globalThis?.structuredClone === 'function') {
15
+ return globalThis.structuredClone(value);
16
+ }
17
+ try {
18
+ return JSON.parse(JSON.stringify(value));
19
+ }
20
+ catch {
21
+ if (Array.isArray(value)) {
22
+ return value.slice();
23
+ }
24
+ if (value && typeof value === 'object') {
25
+ return { ...value };
26
+ }
27
+ return value;
28
+ }
29
+ }
30
+
31
+ function createManualFormSeed(formId, config, options = {}) {
32
+ const cloned = deepClone(config);
33
+ const normalized = ensureIds(cloned);
34
+ normalized.fieldMetadata = (normalized.fieldMetadata || []).map((fm) => ({ ...fm }));
35
+ return {
36
+ formId,
37
+ config: normalized,
38
+ source: options.source ?? 'local',
39
+ version: options.version ?? normalized.metadata?.version,
40
+ context: options.context,
41
+ };
42
+ }
43
+ function toFieldMetadataMap(fieldMetadata) {
44
+ const map = new Map();
45
+ for (const meta of fieldMetadata || []) {
46
+ if (!meta?.name)
47
+ continue;
48
+ map.set(meta.name, { ...meta });
49
+ }
50
+ return map;
51
+ }
52
+
53
+ /**
54
+ * Runtime instance that keeps manual form configuration, metadata and
55
+ * FormGroup in sync. The instance is created through the factory below so the
56
+ * host applications do not need to manage storage or dynamic form services.
57
+ */
58
+ class ManualFormInstance {
59
+ formId;
60
+ seed;
61
+ dynamicFormService;
62
+ storage;
63
+ persistenceOptions;
64
+ formConfig$;
65
+ fieldMetadata$;
66
+ state$;
67
+ formGroup;
68
+ boundComponents = new Map();
69
+ constructor(formId, seed, initialConfig, dynamicFormService, storage, persistenceOptions, externalForm) {
70
+ this.formId = formId;
71
+ this.seed = seed;
72
+ this.dynamicFormService = dynamicFormService;
73
+ this.storage = storage;
74
+ this.persistenceOptions = persistenceOptions;
75
+ const configClone = deepClone(initialConfig);
76
+ const metadataMap = toFieldMetadataMap(configClone.fieldMetadata);
77
+ this.formConfig$ = new BehaviorSubject(configClone);
78
+ this.fieldMetadata$ = new BehaviorSubject(metadataMap);
79
+ if (externalForm) {
80
+ this.formGroup = externalForm;
81
+ // Ensure controls exist and match metadata on the provided form
82
+ this.cloneFieldMetadata(metadataMap).forEach((meta) => {
83
+ try {
84
+ this.dynamicFormService.updateControlFromMetadata(this.formGroup, meta, {
85
+ emitEvent: false,
86
+ });
87
+ }
88
+ catch {
89
+ /* ignore */
90
+ }
91
+ });
92
+ }
93
+ else {
94
+ this.formGroup = this.dynamicFormService.createFormGroupFromMetadata(this.cloneFieldMetadata(metadataMap));
95
+ }
96
+ this.state$ = new BehaviorSubject({
97
+ formId: this.formId,
98
+ config: deepClone(configClone),
99
+ fields: this.cloneFieldMetadata(metadataMap),
100
+ });
101
+ }
102
+ /** Observable stream for consumers interested in config mutations. */
103
+ get stateChanges$() {
104
+ return this.state$.asObservable();
105
+ }
106
+ get formConfigChanges$() {
107
+ return this.formConfig$.asObservable();
108
+ }
109
+ get fieldMetadataChanges$() {
110
+ return this.fieldMetadata$.asObservable();
111
+ }
112
+ get currentConfig() {
113
+ return this.formConfig$.value;
114
+ }
115
+ get currentFieldMetadata() {
116
+ return this.fieldMetadata$.value;
117
+ }
118
+ get form() {
119
+ return this.formGroup;
120
+ }
121
+ /** Retrieve metadata for a specific field (cloned to avoid mutations). */
122
+ getFieldMetadata(fieldName) {
123
+ const meta = this.fieldMetadata$.value.get(fieldName);
124
+ return meta ? { ...meta } : undefined;
125
+ }
126
+ /**
127
+ * Registers a component instance to receive metadata updates for the
128
+ * specified field. Helpful for manual templates that wish to keep hot-update
129
+ * behaviour without wiring everything manually.
130
+ */
131
+ bindComponent(fieldName, componentInstance) {
132
+ if (!fieldName || !componentInstance) {
133
+ return;
134
+ }
135
+ this.boundComponents.set(fieldName, componentInstance);
136
+ this.refreshBoundComponent(fieldName);
137
+ }
138
+ unbindComponent(fieldName) {
139
+ this.boundComponents.delete(fieldName);
140
+ }
141
+ /**
142
+ * Applies a partial metadata patch to a field and propagates the changes to
143
+ * the FormConfig, runtime FormGroup and state streams.
144
+ */
145
+ patchFieldMetadata(fieldName, patch) {
146
+ const current = this.fieldMetadata$.value.get(fieldName);
147
+ if (!current) {
148
+ return;
149
+ }
150
+ const next = this.deepMerge(current, patch);
151
+ this.fieldMetadata$.value.set(fieldName, next);
152
+ this.fieldMetadata$.next(new Map(this.fieldMetadata$.value));
153
+ const configClone = deepClone(this.formConfig$.value);
154
+ configClone.fieldMetadata = (configClone.fieldMetadata || []).map((fm) => fm.name === fieldName ? { ...next } : fm);
155
+ this.publishConfig(configClone);
156
+ try {
157
+ this.dynamicFormService.updateControlFromMetadata(this.formGroup, next, {
158
+ emitEvent: false,
159
+ });
160
+ }
161
+ catch {
162
+ /* Defensive: control might not exist if form is customised manually. */
163
+ }
164
+ this.refreshBoundComponent(fieldName);
165
+ }
166
+ /** Replaces the entire FormConfig, typically used after reconciling with backend seeds. */
167
+ replaceConfig(config) {
168
+ const cloned = deepClone(config);
169
+ this.fieldMetadata$.next(toFieldMetadataMap(cloned.fieldMetadata));
170
+ this.publishConfig(cloned);
171
+ const metadataList = this.getFieldMetadataArray();
172
+ // Rebuild controls to ensure validators align with the new metadata.
173
+ metadataList.forEach((meta) => {
174
+ try {
175
+ this.dynamicFormService.updateControlFromMetadata(this.formGroup, meta, {
176
+ emitEvent: false,
177
+ });
178
+ }
179
+ catch {
180
+ /* ignore */
181
+ }
182
+ });
183
+ // Refresh all bound components with the new metadata references.
184
+ for (const fieldName of this.boundComponents.keys()) {
185
+ this.refreshBoundComponent(fieldName);
186
+ }
187
+ }
188
+ /** Restores configuration back to the original seed definition. */
189
+ resetToSeed() {
190
+ this.replaceConfig(this.seed.config);
191
+ }
192
+ /**
193
+ * Persists the current configuration and, optionally, the provided form value
194
+ * using ConfigStorage. By default, stores the FormGroup raw value.
195
+ */
196
+ saveDraft(value = null) {
197
+ if (!this.storage) {
198
+ return;
199
+ }
200
+ const key = buildStorageKey(this.formId, this.persistenceOptions);
201
+ const payload = {
202
+ config: deepClone(this.formConfig$.value),
203
+ value: value ?? this.formGroup.getRawValue(),
204
+ metadata: {
205
+ savedAt: new Date().toISOString(),
206
+ savedBy: this.persistenceOptions?.profileId,
207
+ host: this.persistenceOptions?.namespace,
208
+ },
209
+ version: this.seed.version,
210
+ };
211
+ this.storage.saveConfig(key, payload);
212
+ }
213
+ publishConfig(config) {
214
+ this.formConfig$.next(config);
215
+ this.state$.next({
216
+ formId: this.formId,
217
+ config: deepClone(config),
218
+ fields: this.getFieldMetadataArray(),
219
+ });
220
+ }
221
+ getFieldMetadataArray() {
222
+ return this.cloneFieldMetadata();
223
+ }
224
+ cloneFieldMetadata(map = this.fieldMetadata$.value) {
225
+ return Array.from(map.values()).map((fm) => ({ ...fm }));
226
+ }
227
+ refreshBoundComponent(fieldName) {
228
+ const component = this.boundComponents.get(fieldName);
229
+ if (!component) {
230
+ return;
231
+ }
232
+ const metadata = this.fieldMetadata$.value.get(fieldName);
233
+ if (!metadata) {
234
+ return;
235
+ }
236
+ try {
237
+ if (typeof component?.setInputMetadata === 'function') {
238
+ component.setInputMetadata({ ...metadata });
239
+ // fallthrough to also apply visibility state on host
240
+ }
241
+ const metaProp = component?.metadata;
242
+ if (metaProp && typeof metaProp === 'object') {
243
+ if (typeof metaProp.set === 'function') {
244
+ metaProp.set({ ...metadata });
245
+ // fallthrough to also apply visibility state on host
246
+ }
247
+ }
248
+ if (!metaProp || typeof metaProp.set !== 'function') {
249
+ component.metadata = { ...metadata };
250
+ }
251
+ // Apply visibility to component and host element for manual templates
252
+ const isHidden = !!metadata.hidden || !!metadata.formHidden;
253
+ if ('visible' in component) {
254
+ try {
255
+ component.visible = !isHidden;
256
+ }
257
+ catch { }
258
+ }
259
+ const hostEl = component?.elementRef?.nativeElement;
260
+ if (hostEl) {
261
+ hostEl.style.display = isHidden ? 'none' : '';
262
+ hostEl.setAttribute('aria-hidden', String(isHidden));
263
+ }
264
+ }
265
+ catch {
266
+ /* ignore */
267
+ }
268
+ }
269
+ /** Deep-merge helper to preserve nested objects (e.g., clearButton.icon + enabled). */
270
+ deepMerge(target, source) {
271
+ if (source == null || typeof source !== 'object')
272
+ return { ...target };
273
+ const out = Array.isArray(target) ? [...target] : { ...target };
274
+ for (const [k, v] of Object.entries(source)) {
275
+ if (v && typeof v === 'object' && !Array.isArray(v)) {
276
+ out[k] = this.deepMerge(out[k] ?? {}, v);
277
+ }
278
+ else {
279
+ out[k] = v;
280
+ }
281
+ }
282
+ return out;
283
+ }
284
+ }
285
+ /**
286
+ * Factory service used to create ManualFormInstance objects with the required
287
+ * dependencies injected from Angular's DI container.
288
+ */
289
+ class ManualFormInstanceFactory {
290
+ storage;
291
+ dynamicFormService = inject(DynamicFormService);
292
+ constructor(storage) {
293
+ this.storage = storage;
294
+ }
295
+ create(seed, options = {}, externalForm) {
296
+ const persisted = this.loadPersistedSnapshot(seed.formId, options);
297
+ const baseConfig = persisted?.config ?? deepClone(seed.config);
298
+ const instance = new ManualFormInstance(seed.formId, seed, baseConfig, this.dynamicFormService, this.storage, options, externalForm);
299
+ // Apply persisted value if present.
300
+ if (persisted?.value) {
301
+ instance.form.patchValue(persisted.value, { emitEvent: false });
302
+ }
303
+ return instance;
304
+ }
305
+ loadPersistedSnapshot(formId, options) {
306
+ if (!this.storage) {
307
+ return undefined;
308
+ }
309
+ const key = buildStorageKey(formId, options);
310
+ return this.storage.loadConfig(key) ?? undefined;
311
+ }
312
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormInstanceFactory, deps: [{ token: CONFIG_STORAGE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
313
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormInstanceFactory, providedIn: 'root' });
314
+ }
315
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormInstanceFactory, decorators: [{
316
+ type: Injectable,
317
+ args: [{ providedIn: 'root' }]
318
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
319
+ type: Optional
320
+ }, {
321
+ type: Inject,
322
+ args: [CONFIG_STORAGE]
323
+ }] }] });
324
+ function buildStorageKey(formId, options = {}) {
325
+ const parts = ['manual-form', options.namespace, options.tenantId, options.profileId, formId]
326
+ .filter(Boolean)
327
+ .map((segment) => String(segment));
328
+ return parts.join(':');
329
+ }
330
+
331
+ class ManualFormHeaderComponent {
332
+ instance = input(...(ngDevMode ? [undefined, { debugName: "instance" }] : []));
333
+ title = input(...(ngDevMode ? [undefined, { debugName: "title" }] : []));
334
+ description = input(...(ngDevMode ? [undefined, { debugName: "description" }] : []));
335
+ saveLabel = input('Salvar customizações', ...(ngDevMode ? [{ debugName: "saveLabel" }] : []));
336
+ resetLabel = input('Restaurar padrão', ...(ngDevMode ? [{ debugName: "resetLabel" }] : []));
337
+ editModeEnabled = input(false, ...(ngDevMode ? [{ debugName: "editModeEnabled" }] : []));
338
+ editFormLabel = input('Editar formulário', ...(ngDevMode ? [{ debugName: "editFormLabel" }] : []));
339
+ save = output();
340
+ reset = output();
341
+ editForm = output();
342
+ fallbackTitle = 'Formulário manual';
343
+ get metadata() {
344
+ return this.instance()?.currentConfig?.metadata;
345
+ }
346
+ get firstSection() {
347
+ return this.instance()?.currentConfig?.sections?.[0];
348
+ }
349
+ get displayTitle() {
350
+ return (this.title() ||
351
+ this.metadata?.title ||
352
+ this.firstSection?.title ||
353
+ this.fallbackTitle);
354
+ }
355
+ get displayDescription() {
356
+ return (this.description() ||
357
+ this.metadata?.description ||
358
+ this.firstSection?.description ||
359
+ undefined);
360
+ }
361
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
362
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: ManualFormHeaderComponent, isStandalone: true, selector: "praxis-manual-form-header", inputs: { instance: { classPropertyName: "instance", publicName: "instance", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, saveLabel: { classPropertyName: "saveLabel", publicName: "saveLabel", isSignal: true, isRequired: false, transformFunction: null }, resetLabel: { classPropertyName: "resetLabel", publicName: "resetLabel", isSignal: true, isRequired: false, transformFunction: null }, editModeEnabled: { classPropertyName: "editModeEnabled", publicName: "editModeEnabled", isSignal: true, isRequired: false, transformFunction: null }, editFormLabel: { classPropertyName: "editFormLabel", publicName: "editFormLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { save: "save", reset: "reset", editForm: "editForm" }, ngImport: i0, template: `
363
+ <header class="pdx-manual-form__header">
364
+ <div class="pdx-manual-form__title">
365
+ <h2>{{ displayTitle }}</h2>
366
+ @if (displayDescription) {
367
+ <p class="pdx-manual-form__subtitle">
368
+ {{ displayDescription }}
369
+ </p>
370
+ }
371
+ </div>
372
+ <div class="pdx-manual-form__header-actions">
373
+ @if (editModeEnabled()) {
374
+ <button type="button" (click)="editForm.emit()" [attr.aria-label]="editFormLabel()">{{ editFormLabel() }}</button>
375
+ }
376
+ <button type="button" (click)="reset.emit()" [attr.aria-label]="resetLabel()">{{ resetLabel() }}</button>
377
+ <button type="button" (click)="save.emit()" [attr.aria-label]="saveLabel()">{{ saveLabel() }}</button>
378
+ </div>
379
+ </header>
380
+ `, isInline: true, styles: [":host{display:block}.pdx-manual-form__header{display:flex;justify-content:space-between;align-items:center;gap:1rem;margin-bottom:1.5rem}.pdx-manual-form__title h2{margin:0;font-size:1.5rem}.pdx-manual-form__subtitle{margin:.25rem 0 0;color:#0009}.pdx-manual-form__header-actions{display:flex;gap:.5rem}button[type=button]{padding:.5rem 1rem;border-radius:4px;border:1px solid rgba(15,23,42,.1);background:#fff;cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
381
+ }
382
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormHeaderComponent, decorators: [{
383
+ type: Component,
384
+ args: [{ selector: 'praxis-manual-form-header', standalone: true, imports: [CommonModule], template: `
385
+ <header class="pdx-manual-form__header">
386
+ <div class="pdx-manual-form__title">
387
+ <h2>{{ displayTitle }}</h2>
388
+ @if (displayDescription) {
389
+ <p class="pdx-manual-form__subtitle">
390
+ {{ displayDescription }}
391
+ </p>
392
+ }
393
+ </div>
394
+ <div class="pdx-manual-form__header-actions">
395
+ @if (editModeEnabled()) {
396
+ <button type="button" (click)="editForm.emit()" [attr.aria-label]="editFormLabel()">{{ editFormLabel() }}</button>
397
+ }
398
+ <button type="button" (click)="reset.emit()" [attr.aria-label]="resetLabel()">{{ resetLabel() }}</button>
399
+ <button type="button" (click)="save.emit()" [attr.aria-label]="saveLabel()">{{ saveLabel() }}</button>
400
+ </div>
401
+ </header>
402
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}.pdx-manual-form__header{display:flex;justify-content:space-between;align-items:center;gap:1rem;margin-bottom:1.5rem}.pdx-manual-form__title h2{margin:0;font-size:1.5rem}.pdx-manual-form__subtitle{margin:.25rem 0 0;color:#0009}.pdx-manual-form__header-actions{display:flex;gap:.5rem}button[type=button]{padding:.5rem 1rem;border-radius:4px;border:1px solid rgba(15,23,42,.1);background:#fff;cursor:pointer}\n"] }]
403
+ }] });
404
+
405
+ class ManualFormActionsComponent {
406
+ actions = input(...(ngDevMode ? [undefined, { debugName: "actions" }] : []));
407
+ trackByFn = null;
408
+ actionClick = output();
409
+ isVisible(action) {
410
+ return action ? action.visible !== false : false;
411
+ }
412
+ emit(kind, action) {
413
+ if (action?.action) {
414
+ this.actionClick.emit(action.action);
415
+ }
416
+ else {
417
+ this.actionClick.emit(kind);
418
+ }
419
+ }
420
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormActionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
421
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: ManualFormActionsComponent, isStandalone: true, selector: "praxis-manual-form-actions", inputs: { actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, trackByFn: { classPropertyName: "trackByFn", publicName: "trackByFn", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { actionClick: "actionClick" }, ngImport: i0, template: `
422
+ @if (actions(); as layout) {
423
+ <footer class="pdx-manual-form__actions">
424
+ <div class="pdx-manual-form__actions-secondary">
425
+ @if (layout.cancel; as cancel) {
426
+ @if (isVisible(cancel)) {
427
+ <button type="button" [disabled]="cancel.disabled" (click)="emit('cancel', cancel)">
428
+ {{ cancel.label || 'Cancelar' }}
429
+ </button>
430
+ }
431
+ }
432
+ @if (layout.reset; as resetBtn) {
433
+ @if (isVisible(resetBtn)) {
434
+ <button type="button" [disabled]="resetBtn.disabled" (click)="emit('reset', resetBtn)">
435
+ {{ resetBtn.label || 'Resetar' }}
436
+ </button>
437
+ }
438
+ }
439
+ @for (action of layout.custom || []; track trackByFn ? trackByFn($index, action) : (action?.id || $index)) {
440
+ @if (isVisible(action)) {
441
+ <button type="button" [disabled]="action.disabled" (click)="emit(action.id || 'custom', action)">
442
+ {{ action.label || action.id || 'Ação' }}
443
+ </button>
444
+ }
445
+ }
446
+ </div>
447
+ <div class="pdx-manual-form__actions-primary">
448
+ @if (layout.submit; as submitBtn) {
449
+ @if (isVisible(submitBtn)) {
450
+ <button type="submit" [disabled]="submitBtn.disabled" (click)="emit('submit', submitBtn)">
451
+ {{ submitBtn.label || 'Salvar' }}
452
+ </button>
453
+ }
454
+ }
455
+ </div>
456
+ </footer>
457
+ }
458
+ `, isInline: true, styles: [":host{display:block}.pdx-manual-form__actions{display:flex;justify-content:space-between;align-items:center;gap:1rem;margin-top:2rem}.pdx-manual-form__actions-secondary,.pdx-manual-form__actions-primary{display:flex;gap:.5rem}button{padding:.5rem 1rem;border-radius:4px;border:1px solid rgba(15,23,42,.1);background:#fff;cursor:pointer}.pdx-manual-form__actions-primary button{background:#1d4ed8;color:#fff;border-color:#1d4ed8}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
459
+ }
460
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormActionsComponent, decorators: [{
461
+ type: Component,
462
+ args: [{ selector: 'praxis-manual-form-actions', standalone: true, imports: [CommonModule], template: `
463
+ @if (actions(); as layout) {
464
+ <footer class="pdx-manual-form__actions">
465
+ <div class="pdx-manual-form__actions-secondary">
466
+ @if (layout.cancel; as cancel) {
467
+ @if (isVisible(cancel)) {
468
+ <button type="button" [disabled]="cancel.disabled" (click)="emit('cancel', cancel)">
469
+ {{ cancel.label || 'Cancelar' }}
470
+ </button>
471
+ }
472
+ }
473
+ @if (layout.reset; as resetBtn) {
474
+ @if (isVisible(resetBtn)) {
475
+ <button type="button" [disabled]="resetBtn.disabled" (click)="emit('reset', resetBtn)">
476
+ {{ resetBtn.label || 'Resetar' }}
477
+ </button>
478
+ }
479
+ }
480
+ @for (action of layout.custom || []; track trackByFn ? trackByFn($index, action) : (action?.id || $index)) {
481
+ @if (isVisible(action)) {
482
+ <button type="button" [disabled]="action.disabled" (click)="emit(action.id || 'custom', action)">
483
+ {{ action.label || action.id || 'Ação' }}
484
+ </button>
485
+ }
486
+ }
487
+ </div>
488
+ <div class="pdx-manual-form__actions-primary">
489
+ @if (layout.submit; as submitBtn) {
490
+ @if (isVisible(submitBtn)) {
491
+ <button type="submit" [disabled]="submitBtn.disabled" (click)="emit('submit', submitBtn)">
492
+ {{ submitBtn.label || 'Salvar' }}
493
+ </button>
494
+ }
495
+ }
496
+ </div>
497
+ </footer>
498
+ }
499
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}.pdx-manual-form__actions{display:flex;justify-content:space-between;align-items:center;gap:1rem;margin-top:2rem}.pdx-manual-form__actions-secondary,.pdx-manual-form__actions-primary{display:flex;gap:.5rem}button{padding:.5rem 1rem;border-radius:4px;border:1px solid rgba(15,23,42,.1);background:#fff;cursor:pointer}.pdx-manual-form__actions-primary button{background:#1d4ed8;color:#fff;border-color:#1d4ed8}\n"] }]
500
+ }], propDecorators: { trackByFn: [{
501
+ type: Input
502
+ }] } });
503
+
504
+ const MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE = new InjectionToken('MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE');
505
+ const MANUAL_FORM_CONSTRUCTOR_TO_CONTROL_TYPE = new InjectionToken('MANUAL_FORM_CONSTRUCTOR_TO_CONTROL_TYPE');
506
+
507
+ class ManualFieldMetadataBridgeService {
508
+ patchSignatures = new WeakMap();
509
+ settingsPanel = inject(SettingsPanelService);
510
+ buildEditorSeed(instance, fieldName) {
511
+ return instance.getFieldMetadata(fieldName);
512
+ }
513
+ applyPatch(instance, fieldName, patch) {
514
+ instance.patchFieldMetadata(fieldName, patch);
515
+ }
516
+ async openEditor(instance, fieldName) {
517
+ if (!instance || !fieldName) {
518
+ return;
519
+ }
520
+ const seed = this.buildEditorSeed(instance, fieldName);
521
+ if (!seed) {
522
+ if (isDevMode()) {
523
+ try {
524
+ console.warn('[ManualFieldMetadataBridge] metadata not found for field', fieldName);
525
+ }
526
+ catch { }
527
+ }
528
+ return;
529
+ }
530
+ let FieldMetadataEditorComponent;
531
+ try {
532
+ const module = await import('@praxisui/metadata-editor');
533
+ FieldMetadataEditorComponent = module?.FieldMetadataEditorComponent;
534
+ if (!FieldMetadataEditorComponent) {
535
+ throw new Error('FieldMetadataEditorComponent export missing');
536
+ }
537
+ }
538
+ catch (err) {
539
+ if (isDevMode()) {
540
+ try {
541
+ console.error('[ManualFieldMetadataBridge] failed to load @praxisui/metadata-editor', err);
542
+ }
543
+ catch { }
544
+ }
545
+ return;
546
+ }
547
+ const controlType = seed?.controlType;
548
+ const title = this.buildPanelTitle(fieldName, seed);
549
+ const panelId = this.buildPanelId(instance, fieldName);
550
+ let ref;
551
+ try {
552
+ ref = this.settingsPanel.open({
553
+ id: panelId,
554
+ title,
555
+ titleIcon: this.resolveIcon(controlType),
556
+ content: {
557
+ component: FieldMetadataEditorComponent,
558
+ inputs: {
559
+ controlType,
560
+ seed,
561
+ hostBridge: {
562
+ applyPatch: (patch) => this.handlePatch(instance, fieldName, patch),
563
+ },
564
+ },
565
+ },
566
+ });
567
+ }
568
+ catch (err) {
569
+ if (isDevMode()) {
570
+ try {
571
+ console.error('[ManualFieldMetadataBridge] failed to open settings panel', err);
572
+ }
573
+ catch { }
574
+ }
575
+ return;
576
+ }
577
+ ref.applied$.subscribe((patch) => this.handlePatch(instance, fieldName, patch));
578
+ ref.saved$.subscribe((patch) => this.handlePatch(instance, fieldName, patch));
579
+ }
580
+ handlePatch(instance, fieldName, patch) {
581
+ if (!patch || typeof patch !== 'object') {
582
+ return;
583
+ }
584
+ if (!this.shouldApplyPatch(instance, fieldName, patch)) {
585
+ return;
586
+ }
587
+ this.applyPatch(instance, fieldName, patch);
588
+ try {
589
+ instance.saveDraft();
590
+ }
591
+ catch (err) {
592
+ if (isDevMode()) {
593
+ try {
594
+ console.error('[ManualFieldMetadataBridge] failed to persist draft after patch', err);
595
+ }
596
+ catch { }
597
+ }
598
+ }
599
+ }
600
+ shouldApplyPatch(instance, fieldName, patch) {
601
+ let fieldsMap = this.patchSignatures.get(instance);
602
+ if (!fieldsMap) {
603
+ fieldsMap = new Map();
604
+ this.patchSignatures.set(instance, fieldsMap);
605
+ }
606
+ const now = Date.now();
607
+ let signature = '';
608
+ try {
609
+ signature = JSON.stringify(patch);
610
+ }
611
+ catch {
612
+ signature = `${now}`;
613
+ }
614
+ const existing = fieldsMap.get(fieldName);
615
+ if (existing && existing.sig === signature && now - existing.at < 1200) {
616
+ if (isDevMode()) {
617
+ try {
618
+ console.debug('[ManualFieldMetadataBridge] duplicate patch ignored', { fieldName });
619
+ }
620
+ catch { }
621
+ }
622
+ return false;
623
+ }
624
+ fieldsMap.set(fieldName, { sig: signature, at: now });
625
+ return true;
626
+ }
627
+ buildPanelTitle(fieldName, seed) {
628
+ const label = seed?.label || seed?.placeholder || fieldName;
629
+ const type = this.formatControlType(seed?.controlType);
630
+ return type ? `${type} — ${label}` : label;
631
+ }
632
+ buildPanelId(instance, fieldName) {
633
+ const formId = this.extractFormId(instance);
634
+ return formId ? `manual-field.${formId}.${fieldName}` : `manual-field.${fieldName}`;
635
+ }
636
+ extractFormId(instance) {
637
+ try {
638
+ return instance?.formId || instance?.seed?.formId;
639
+ }
640
+ catch {
641
+ return undefined;
642
+ }
643
+ }
644
+ formatControlType(controlType) {
645
+ if (!controlType) {
646
+ return '';
647
+ }
648
+ return String(controlType)
649
+ .split(/[-_]/)
650
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase())
651
+ .join(' ');
652
+ }
653
+ resolveIcon(controlType) {
654
+ if (!controlType) {
655
+ return 'tune';
656
+ }
657
+ const map = {
658
+ INPUT: 'text_fields',
659
+ TEXTAREA: 'notes',
660
+ NUMERIC_TEXT_BOX: 'pin',
661
+ CURRENCY_INPUT: 'attach_money',
662
+ EMAIL_INPUT: 'mail',
663
+ PASSWORD: 'vpn_key',
664
+ SELECT: 'list',
665
+ MULTI_SELECT: 'checklist',
666
+ AUTO_COMPLETE: 'search',
667
+ RADIO: 'radio_button_checked',
668
+ CHECKBOX: 'check_box',
669
+ TOGGLE: 'toggle_on',
670
+ SLIDER: 'tune',
671
+ DATE_INPUT: 'event',
672
+ DATE_RANGE: 'event_repeat',
673
+ TIME_INPUT: 'schedule',
674
+ TIME_PICKER: 'schedule',
675
+ RATING: 'grade',
676
+ TREE_SELECT: 'account_tree',
677
+ FILE_UPLOAD: 'upload_file',
678
+ TRANSFER_LIST: 'compare_arrows',
679
+ CHIP_INPUT: 'sell',
680
+ CPF_CNPJ_INPUT: 'fingerprint',
681
+ };
682
+ const key = String(controlType).toUpperCase();
683
+ return map[key] || 'tune';
684
+ }
685
+ bindComponent(instance, fieldName, componentInstance) {
686
+ instance.bindComponent(fieldName, componentInstance);
687
+ }
688
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFieldMetadataBridgeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
689
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFieldMetadataBridgeService, providedIn: 'root' });
690
+ }
691
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFieldMetadataBridgeService, decorators: [{
692
+ type: Injectable,
693
+ args: [{ providedIn: 'root' }]
694
+ }] });
695
+
696
+ class ManualFormComponent {
697
+ instanceFactory = inject(ManualFormInstanceFactory);
698
+ cdr = inject(ChangeDetectorRef);
699
+ destroyRef = inject(DestroyRef);
700
+ metadataBridge = inject(ManualFieldMetadataBridgeService);
701
+ settingsPanel = inject(SettingsPanelService);
702
+ selectorToControlType = inject(MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE, { optional: true }) ?? DEFAULT_SELECTOR_TO_CONTROL_TYPE;
703
+ constructorToControlType = inject(MANUAL_FORM_CONSTRUCTOR_TO_CONTROL_TYPE, { optional: true }) ?? DEFAULT_CONSTRUCTOR_TO_CONTROL_TYPE;
704
+ formId = input.required(...(ngDevMode ? [{ debugName: "formId" }] : []));
705
+ formTitle = input(...(ngDevMode ? [undefined, { debugName: "formTitle" }] : []));
706
+ formDescription = input(...(ngDevMode ? [undefined, { debugName: "formDescription" }] : []));
707
+ actions = input(...(ngDevMode ? [undefined, { debugName: "actions" }] : []));
708
+ showHeader = input(true, ...(ngDevMode ? [{ debugName: "showHeader" }] : []));
709
+ showActions = input(true, ...(ngDevMode ? [{ debugName: "showActions" }] : []));
710
+ enableAutoSave = input(true, ...(ngDevMode ? [{ debugName: "enableAutoSave" }] : []));
711
+ // Allows editor affordances (e.g., opening metadata editor on interactions)
712
+ editModeEnabled = input(false, ...(ngDevMode ? [{ debugName: "editModeEnabled" }] : []));
713
+ persistenceOptions = input(...(ngDevMode ? [undefined, { debugName: "persistenceOptions" }] : []));
714
+ // When true, uses FormControlName.path (joined by '.') as FieldMetadata.name.
715
+ // Requires nested path support (already present in DynamicFormService).
716
+ usePathNames = input(false, ...(ngDevMode ? [{ debugName: "usePathNames" }] : []));
717
+ // Debounce interval for autosave (ms). Only used when enableAutoSave is true.
718
+ autoSaveDebounceMs = input(800, ...(ngDevMode ? [{ debugName: "autoSaveDebounceMs" }] : []));
719
+ submitted = output();
720
+ saved = output();
721
+ resetEvent = output({ alias: 'reset' });
722
+ metadataChange = output();
723
+ formControls;
724
+ // FormGroupDirective is applied on the component host via hostDirectives
725
+ hostFormGroupDirective = inject(FormGroupDirective, { self: true, optional: true });
726
+ instance;
727
+ resolvedActions;
728
+ formGroup = new FormGroup({});
729
+ registeredDirectives = [];
730
+ constructor() {
731
+ // Host-typed mode: if a FormGroupDirective is present, adopt its FormGroup without monkey patches.
732
+ if (this.hostFormGroupDirective) {
733
+ if (isDevMode())
734
+ console.debug('[ManualForm] Host FormGroupDirective attached early');
735
+ if (this.hostFormGroupDirective.form) {
736
+ this.formGroup = this.hostFormGroupDirective.form;
737
+ if (isDevMode())
738
+ console.debug('[ManualForm] Using host-provided FormGroup instance');
739
+ }
740
+ else {
741
+ // Keep placeholder until Angular binds [formGroup]; our addControl creates missing controls if needed.
742
+ if (isDevMode())
743
+ console.debug('[ManualForm] No host FormGroup yet; awaiting binding without interception');
744
+ }
745
+ }
746
+ else {
747
+ if (isDevMode())
748
+ console.debug('[ManualForm] No host FormGroupDirective detected at construction (dynamic mode)');
749
+ }
750
+ }
751
+ ngAfterContentInit() {
752
+ if (this.formControls) {
753
+ this.formControls.changes
754
+ .pipe(takeUntilDestroyed(this.destroyRef))
755
+ .subscribe(() => this.initialize(true));
756
+ }
757
+ queueMicrotask(() => this.initialize(false));
758
+ }
759
+ ngOnDestroy() {
760
+ // Output signals do not need manual completion
761
+ }
762
+ // =============================
763
+ // ControlContainer interface
764
+ // =============================
765
+ // Make this component act as the ControlContainer for projected children.
766
+ // FormControlName will call `parent.formDirective.addControl(this)` on us.
767
+ get formDirective() {
768
+ return this;
769
+ }
770
+ get control() {
771
+ return this.formGroup;
772
+ }
773
+ get path() {
774
+ return [];
775
+ }
776
+ addControl(dir) {
777
+ const name = typeof dir.name === 'string' ? dir.name : String(dir.name);
778
+ const pathSegs = Array.isArray(dir.path) && dir.path.length
779
+ ? dir.path
780
+ : (name ? [name] : []);
781
+ const pathStr = this.usePathNames() ? pathSegs.join('.') : name;
782
+ // Ensure control exists locally when host is not ready
783
+ if (pathStr && !this.getControlByPath(pathSegs)) {
784
+ this.ensureControlPath(pathSegs);
785
+ if (isDevMode())
786
+ console.debug('[ManualForm] addControl created missing control', pathStr);
787
+ }
788
+ // Delegate wiring to host FormGroupDirective when available (ensures CVA pipelines are set)
789
+ if (this.hostFormGroupDirective && this.hostFormGroupDirective.form) {
790
+ if (isDevMode())
791
+ console.debug('[ManualForm] Delegating addControl to host FormGroupDirective for', pathStr);
792
+ const ctrl = this.hostFormGroupDirective.addControl(dir);
793
+ this.registeredDirectives.push(dir);
794
+ return ctrl;
795
+ }
796
+ // Fallback minimal wiring if no host directive
797
+ const ctrl = this.formGroup.get(dir.path);
798
+ try {
799
+ const va = dir.valueAccessor;
800
+ va?.writeValue?.(ctrl?.value);
801
+ va?.setDisabledState?.(ctrl?.disabled ?? false);
802
+ va?.registerOnChange?.((value) => {
803
+ ctrl?.setValue(value);
804
+ });
805
+ va?.registerOnTouched?.(() => {
806
+ ctrl?.markAsTouched();
807
+ });
808
+ }
809
+ catch { }
810
+ dir.control = ctrl;
811
+ return ctrl;
812
+ }
813
+ removeControl(dir) {
814
+ this.hostFormGroupDirective?.removeControl(dir);
815
+ const idx = this.registeredDirectives.indexOf(dir);
816
+ if (idx >= 0)
817
+ this.registeredDirectives.splice(idx, 1);
818
+ }
819
+ getControl(dir) {
820
+ return this.formGroup.get(dir.path);
821
+ }
822
+ updateModel(dir, value) {
823
+ if (this.hostFormGroupDirective) {
824
+ this.hostFormGroupDirective.updateModel(dir, value);
825
+ }
826
+ else {
827
+ const ctrl = this.formGroup.get(dir.path);
828
+ ctrl?.setValue(value);
829
+ }
830
+ }
831
+ handleAction(actionId) {
832
+ if (actionId === 'submit') {
833
+ this.handleSubmit();
834
+ }
835
+ else if (actionId === 'save') {
836
+ this.handleSave();
837
+ }
838
+ else if (actionId === 'reset') {
839
+ this.handleReset();
840
+ }
841
+ else if (this.instance) {
842
+ this.metadataChange.emit(this.instance.currentConfig);
843
+ }
844
+ }
845
+ handleSubmit() {
846
+ if (!this.instance) {
847
+ return;
848
+ }
849
+ const value = this.formGroup.getRawValue();
850
+ if (this.enableAutoSave()) {
851
+ this.instance.saveDraft(value);
852
+ }
853
+ this.submitted.emit({ value, instance: this.instance });
854
+ }
855
+ handleSave() {
856
+ if (!this.instance) {
857
+ return;
858
+ }
859
+ if (this.enableAutoSave()) {
860
+ this.instance.saveDraft();
861
+ }
862
+ this.saved.emit(this.instance);
863
+ this.metadataChange.emit(this.instance.currentConfig);
864
+ }
865
+ handleReset() {
866
+ if (!this.instance) {
867
+ return;
868
+ }
869
+ this.instance.resetToSeed();
870
+ this.hostFormGroupDirective?.resetForm(this.instance.form.getRawValue());
871
+ this.resetEvent.emit(this.instance);
872
+ }
873
+ /**
874
+ * Attempts to open the field metadata editor respecting the component's
875
+ * editModeEnabled input. No-ops when disabled or without an instance.
876
+ */
877
+ tryOpenFieldEditor(fieldName) {
878
+ if (!this.editModeEnabled()) {
879
+ return;
880
+ }
881
+ const instance = this.instance;
882
+ if (!instance || !fieldName) {
883
+ return;
884
+ }
885
+ try {
886
+ void this.metadataBridge.openEditor(instance, fieldName);
887
+ }
888
+ catch { }
889
+ }
890
+ /** Opens a simple form-level editor listing the fields and their visibility. */
891
+ async openFormEditor() {
892
+ if (!this.editModeEnabled() || !this.instance)
893
+ return;
894
+ let ManualFormConfigEditorComponent;
895
+ try {
896
+ const module = await Promise.resolve().then(function () { return manualFormConfigEditor_component; });
897
+ ManualFormConfigEditorComponent = module?.ManualFormConfigEditorComponent;
898
+ }
899
+ catch {
900
+ return;
901
+ }
902
+ const formId = this.formId();
903
+ this.settingsPanel.open({
904
+ id: formId ? `manual-form-editor.${formId}` : 'manual-form-editor',
905
+ title: this.formTitle() || 'Editor do Formulário',
906
+ titleIcon: 'tune',
907
+ content: {
908
+ component: ManualFormConfigEditorComponent,
909
+ inputs: { instance: this.instance },
910
+ },
911
+ });
912
+ }
913
+ initialize(fromChanges) {
914
+ if (!this.formId()) {
915
+ return;
916
+ }
917
+ const fields = this.collectFields();
918
+ if (isDevMode())
919
+ console.debug('[ManualForm] initialize: detected fields', fields.map(f => f.name));
920
+ if (fields.length === 0) {
921
+ return;
922
+ }
923
+ const seed = this.createSeedFromFields(fields);
924
+ // Create runtime instance and let it populate our existing FormGroup
925
+ this.instance = this.instanceFactory.create(seed, this.persistenceOptions() ?? {}, this.formGroup);
926
+ if (isDevMode())
927
+ console.debug('[ManualForm] instance created with seed; current controls', Object.keys(this.formGroup.controls || {}));
928
+ this.applyInstanceToTemplate(fields);
929
+ // Auto-save value changes with debounce
930
+ if (this.enableAutoSave() && this.instance) {
931
+ this.instance.form.valueChanges
932
+ .pipe(debounceTime(this.autoSaveDebounceMs()), takeUntilDestroyed(this.destroyRef))
933
+ .subscribe(() => {
934
+ try {
935
+ this.instance?.saveDraft();
936
+ }
937
+ catch { }
938
+ });
939
+ }
940
+ this.cdr.markForCheck();
941
+ }
942
+ collectFields() {
943
+ if (!this.formControls) {
944
+ return [];
945
+ }
946
+ const result = [];
947
+ for (const dir of this.formControls) {
948
+ const name = this.usePathNames()
949
+ ? (Array.isArray(dir.path) && dir.path.length ? dir.path.join('.') : (typeof dir.name === 'string' ? dir.name : null))
950
+ : (typeof dir.name === 'string' ? dir.name : null);
951
+ if (!name) {
952
+ continue;
953
+ }
954
+ const component = this.resolveValueAccessor(dir);
955
+ if (!component) {
956
+ continue;
957
+ }
958
+ result.push({
959
+ name,
960
+ control: dir.control,
961
+ component,
962
+ selector: this.resolveSelector(component, dir),
963
+ });
964
+ }
965
+ return result;
966
+ }
967
+ resolveValueAccessor(dir) {
968
+ const accessor = dir.valueAccessor;
969
+ if (!accessor) {
970
+ return undefined;
971
+ }
972
+ if (Array.isArray(accessor)) {
973
+ return accessor[0];
974
+ }
975
+ return accessor;
976
+ }
977
+ resolveSelector(component, dir) {
978
+ const cmpDef = component?.constructor?.ɵcmp;
979
+ if (cmpDef?.selectors?.length) {
980
+ const first = cmpDef.selectors[0];
981
+ if (Array.isArray(first) && first.length && typeof first[0] === 'string') {
982
+ return first[0];
983
+ }
984
+ }
985
+ const element = component?.elementRef?.nativeElement
986
+ ?? dir?._elementRef?.nativeElement;
987
+ return element?.tagName?.toLowerCase();
988
+ }
989
+ createSeedFromFields(fields) {
990
+ const metadataList = fields.map((field) => this.buildMetadataForField(field));
991
+ const actions = this.normalizeActions(this.actions() ?? null);
992
+ const currentMeta = this.instance?.currentConfig.metadata
993
+ ? deepClone(this.instance.currentConfig.metadata)
994
+ : undefined;
995
+ const config = {
996
+ sections: [
997
+ {
998
+ id: 'auto-section',
999
+ title: this.formTitle(),
1000
+ description: this.formDescription(),
1001
+ rows: [
1002
+ {
1003
+ id: 'auto-row',
1004
+ columns: [
1005
+ {
1006
+ id: 'auto-column',
1007
+ fields: metadataList.map((meta) => meta.name),
1008
+ },
1009
+ ],
1010
+ },
1011
+ ],
1012
+ },
1013
+ ],
1014
+ fieldMetadata: metadataList,
1015
+ metadata: currentMeta,
1016
+ actions,
1017
+ };
1018
+ this.resolvedActions = actions;
1019
+ return createManualFormSeed(this.formId(), config, {
1020
+ source: 'generated',
1021
+ version: '1.0.0',
1022
+ });
1023
+ }
1024
+ applyInstanceToTemplate(fields) {
1025
+ if (!this.instance) {
1026
+ return;
1027
+ }
1028
+ for (const field of fields) {
1029
+ this.instance.bindComponent(field.name, field.component);
1030
+ // Only apply metadata manually if the component doesn't expose a standard API
1031
+ const hasStdApi = typeof field.component?.setInputMetadata === 'function' ||
1032
+ !!(field.component?.metadata && typeof field.component.metadata.set === 'function');
1033
+ if (!hasStdApi) {
1034
+ const metadata = this.instance.getFieldMetadata(field.name);
1035
+ if (metadata) {
1036
+ this.applyMetadataToComponent(field.component, metadata);
1037
+ }
1038
+ }
1039
+ }
1040
+ }
1041
+ buildMetadataForField(field) {
1042
+ const existing = this.extractExistingMetadata(field.component);
1043
+ const label = existing?.label ?? this.inferLabel(field, existing);
1044
+ const controlType = existing?.controlType ?? this.inferControlType(field);
1045
+ const validators = this.inferValidators(field.control, existing);
1046
+ return {
1047
+ ...(existing ?? {}),
1048
+ name: field.name,
1049
+ label,
1050
+ controlType,
1051
+ validators: { ...existing?.validators, ...validators },
1052
+ };
1053
+ }
1054
+ getControlByPath(path) {
1055
+ if (!Array.isArray(path) || path.length === 0)
1056
+ return null;
1057
+ return this.formGroup.get(path);
1058
+ }
1059
+ ensureControlPath(path) {
1060
+ if (!Array.isArray(path) || path.length === 0)
1061
+ return;
1062
+ const leaf = path[path.length - 1];
1063
+ let group = this.formGroup;
1064
+ for (let i = 0; i < path.length - 1; i++) {
1065
+ const seg = path[i];
1066
+ const existing = group.get(seg);
1067
+ if (existing instanceof FormGroup) {
1068
+ group = existing;
1069
+ }
1070
+ else {
1071
+ const next = new FormGroup({});
1072
+ group.addControl(seg, next);
1073
+ group = next;
1074
+ }
1075
+ }
1076
+ if (!group.get(leaf)) {
1077
+ group.addControl(leaf, new FormControl());
1078
+ }
1079
+ }
1080
+ extractExistingMetadata(component) {
1081
+ try {
1082
+ const metaSignal = component?.metadata;
1083
+ if (typeof metaSignal === 'function') {
1084
+ const value = metaSignal();
1085
+ if (value) {
1086
+ return deepClone(value);
1087
+ }
1088
+ }
1089
+ if (typeof component?.getMetadata === 'function') {
1090
+ const value = component.getMetadata();
1091
+ if (value) {
1092
+ return deepClone(value);
1093
+ }
1094
+ }
1095
+ }
1096
+ catch { }
1097
+ return undefined;
1098
+ }
1099
+ inferLabel(field, existing) {
1100
+ if (existing?.label) {
1101
+ return existing.label;
1102
+ }
1103
+ const componentLabel = field.component?.label;
1104
+ if (typeof componentLabel === 'string' && componentLabel.trim().length > 0) {
1105
+ return componentLabel;
1106
+ }
1107
+ return field.name
1108
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
1109
+ .replace(/[-_]/g, ' ')
1110
+ .replace(/\s+/g, ' ')
1111
+ .replace(/^\w/, (c) => c.toUpperCase());
1112
+ }
1113
+ inferControlType(field) {
1114
+ const selector = field.selector?.toLowerCase();
1115
+ if (selector && this.selectorToControlType[selector]) {
1116
+ return this.selectorToControlType[selector];
1117
+ }
1118
+ const ctorName = field.component?.constructor?.name ?? '';
1119
+ for (const [key, value] of Object.entries(this.constructorToControlType)) {
1120
+ if (ctorName.includes(key)) {
1121
+ return value;
1122
+ }
1123
+ }
1124
+ return FieldControlType.INPUT;
1125
+ }
1126
+ inferValidators(control, existing) {
1127
+ const validators = existing?.validators ? { ...existing.validators } : {};
1128
+ if (!control) {
1129
+ return validators;
1130
+ }
1131
+ const hasValidator = (fn) => {
1132
+ const anyControl = control;
1133
+ if (typeof anyControl.hasValidator === 'function') {
1134
+ return anyControl.hasValidator(fn);
1135
+ }
1136
+ try {
1137
+ return !!control.validator && !!control.validator({});
1138
+ }
1139
+ catch {
1140
+ return false;
1141
+ }
1142
+ };
1143
+ if (hasValidator(Validators.required)) {
1144
+ validators.required = true;
1145
+ }
1146
+ return validators;
1147
+ }
1148
+ applyMetadataToComponent(component, metadata) {
1149
+ try {
1150
+ if (typeof component?.setInputMetadata === 'function') {
1151
+ component.setInputMetadata(metadata);
1152
+ }
1153
+ const metaSignal = component?.metadata;
1154
+ if (metaSignal && typeof metaSignal.set === 'function') {
1155
+ metaSignal.set(metadata);
1156
+ }
1157
+ if ('label' in component && !component.label) {
1158
+ component.label = metadata.label;
1159
+ }
1160
+ // Visibilidade: esconder/mostrar com base em hidden/formHidden, mesmo em templates manuais
1161
+ const isHidden = !!metadata?.hidden || !!metadata?.formHidden;
1162
+ if ('visible' in component) {
1163
+ try {
1164
+ component.visible = !isHidden;
1165
+ }
1166
+ catch { }
1167
+ }
1168
+ const hostEl = component?.elementRef?.nativeElement;
1169
+ if (hostEl) {
1170
+ hostEl.style.display = isHidden ? 'none' : '';
1171
+ hostEl.setAttribute('aria-hidden', String(isHidden));
1172
+ }
1173
+ }
1174
+ catch { }
1175
+ }
1176
+ buildCurrentConfigSnapshot() {
1177
+ const metadata = this.instance?.currentConfig.fieldMetadata ?? [];
1178
+ return {
1179
+ fieldMetadata: deepClone(metadata),
1180
+ sections: this.instance?.currentConfig.sections
1181
+ ? deepClone(this.instance.currentConfig.sections)
1182
+ : [],
1183
+ };
1184
+ }
1185
+ normalizeActions(source) {
1186
+ const base = deepClone(DEFAULT_ACTIONS);
1187
+ if (!source) {
1188
+ return base;
1189
+ }
1190
+ return {
1191
+ submit: mergeAction(base.submit, source.submit),
1192
+ cancel: mergeAction(base.cancel, source.cancel),
1193
+ reset: mergeAction(base.reset, source.reset),
1194
+ custom: source.custom ? source.custom.map(makeAction) : base.custom,
1195
+ position: source.position ?? base.position,
1196
+ orientation: source.orientation ?? base.orientation,
1197
+ spacing: source.spacing ?? base.spacing,
1198
+ sticky: source.sticky ?? base.sticky,
1199
+ placement: source.placement ?? base.placement,
1200
+ mobile: source.mobile ?? base.mobile,
1201
+ containerClassName: source.containerClassName ?? base.containerClassName,
1202
+ containerStyles: source.containerStyles ?? base.containerStyles,
1203
+ showSaveButton: source.showSaveButton ?? base.showSaveButton,
1204
+ submitButtonLabel: source.submitButtonLabel ?? base.submitButtonLabel,
1205
+ showCancelButton: source.showCancelButton ?? base.showCancelButton,
1206
+ cancelButtonLabel: source.cancelButtonLabel ?? base.cancelButtonLabel,
1207
+ showResetButton: source.showResetButton ?? base.showResetButton,
1208
+ resetButtonLabel: source.resetButtonLabel ?? base.resetButtonLabel,
1209
+ };
1210
+ }
1211
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1212
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: ManualFormComponent, isStandalone: true, selector: "praxis-manual-form", inputs: { formId: { classPropertyName: "formId", publicName: "formId", isSignal: true, isRequired: true, transformFunction: null }, formTitle: { classPropertyName: "formTitle", publicName: "formTitle", isSignal: true, isRequired: false, transformFunction: null }, formDescription: { classPropertyName: "formDescription", publicName: "formDescription", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, showActions: { classPropertyName: "showActions", publicName: "showActions", isSignal: true, isRequired: false, transformFunction: null }, enableAutoSave: { classPropertyName: "enableAutoSave", publicName: "enableAutoSave", isSignal: true, isRequired: false, transformFunction: null }, editModeEnabled: { classPropertyName: "editModeEnabled", publicName: "editModeEnabled", isSignal: true, isRequired: false, transformFunction: null }, persistenceOptions: { classPropertyName: "persistenceOptions", publicName: "persistenceOptions", isSignal: true, isRequired: false, transformFunction: null }, usePathNames: { classPropertyName: "usePathNames", publicName: "usePathNames", isSignal: true, isRequired: false, transformFunction: null }, autoSaveDebounceMs: { classPropertyName: "autoSaveDebounceMs", publicName: "autoSaveDebounceMs", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { submitted: "submitted", saved: "saved", resetEvent: "reset", metadataChange: "metadataChange" }, providers: [
1213
+ // Provide a ControlContainer at the host boundary so projected formControlName can resolve it
1214
+ { provide: ControlContainer, useExisting: ManualFormComponent },
1215
+ ], queries: [{ propertyName: "formControls", predicate: FormControlName, descendants: true }], ngImport: i0, template: "<div class=\"pdx-manual-form\">\n @if (showHeader()) {\n <praxis-manual-form-header\n [instance]=\"instance\"\n [title]=\"formTitle()\"\n [description]=\"formDescription()\"\n [editModeEnabled]=\"editModeEnabled()\"\n (editForm)=\"openFormEditor()\"\n (save)=\"handleSave()\"\n (reset)=\"handleReset()\"\n ></praxis-manual-form-header>\n }\n\n <form class=\"pdx-manual-form__form\" (submit)=\"handleSubmit(); $event.preventDefault()\">\n <ng-content></ng-content>\n </form>\n\n @if (showActions() && resolvedActions) {\n <praxis-manual-form-actions\n [actions]=\"resolvedActions\"\n (actionClick)=\"handleAction($event)\"\n ></praxis-manual-form-actions>\n }\n</div>\n", styles: [".pdx-manual-form{display:flex;flex-direction:column;gap:1.5rem}.pdx-manual-form__form{display:grid;gap:1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "component", type: ManualFormHeaderComponent, selector: "praxis-manual-form-header", inputs: ["instance", "title", "description", "saveLabel", "resetLabel", "editModeEnabled", "editFormLabel"], outputs: ["save", "reset", "editForm"] }, { kind: "component", type: ManualFormActionsComponent, selector: "praxis-manual-form-actions", inputs: ["actions", "trackByFn"], outputs: ["actionClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1216
+ }
1217
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormComponent, decorators: [{
1218
+ type: Component,
1219
+ args: [{ selector: 'praxis-manual-form', standalone: true, imports: [
1220
+ CommonModule,
1221
+ ReactiveFormsModule,
1222
+ ManualFormHeaderComponent,
1223
+ ManualFormActionsComponent,
1224
+ ], providers: [
1225
+ // Provide a ControlContainer at the host boundary so projected formControlName can resolve it
1226
+ { provide: ControlContainer, useExisting: ManualFormComponent },
1227
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"pdx-manual-form\">\n @if (showHeader()) {\n <praxis-manual-form-header\n [instance]=\"instance\"\n [title]=\"formTitle()\"\n [description]=\"formDescription()\"\n [editModeEnabled]=\"editModeEnabled()\"\n (editForm)=\"openFormEditor()\"\n (save)=\"handleSave()\"\n (reset)=\"handleReset()\"\n ></praxis-manual-form-header>\n }\n\n <form class=\"pdx-manual-form__form\" (submit)=\"handleSubmit(); $event.preventDefault()\">\n <ng-content></ng-content>\n </form>\n\n @if (showActions() && resolvedActions) {\n <praxis-manual-form-actions\n [actions]=\"resolvedActions\"\n (actionClick)=\"handleAction($event)\"\n ></praxis-manual-form-actions>\n }\n</div>\n", styles: [".pdx-manual-form{display:flex;flex-direction:column;gap:1.5rem}.pdx-manual-form__form{display:grid;gap:1rem}\n"] }]
1228
+ }], ctorParameters: () => [], propDecorators: { formControls: [{
1229
+ type: ContentChildren,
1230
+ args: [FormControlName, { descendants: true }]
1231
+ }] } });
1232
+ const DEFAULT_ACTIONS = {
1233
+ submit: makeAction({ id: 'submit', label: 'Salvar', visible: true, type: 'submit', color: 'primary' }),
1234
+ cancel: makeAction({ id: 'cancel', label: 'Cancelar', visible: false, type: 'button' }),
1235
+ reset: makeAction({ id: 'reset', label: 'Restaurar', visible: true, type: 'reset' }),
1236
+ custom: [],
1237
+ position: 'right',
1238
+ orientation: 'horizontal',
1239
+ spacing: 'normal',
1240
+ };
1241
+ function makeAction(action) {
1242
+ return {
1243
+ id: action.id ?? 'action',
1244
+ label: action.label ?? 'Ação',
1245
+ visible: action.visible !== false,
1246
+ type: action.type ?? 'button',
1247
+ color: action.color,
1248
+ disabled: action.disabled,
1249
+ icon: action.icon,
1250
+ action: action.action,
1251
+ tooltip: action.tooltip,
1252
+ variant: action.variant,
1253
+ size: action.size,
1254
+ loading: action.loading,
1255
+ shortcut: action.shortcut,
1256
+ };
1257
+ }
1258
+ function mergeAction(base, override) {
1259
+ if (!override) {
1260
+ return base;
1261
+ }
1262
+ return {
1263
+ ...base,
1264
+ ...override,
1265
+ visible: override.visible !== undefined ? override.visible : base.visible,
1266
+ };
1267
+ }
1268
+ const DEFAULT_SELECTOR_TO_CONTROL_TYPE = {
1269
+ 'pdx-text-input': FieldControlType.INPUT,
1270
+ 'pdx-material-textarea': FieldControlType.TEXTAREA,
1271
+ 'pdx-number-input': FieldControlType.NUMERIC_TEXT_BOX,
1272
+ 'pdx-material-currency': FieldControlType.CURRENCY_INPUT,
1273
+ 'pdx-material-datepicker': FieldControlType.DATE_PICKER,
1274
+ 'pdx-material-date-range': FieldControlType.DATE_RANGE,
1275
+ 'pdx-material-timepicker': FieldControlType.TIME_PICKER,
1276
+ 'pdx-material-colorpicker': FieldControlType.COLOR_PICKER,
1277
+ 'pdx-material-select': FieldControlType.SELECT,
1278
+ 'pdx-material-autocomplete': FieldControlType.AUTO_COMPLETE,
1279
+ 'pdx-material-checkbox-group': FieldControlType.CHECKBOX,
1280
+ 'pdx-material-radio-group': FieldControlType.RADIO,
1281
+ 'pdx-material-slide-toggle': FieldControlType.TOGGLE,
1282
+ 'pdx-material-slider': FieldControlType.SLIDER,
1283
+ 'pdx-material-range-slider': FieldControlType.RANGE_SLIDER,
1284
+ 'pdx-material-file-upload': FieldControlType.FILE_UPLOAD,
1285
+ };
1286
+ const DEFAULT_CONSTRUCTOR_TO_CONTROL_TYPE = {
1287
+ TextInput: FieldControlType.INPUT,
1288
+ Textarea: FieldControlType.TEXTAREA,
1289
+ NumberInput: FieldControlType.NUMERIC_TEXT_BOX,
1290
+ Currency: FieldControlType.CURRENCY_INPUT,
1291
+ DatePicker: FieldControlType.DATE_PICKER,
1292
+ DateRange: FieldControlType.DATE_RANGE,
1293
+ TimePicker: FieldControlType.TIME_PICKER,
1294
+ ColorPicker: FieldControlType.COLOR_PICKER,
1295
+ SelectComponent: FieldControlType.SELECT,
1296
+ Autocomplete: FieldControlType.AUTO_COMPLETE,
1297
+ CheckboxGroup: FieldControlType.CHECKBOX,
1298
+ RadioGroup: FieldControlType.RADIO,
1299
+ SlideToggle: FieldControlType.TOGGLE,
1300
+ SliderComponent: FieldControlType.SLIDER,
1301
+ RangeSlider: FieldControlType.RANGE_SLIDER,
1302
+ FileUpload: FieldControlType.FILE_UPLOAD,
1303
+ };
1304
+
1305
+ class ManualFormConfigEditorComponent {
1306
+ ref;
1307
+ instance;
1308
+ all = [];
1309
+ filtered = [];
1310
+ onlyHidden = false;
1311
+ // SettingsValueProvider observables
1312
+ isDirty$ = new BehaviorSubject(false);
1313
+ isValid$ = new BehaviorSubject(true);
1314
+ isBusy$ = new BehaviorSubject(false);
1315
+ constructor(inputs, ref) {
1316
+ this.ref = ref;
1317
+ this.instance = inputs.instance;
1318
+ }
1319
+ ngOnInit() {
1320
+ this.refresh();
1321
+ }
1322
+ close() { this.ref.close('cancel'); }
1323
+ applyFilter() {
1324
+ this.filtered = this.onlyHidden ? this.all.filter(f => !!f.hidden) : [...this.all];
1325
+ }
1326
+ toggleVisibility(field, checked) {
1327
+ const visible = !!checked; // checked === visible
1328
+ const toPatch = { hidden: !visible };
1329
+ try {
1330
+ this.instance.patchFieldMetadata(field.name, toPatch);
1331
+ this.instance.saveDraft();
1332
+ this.refresh();
1333
+ this.isDirty$.next(true);
1334
+ }
1335
+ catch { }
1336
+ }
1337
+ toggleRequired(field, checked) {
1338
+ const toPatch = { required: !!checked };
1339
+ try {
1340
+ this.instance.patchFieldMetadata(field.name, toPatch);
1341
+ this.instance.saveDraft();
1342
+ this.refresh();
1343
+ this.isDirty$.next(true);
1344
+ }
1345
+ catch { }
1346
+ }
1347
+ toggleReadOnly(field, checked) {
1348
+ const toPatch = { readOnly: !!checked };
1349
+ try {
1350
+ this.instance.patchFieldMetadata(field.name, toPatch);
1351
+ this.instance.saveDraft();
1352
+ this.refresh();
1353
+ this.isDirty$.next(true);
1354
+ }
1355
+ catch { }
1356
+ }
1357
+ toggleDisabled(field, checked) {
1358
+ const toPatch = { disabled: !!checked };
1359
+ try {
1360
+ this.instance.patchFieldMetadata(field.name, toPatch);
1361
+ this.instance.saveDraft();
1362
+ this.refresh();
1363
+ this.isDirty$.next(true);
1364
+ }
1365
+ catch { }
1366
+ }
1367
+ getSettingsValue() {
1368
+ return {
1369
+ fieldStates: (this.instance.currentConfig.fieldMetadata || []).map((f) => ({
1370
+ name: f.name,
1371
+ hidden: !!f.hidden,
1372
+ required: !!f.required,
1373
+ readOnly: !!f.readOnly,
1374
+ disabled: !!f.disabled,
1375
+ })),
1376
+ };
1377
+ }
1378
+ onSave() {
1379
+ const value = this.getSettingsValue();
1380
+ // Consider changes persisted via saveDraft; mark clean so buttons desativem se reabrir
1381
+ this.isDirty$.next(false);
1382
+ return value;
1383
+ }
1384
+ refresh() {
1385
+ this.all = (this.instance.currentConfig.fieldMetadata || []).map(f => ({ ...f }));
1386
+ this.applyFilter();
1387
+ }
1388
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormConfigEditorComponent, deps: [{ token: SETTINGS_PANEL_DATA }, { token: SETTINGS_PANEL_REF }], target: i0.ɵɵFactoryTarget.Component });
1389
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: ManualFormConfigEditorComponent, isStandalone: true, selector: "praxis-manual-form-config-editor", ngImport: i0, template: `
1390
+ <div class="mf-editor">
1391
+ <div class="mf-editor__toolbar">
1392
+ <label><input type="checkbox" [(ngModel)]="onlyHidden" (change)="applyFilter()" /> Mostrar apenas ocultos</label>
1393
+ <div class="spacer"></div>
1394
+ <button type="button" (click)="close()">Fechar</button>
1395
+ </div>
1396
+
1397
+ <div class="mf-editor__list">
1398
+ <div class="mf-editor__row mf-editor__row--head">
1399
+ <div class="col col--name">Campo</div>
1400
+ <div class="col col--label">Rótulo</div>
1401
+ <div class="col col--type">Tipo</div>
1402
+ <div class="col col--vis">Visível</div>
1403
+ <div class="col col--req">Obrigatório</div>
1404
+ <div class="col col--ro">Som. leitura</div>
1405
+ <div class="col col--dis">Desabilitado</div>
1406
+ </div>
1407
+
1408
+ <div class="mf-editor__row" *ngFor="let f of filtered">
1409
+ <div class="col col--name"><code>{{ f.name }}</code></div>
1410
+ <div class="col col--label">{{ f.label || f.name }}</div>
1411
+ <div class="col col--type">{{ f.controlType }}</div>
1412
+ <div class="col col--vis">
1413
+ <label class="toggle">
1414
+ <input type="checkbox" [checked]="!f.hidden" (change)="toggleVisibility(f, $any($event.target).checked)" />
1415
+ <span>{{ f.hidden ? 'Oculto' : 'Visível' }}</span>
1416
+ </label>
1417
+ </div>
1418
+ <div class="col col--req">
1419
+ <label class="toggle">
1420
+ <input type="checkbox" [checked]="!!f.required" (change)="toggleRequired(f, $any($event.target).checked)" />
1421
+ <span>{{ f.required ? 'Sim' : 'Não' }}</span>
1422
+ </label>
1423
+ </div>
1424
+ <div class="col col--ro">
1425
+ <label class="toggle">
1426
+ <input type="checkbox" [checked]="!!f.readOnly" (change)="toggleReadOnly(f, $any($event.target).checked)" />
1427
+ <span>{{ f.readOnly ? 'Sim' : 'Não' }}</span>
1428
+ </label>
1429
+ </div>
1430
+ <div class="col col--dis">
1431
+ <label class="toggle">
1432
+ <input type="checkbox" [checked]="!!f.disabled" (change)="toggleDisabled(f, $any($event.target).checked)" />
1433
+ <span>{{ f.disabled ? 'Sim' : 'Não' }}</span>
1434
+ </label>
1435
+ </div>
1436
+ </div>
1437
+ </div>
1438
+ </div>
1439
+ `, isInline: true, styles: [".mf-editor{display:grid;gap:12px;padding:12px}.mf-editor__toolbar{display:flex;align-items:center;gap:8px}.mf-editor__toolbar .spacer{flex:1}.mf-editor__list{display:grid;gap:6px}.mf-editor__row{display:grid;grid-template-columns:2fr 2fr 1fr 1fr 1fr 1fr 1fr;align-items:center;gap:8px;padding:8px 10px;border:1px solid rgba(0,0,0,.08);border-radius:6px}.mf-editor__row--head{font-weight:600;background:#0000000a}.col code{background:#0000000d;padding:2px 6px;border-radius:4px}button{padding:6px 10px;border:1px solid rgba(0,0,0,.12);background:#fff;border-radius:4px;cursor:pointer}.toggle{display:inline-flex;align-items:center;gap:6px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
1440
+ }
1441
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormConfigEditorComponent, decorators: [{
1442
+ type: Component,
1443
+ args: [{ selector: 'praxis-manual-form-config-editor', standalone: true, imports: [CommonModule, FormsModule], template: `
1444
+ <div class="mf-editor">
1445
+ <div class="mf-editor__toolbar">
1446
+ <label><input type="checkbox" [(ngModel)]="onlyHidden" (change)="applyFilter()" /> Mostrar apenas ocultos</label>
1447
+ <div class="spacer"></div>
1448
+ <button type="button" (click)="close()">Fechar</button>
1449
+ </div>
1450
+
1451
+ <div class="mf-editor__list">
1452
+ <div class="mf-editor__row mf-editor__row--head">
1453
+ <div class="col col--name">Campo</div>
1454
+ <div class="col col--label">Rótulo</div>
1455
+ <div class="col col--type">Tipo</div>
1456
+ <div class="col col--vis">Visível</div>
1457
+ <div class="col col--req">Obrigatório</div>
1458
+ <div class="col col--ro">Som. leitura</div>
1459
+ <div class="col col--dis">Desabilitado</div>
1460
+ </div>
1461
+
1462
+ <div class="mf-editor__row" *ngFor="let f of filtered">
1463
+ <div class="col col--name"><code>{{ f.name }}</code></div>
1464
+ <div class="col col--label">{{ f.label || f.name }}</div>
1465
+ <div class="col col--type">{{ f.controlType }}</div>
1466
+ <div class="col col--vis">
1467
+ <label class="toggle">
1468
+ <input type="checkbox" [checked]="!f.hidden" (change)="toggleVisibility(f, $any($event.target).checked)" />
1469
+ <span>{{ f.hidden ? 'Oculto' : 'Visível' }}</span>
1470
+ </label>
1471
+ </div>
1472
+ <div class="col col--req">
1473
+ <label class="toggle">
1474
+ <input type="checkbox" [checked]="!!f.required" (change)="toggleRequired(f, $any($event.target).checked)" />
1475
+ <span>{{ f.required ? 'Sim' : 'Não' }}</span>
1476
+ </label>
1477
+ </div>
1478
+ <div class="col col--ro">
1479
+ <label class="toggle">
1480
+ <input type="checkbox" [checked]="!!f.readOnly" (change)="toggleReadOnly(f, $any($event.target).checked)" />
1481
+ <span>{{ f.readOnly ? 'Sim' : 'Não' }}</span>
1482
+ </label>
1483
+ </div>
1484
+ <div class="col col--dis">
1485
+ <label class="toggle">
1486
+ <input type="checkbox" [checked]="!!f.disabled" (change)="toggleDisabled(f, $any($event.target).checked)" />
1487
+ <span>{{ f.disabled ? 'Sim' : 'Não' }}</span>
1488
+ </label>
1489
+ </div>
1490
+ </div>
1491
+ </div>
1492
+ </div>
1493
+ `, styles: [".mf-editor{display:grid;gap:12px;padding:12px}.mf-editor__toolbar{display:flex;align-items:center;gap:8px}.mf-editor__toolbar .spacer{flex:1}.mf-editor__list{display:grid;gap:6px}.mf-editor__row{display:grid;grid-template-columns:2fr 2fr 1fr 1fr 1fr 1fr 1fr;align-items:center;gap:8px;padding:8px 10px;border:1px solid rgba(0,0,0,.08);border-radius:6px}.mf-editor__row--head{font-weight:600;background:#0000000a}.col code{background:#0000000d;padding:2px 6px;border-radius:4px}button{padding:6px 10px;border:1px solid rgba(0,0,0,.12);background:#fff;border-radius:4px;cursor:pointer}.toggle{display:inline-flex;align-items:center;gap:6px}\n"] }]
1494
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
1495
+ type: Inject,
1496
+ args: [SETTINGS_PANEL_DATA]
1497
+ }] }, { type: i3.SettingsPanelRef, decorators: [{
1498
+ type: Inject,
1499
+ args: [SETTINGS_PANEL_REF]
1500
+ }] }] });
1501
+
1502
+ var manualFormConfigEditor_component = /*#__PURE__*/Object.freeze({
1503
+ __proto__: null,
1504
+ ManualFormConfigEditorComponent: ManualFormConfigEditorComponent
1505
+ });
1506
+
1507
+ class ManualFieldDirective {
1508
+ templateRef;
1509
+ viewContainer;
1510
+ fieldName;
1511
+ instance;
1512
+ constructor(templateRef, viewContainer) {
1513
+ this.templateRef = templateRef;
1514
+ this.viewContainer = viewContainer;
1515
+ }
1516
+ ngOnChanges(changes) {
1517
+ if ('fieldName' in changes || 'instance' in changes) {
1518
+ this.render();
1519
+ }
1520
+ }
1521
+ render() {
1522
+ this.viewContainer.clear();
1523
+ if (!this.instance || !this.fieldName) {
1524
+ return;
1525
+ }
1526
+ const metadata = this.instance.getFieldMetadata(this.fieldName);
1527
+ const context = {
1528
+ $implicit: metadata,
1529
+ metadata,
1530
+ fieldName: this.fieldName,
1531
+ };
1532
+ this.viewContainer.createEmbeddedView(this.templateRef, context);
1533
+ }
1534
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFieldDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive });
1535
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.1.4", type: ManualFieldDirective, isStandalone: true, selector: "[praxisManualField]", inputs: { fieldName: ["praxisManualField", "fieldName"], instance: ["praxisManualFieldInstance", "instance"] }, usesOnChanges: true, ngImport: i0 });
1536
+ }
1537
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFieldDirective, decorators: [{
1538
+ type: Directive,
1539
+ args: [{
1540
+ selector: '[praxisManualField]',
1541
+ standalone: true,
1542
+ }]
1543
+ }], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }], propDecorators: { fieldName: [{
1544
+ type: Input,
1545
+ args: ['praxisManualField']
1546
+ }], instance: [{
1547
+ type: Input,
1548
+ args: ['praxisManualFieldInstance']
1549
+ }] } });
1550
+
1551
+ /**
1552
+ * Attach to any field inside <praxis-manual-form> to open the metadata editor
1553
+ * on double click, respecting the form's editModeEnabled state.
1554
+ *
1555
+ * Usage:
1556
+ * <pdx-text-input pdxManualEdit="nome" ...></pdx-text-input>
1557
+ */
1558
+ class ManualFieldEditorOnDblclickDirective {
1559
+ fieldName;
1560
+ manualForm = inject(ManualFormComponent, { optional: true });
1561
+ onDblClick() {
1562
+ if (!this.fieldName)
1563
+ return;
1564
+ this.manualForm?.tryOpenFieldEditor(this.fieldName);
1565
+ }
1566
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFieldEditorOnDblclickDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1567
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.1.4", type: ManualFieldEditorOnDblclickDirective, isStandalone: true, selector: "[pdxManualEdit]", inputs: { fieldName: ["pdxManualEdit", "fieldName"] }, host: { listeners: { "dblclick": "onDblClick()" } }, ngImport: i0 });
1568
+ }
1569
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFieldEditorOnDblclickDirective, decorators: [{
1570
+ type: Directive,
1571
+ args: [{
1572
+ selector: '[pdxManualEdit]',
1573
+ standalone: true,
1574
+ }]
1575
+ }], propDecorators: { fieldName: [{
1576
+ type: Input,
1577
+ args: ['pdxManualEdit']
1578
+ }], onDblClick: [{
1579
+ type: HostListener,
1580
+ args: ['dblclick']
1581
+ }] } });
1582
+
1583
+ /*
1584
+ * Public API Surface of praxis-manual-form
1585
+ */
1586
+
1587
+ /**
1588
+ * Generated bundle index. Do not edit.
1589
+ */
1590
+
1591
+ export { MANUAL_FORM_CONSTRUCTOR_TO_CONTROL_TYPE, MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE, ManualFieldDirective, ManualFieldEditorOnDblclickDirective, ManualFieldMetadataBridgeService, ManualFormActionsComponent, ManualFormComponent, ManualFormConfigEditorComponent, ManualFormHeaderComponent, ManualFormInstance, ManualFormInstanceFactory, createManualFormSeed, toFieldMetadataMap };
1592
+ //# sourceMappingURL=praxisui-manual-form.mjs.map