@praxisui/manual-form 1.0.0-beta.7 → 2.0.0-beta.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.
@@ -1,14 +1,35 @@
1
- import { ensureIds, DynamicFormService, CONFIG_STORAGE, FieldControlType } from '@praxisui/core';
1
+ import { ensureIds, DynamicFormService, ASYNC_CONFIG_STORAGE, RULE_PROPERTY_SCHEMA, deepMerge, resolveControlTypeAlias, FieldControlType, normalizeControlTypeKey, FormHooksRegistry, ComponentKeyService, FIELD_SELECTOR_REGISTRY_DISABLE_DEFAULTS, DEFAULT_FIELD_SELECTOR_CONTROL_TYPE_MAP, FieldSelectorRegistry, API_URL } from '@praxisui/core';
2
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';
3
+ import { inject, Optional, Inject, Injectable, input, output, ChangeDetectionStrategy, Component, Input, InjectionToken, isDevMode, EventEmitter, HostListener, ViewChild, Output, ChangeDetectorRef, DestroyRef, PLATFORM_ID, effect, ContentChildren, signal, computed, Directive } from '@angular/core';
4
+ import { BehaviorSubject, debounceTime, fromEvent, of } from 'rxjs';
5
+ import { take } from 'rxjs/operators';
6
+ import { CommonModule, isPlatformBrowser } from '@angular/common';
7
+ import { ActivatedRoute } from '@angular/router';
8
+ import { BaseAiAdapter, PraxisAiAssistantComponent } from '@praxisui/ai';
9
+ import * as i1 from '@angular/forms';
8
10
  import { FormGroupDirective, FormGroup, FormControl, Validators, ReactiveFormsModule, FormControlName, ControlContainer, FormsModule } from '@angular/forms';
9
11
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
10
- import * as i3 from '@praxisui/settings-panel';
12
+ import { Overlay } from '@angular/cdk/overlay';
13
+ import { ComponentPortal } from '@angular/cdk/portal';
14
+ import * as i10 from '@praxisui/settings-panel';
11
15
  import { SettingsPanelService, SETTINGS_PANEL_DATA, SETTINGS_PANEL_REF } from '@praxisui/settings-panel';
16
+ import * as i2 from '@angular/material/tabs';
17
+ import { MatTabsModule } from '@angular/material/tabs';
18
+ import * as i3 from '@angular/material/form-field';
19
+ import { MatFormFieldModule } from '@angular/material/form-field';
20
+ import * as i4 from '@angular/material/input';
21
+ import { MatInputModule } from '@angular/material/input';
22
+ import * as i5 from '@angular/material/select';
23
+ import { MatSelectModule } from '@angular/material/select';
24
+ import * as i6 from '@angular/material/slide-toggle';
25
+ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
26
+ import * as i7 from '@angular/material/button';
27
+ import { MatButtonModule } from '@angular/material/button';
28
+ import * as i8 from '@angular/material/icon';
29
+ import { MatIconModule } from '@angular/material/icon';
30
+ import * as i9 from '@angular/material/tooltip';
31
+ import { MatTooltipModule } from '@angular/material/tooltip';
32
+ import { CascadeManagerTabComponent } from '@praxisui/metadata-editor';
12
33
 
13
34
  function deepClone(value) {
14
35
  if (typeof globalThis?.structuredClone === 'function') {
@@ -109,6 +130,14 @@ class ManualFormInstance {
109
130
  get fieldMetadataChanges$() {
110
131
  return this.fieldMetadata$.asObservable();
111
132
  }
133
+ /**
134
+ * Emits after field metadata has been applied to the runtime state.
135
+ * Fires on patchFieldMetadata(), replaceConfig(), resetToSeed(), and
136
+ * persisted config loads (when metadata is replaced).
137
+ */
138
+ metadataChanges() {
139
+ return this.fieldMetadata$.asObservable();
140
+ }
112
141
  get currentConfig() {
113
142
  return this.formConfig$.value;
114
143
  }
@@ -147,7 +176,7 @@ class ManualFormInstance {
147
176
  if (!current) {
148
177
  return;
149
178
  }
150
- const next = this.deepMerge(current, patch);
179
+ const next = this.applyJsonMergePatch(current, patch);
151
180
  this.fieldMetadata$.value.set(fieldName, next);
152
181
  this.fieldMetadata$.next(new Map(this.fieldMetadata$.value));
153
182
  const configClone = deepClone(this.formConfig$.value);
@@ -191,7 +220,7 @@ class ManualFormInstance {
191
220
  }
192
221
  /**
193
222
  * Persists the current configuration and, optionally, the provided form value
194
- * using ConfigStorage. By default, stores the FormGroup raw value.
223
+ * using AsyncConfigStorage. By default, stores the FormGroup raw value.
195
224
  */
196
225
  saveDraft(value = null) {
197
226
  if (!this.storage) {
@@ -208,7 +237,7 @@ class ManualFormInstance {
208
237
  },
209
238
  version: this.seed.version,
210
239
  };
211
- this.storage.saveConfig(key, payload);
240
+ this.storage.saveConfig(key, payload).pipe(take(1)).subscribe({ error: () => { } });
212
241
  }
213
242
  publishConfig(config) {
214
243
  this.formConfig$.next(config);
@@ -266,18 +295,37 @@ class ManualFormInstance {
266
295
  /* ignore */
267
296
  }
268
297
  }
269
- /** Deep-merge helper to preserve nested objects (e.g., clearButton.icon + enabled). */
270
- deepMerge(target, source) {
271
- if (source == null || typeof source !== 'object')
298
+ /**
299
+ * JSON Merge Patch semantics for metadata patches.
300
+ * `null` remove a propriedade-alvo e objetos vazios são podados.
301
+ */
302
+ applyJsonMergePatch(target, patch) {
303
+ if (patch == null || typeof patch !== 'object' || Array.isArray(patch)) {
272
304
  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);
305
+ }
306
+ const out = Array.isArray(target)
307
+ ? [...target]
308
+ : { ...target };
309
+ for (const [key, value] of Object.entries(patch)) {
310
+ if (value === null) {
311
+ delete out[key];
312
+ continue;
277
313
  }
278
- else {
279
- out[k] = v;
314
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
315
+ const merged = this.applyJsonMergePatch(out[key] && typeof out[key] === 'object' && !Array.isArray(out[key])
316
+ ? out[key]
317
+ : {}, value);
318
+ if (merged &&
319
+ typeof merged === 'object' &&
320
+ !Array.isArray(merged) &&
321
+ !Object.keys(merged).length) {
322
+ delete out[key];
323
+ continue;
324
+ }
325
+ out[key] = merged;
326
+ continue;
280
327
  }
328
+ out[key] = value;
281
329
  }
282
330
  return out;
283
331
  }
@@ -293,35 +341,42 @@ class ManualFormInstanceFactory {
293
341
  this.storage = storage;
294
342
  }
295
343
  create(seed, options = {}, externalForm) {
296
- const persisted = this.loadPersistedSnapshot(seed.formId, options);
297
- const baseConfig = persisted?.config ?? deepClone(seed.config);
344
+ const baseConfig = deepClone(seed.config);
298
345
  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 });
346
+ if (this.storage) {
347
+ const key = buildStorageKey(seed.formId, options);
348
+ this.storage
349
+ .loadConfig(key)
350
+ .pipe(take(1))
351
+ .subscribe((persisted) => {
352
+ if (!persisted)
353
+ return;
354
+ if (persisted.config) {
355
+ instance.replaceConfig(persisted.config);
356
+ }
357
+ if (persisted.value) {
358
+ instance.form.patchValue(persisted.value, { emitEvent: false });
359
+ }
360
+ });
302
361
  }
303
362
  return instance;
304
363
  }
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' });
364
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormInstanceFactory, deps: [{ token: ASYNC_CONFIG_STORAGE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
365
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormInstanceFactory, providedIn: 'root' });
314
366
  }
315
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormInstanceFactory, decorators: [{
367
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormInstanceFactory, decorators: [{
316
368
  type: Injectable,
317
369
  args: [{ providedIn: 'root' }]
318
370
  }], ctorParameters: () => [{ type: undefined, decorators: [{
319
371
  type: Optional
320
372
  }, {
321
373
  type: Inject,
322
- args: [CONFIG_STORAGE]
374
+ args: [ASYNC_CONFIG_STORAGE]
323
375
  }] }] });
324
376
  function buildStorageKey(formId, options = {}) {
377
+ if (options.storageKey) {
378
+ return String(options.storageKey);
379
+ }
325
380
  const parts = ['manual-form', options.namespace, options.tenantId, options.profileId, formId]
326
381
  .filter(Boolean)
327
382
  .map((segment) => String(segment));
@@ -334,7 +389,7 @@ class ManualFormHeaderComponent {
334
389
  description = input(...(ngDevMode ? [undefined, { debugName: "description" }] : []));
335
390
  saveLabel = input('Salvar customizações', ...(ngDevMode ? [{ debugName: "saveLabel" }] : []));
336
391
  resetLabel = input('Restaurar padrão', ...(ngDevMode ? [{ debugName: "resetLabel" }] : []));
337
- editModeEnabled = input(false, ...(ngDevMode ? [{ debugName: "editModeEnabled" }] : []));
392
+ enableCustomization = input(false, ...(ngDevMode ? [{ debugName: "enableCustomization" }] : []));
338
393
  editFormLabel = input('Editar formulário', ...(ngDevMode ? [{ debugName: "editFormLabel" }] : []));
339
394
  save = output();
340
395
  reset = output();
@@ -358,8 +413,8 @@ class ManualFormHeaderComponent {
358
413
  this.firstSection?.description ||
359
414
  undefined);
360
415
  }
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: `
416
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
417
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", 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 }, enableCustomization: { classPropertyName: "enableCustomization", publicName: "enableCustomization", 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
418
  <header class="pdx-manual-form__header">
364
419
  <div class="pdx-manual-form__title">
365
420
  <h2>{{ displayTitle }}</h2>
@@ -370,16 +425,16 @@ class ManualFormHeaderComponent {
370
425
  }
371
426
  </div>
372
427
  <div class="pdx-manual-form__header-actions">
373
- @if (editModeEnabled()) {
374
- <button type="button" (click)="editForm.emit()" [attr.aria-label]="editFormLabel()">{{ editFormLabel() }}</button>
428
+ @if (enableCustomization()) {
429
+ <button type="button" class="pdx-btn pdx-btn--ghost" (click)="editForm.emit()" [attr.aria-label]="editFormLabel()">{{ editFormLabel() }}</button>
375
430
  }
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>
431
+ <button type="button" class="pdx-btn pdx-btn--ghost" (click)="reset.emit()" [attr.aria-label]="resetLabel()">{{ resetLabel() }}</button>
432
+ <button type="button" class="pdx-btn pdx-btn--primary" (click)="save.emit()" [attr.aria-label]="saveLabel()">{{ saveLabel() }}</button>
378
433
  </div>
379
434
  </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 });
435
+ `, 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.25rem;font-weight:600;line-height:1.3}.pdx-manual-form__subtitle{margin:.25rem 0 0;color:var(--md-sys-color-on-surface-variant)}.pdx-manual-form__header-actions{display:flex;gap:.5rem;flex-wrap:wrap}.pdx-btn{min-height:36px;padding:0 14px;border-radius:8px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);cursor:pointer;transition:background-color .18s ease,border-color .18s ease,color .18s ease}.pdx-btn:hover{background:var(--md-sys-color-surface-container)}.pdx-btn:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-btn--ghost{background:transparent;border-color:var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant)}.pdx-btn--primary{background:var(--md-sys-color-primary);border-color:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary)}.pdx-btn--primary:hover{background:var(--md-sys-color-primary-container);border-color:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
381
436
  }
382
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormHeaderComponent, decorators: [{
437
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormHeaderComponent, decorators: [{
383
438
  type: Component,
384
439
  args: [{ selector: 'praxis-manual-form-header', standalone: true, imports: [CommonModule], template: `
385
440
  <header class="pdx-manual-form__header">
@@ -392,15 +447,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
392
447
  }
393
448
  </div>
394
449
  <div class="pdx-manual-form__header-actions">
395
- @if (editModeEnabled()) {
396
- <button type="button" (click)="editForm.emit()" [attr.aria-label]="editFormLabel()">{{ editFormLabel() }}</button>
450
+ @if (enableCustomization()) {
451
+ <button type="button" class="pdx-btn pdx-btn--ghost" (click)="editForm.emit()" [attr.aria-label]="editFormLabel()">{{ editFormLabel() }}</button>
397
452
  }
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>
453
+ <button type="button" class="pdx-btn pdx-btn--ghost" (click)="reset.emit()" [attr.aria-label]="resetLabel()">{{ resetLabel() }}</button>
454
+ <button type="button" class="pdx-btn pdx-btn--primary" (click)="save.emit()" [attr.aria-label]="saveLabel()">{{ saveLabel() }}</button>
400
455
  </div>
401
456
  </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
- }] });
457
+ `, 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.25rem;font-weight:600;line-height:1.3}.pdx-manual-form__subtitle{margin:.25rem 0 0;color:var(--md-sys-color-on-surface-variant)}.pdx-manual-form__header-actions{display:flex;gap:.5rem;flex-wrap:wrap}.pdx-btn{min-height:36px;padding:0 14px;border-radius:8px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);cursor:pointer;transition:background-color .18s ease,border-color .18s ease,color .18s ease}.pdx-btn:hover{background:var(--md-sys-color-surface-container)}.pdx-btn:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-btn--ghost{background:transparent;border-color:var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant)}.pdx-btn--primary{background:var(--md-sys-color-primary);border-color:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary)}.pdx-btn--primary:hover{background:var(--md-sys-color-primary-container);border-color:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}\n"] }]
458
+ }], propDecorators: { instance: [{ type: i0.Input, args: [{ isSignal: true, alias: "instance", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], saveLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "saveLabel", required: false }] }], resetLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "resetLabel", required: false }] }], enableCustomization: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableCustomization", required: false }] }], editFormLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "editFormLabel", required: false }] }], save: [{ type: i0.Output, args: ["save"] }], reset: [{ type: i0.Output, args: ["reset"] }], editForm: [{ type: i0.Output, args: ["editForm"] }] } });
404
459
 
405
460
  class ManualFormActionsComponent {
406
461
  actions = input(...(ngDevMode ? [undefined, { debugName: "actions" }] : []));
@@ -417,28 +472,28 @@ class ManualFormActionsComponent {
417
472
  this.actionClick.emit(kind);
418
473
  }
419
474
  }
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: `
475
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormActionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
476
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", 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
477
  @if (actions(); as layout) {
423
478
  <footer class="pdx-manual-form__actions">
424
479
  <div class="pdx-manual-form__actions-secondary">
425
480
  @if (layout.cancel; as cancel) {
426
481
  @if (isVisible(cancel)) {
427
- <button type="button" [disabled]="cancel.disabled" (click)="emit('cancel', cancel)">
482
+ <button type="button" class="pdx-btn pdx-btn--ghost" [disabled]="cancel.disabled" (click)="emit('cancel', cancel)">
428
483
  {{ cancel.label || 'Cancelar' }}
429
484
  </button>
430
485
  }
431
486
  }
432
487
  @if (layout.reset; as resetBtn) {
433
488
  @if (isVisible(resetBtn)) {
434
- <button type="button" [disabled]="resetBtn.disabled" (click)="emit('reset', resetBtn)">
489
+ <button type="button" class="pdx-btn pdx-btn--ghost" [disabled]="resetBtn.disabled" (click)="emit('reset', resetBtn)">
435
490
  {{ resetBtn.label || 'Resetar' }}
436
491
  </button>
437
492
  }
438
493
  }
439
494
  @for (action of layout.custom || []; track trackByFn ? trackByFn($index, action) : (action?.id || $index)) {
440
495
  @if (isVisible(action)) {
441
- <button type="button" [disabled]="action.disabled" (click)="emit(action.id || 'custom', action)">
496
+ <button type="button" class="pdx-btn pdx-btn--ghost" [disabled]="action.disabled" (click)="emit(action.id || 'custom', action)">
442
497
  {{ action.label || action.id || 'Ação' }}
443
498
  </button>
444
499
  }
@@ -447,7 +502,7 @@ class ManualFormActionsComponent {
447
502
  <div class="pdx-manual-form__actions-primary">
448
503
  @if (layout.submit; as submitBtn) {
449
504
  @if (isVisible(submitBtn)) {
450
- <button type="submit" [disabled]="submitBtn.disabled" (click)="emit('submit', submitBtn)">
505
+ <button type="submit" class="pdx-btn pdx-btn--primary" [disabled]="submitBtn.disabled" (click)="emit('submit', submitBtn)">
451
506
  {{ submitBtn.label || 'Salvar' }}
452
507
  </button>
453
508
  }
@@ -455,9 +510,9 @@ class ManualFormActionsComponent {
455
510
  </div>
456
511
  </footer>
457
512
  }
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 });
513
+ `, isInline: true, styles: [":host{display:block}.pdx-manual-form__actions{display:flex;justify-content:space-between;align-items:center;gap:1rem;margin-top:2rem;flex-wrap:wrap}.pdx-manual-form__actions-secondary,.pdx-manual-form__actions-primary{display:flex;gap:.5rem}.pdx-btn{min-height:36px;padding:0 14px;border-radius:8px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);cursor:pointer;transition:background-color .18s ease,border-color .18s ease,color .18s ease}.pdx-btn:hover{background:var(--md-sys-color-surface-container)}.pdx-btn:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-btn:disabled{opacity:.6;cursor:not-allowed}.pdx-btn--ghost{background:transparent;border-color:var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant)}.pdx-btn--primary{background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);border-color:var(--md-sys-color-primary)}.pdx-btn--primary:hover{background:var(--md-sys-color-primary-container);border-color:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
459
514
  }
460
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormActionsComponent, decorators: [{
515
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormActionsComponent, decorators: [{
461
516
  type: Component,
462
517
  args: [{ selector: 'praxis-manual-form-actions', standalone: true, imports: [CommonModule], template: `
463
518
  @if (actions(); as layout) {
@@ -465,21 +520,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
465
520
  <div class="pdx-manual-form__actions-secondary">
466
521
  @if (layout.cancel; as cancel) {
467
522
  @if (isVisible(cancel)) {
468
- <button type="button" [disabled]="cancel.disabled" (click)="emit('cancel', cancel)">
523
+ <button type="button" class="pdx-btn pdx-btn--ghost" [disabled]="cancel.disabled" (click)="emit('cancel', cancel)">
469
524
  {{ cancel.label || 'Cancelar' }}
470
525
  </button>
471
526
  }
472
527
  }
473
528
  @if (layout.reset; as resetBtn) {
474
529
  @if (isVisible(resetBtn)) {
475
- <button type="button" [disabled]="resetBtn.disabled" (click)="emit('reset', resetBtn)">
530
+ <button type="button" class="pdx-btn pdx-btn--ghost" [disabled]="resetBtn.disabled" (click)="emit('reset', resetBtn)">
476
531
  {{ resetBtn.label || 'Resetar' }}
477
532
  </button>
478
533
  }
479
534
  }
480
535
  @for (action of layout.custom || []; track trackByFn ? trackByFn($index, action) : (action?.id || $index)) {
481
536
  @if (isVisible(action)) {
482
- <button type="button" [disabled]="action.disabled" (click)="emit(action.id || 'custom', action)">
537
+ <button type="button" class="pdx-btn pdx-btn--ghost" [disabled]="action.disabled" (click)="emit(action.id || 'custom', action)">
483
538
  {{ action.label || action.id || 'Ação' }}
484
539
  </button>
485
540
  }
@@ -488,7 +543,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
488
543
  <div class="pdx-manual-form__actions-primary">
489
544
  @if (layout.submit; as submitBtn) {
490
545
  @if (isVisible(submitBtn)) {
491
- <button type="submit" [disabled]="submitBtn.disabled" (click)="emit('submit', submitBtn)">
546
+ <button type="submit" class="pdx-btn pdx-btn--primary" [disabled]="submitBtn.disabled" (click)="emit('submit', submitBtn)">
492
547
  {{ submitBtn.label || 'Salvar' }}
493
548
  </button>
494
549
  }
@@ -496,10 +551,632 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
496
551
  </div>
497
552
  </footer>
498
553
  }
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: [{
554
+ `, 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;flex-wrap:wrap}.pdx-manual-form__actions-secondary,.pdx-manual-form__actions-primary{display:flex;gap:.5rem}.pdx-btn{min-height:36px;padding:0 14px;border-radius:8px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);cursor:pointer;transition:background-color .18s ease,border-color .18s ease,color .18s ease}.pdx-btn:hover{background:var(--md-sys-color-surface-container)}.pdx-btn:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.pdx-btn:disabled{opacity:.6;cursor:not-allowed}.pdx-btn--ghost{background:transparent;border-color:var(--md-sys-color-outline-variant);color:var(--md-sys-color-on-surface-variant)}.pdx-btn--primary{background:var(--md-sys-color-primary);color:var(--md-sys-color-on-primary);border-color:var(--md-sys-color-primary)}.pdx-btn--primary:hover{background:var(--md-sys-color-primary-container);border-color:var(--md-sys-color-primary-container);color:var(--md-sys-color-on-primary-container)}\n"] }]
555
+ }], propDecorators: { actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], trackByFn: [{
501
556
  type: Input
502
- }] } });
557
+ }], actionClick: [{ type: i0.Output, args: ["actionClick"] }] } });
558
+
559
+ /**
560
+ * Capabilities catalog for Manual Form (FormConfig-based).
561
+ * Focuses on the FormConfig structure managed by ManualFormInstance.
562
+ */
563
+ const ENUMS = {
564
+ actionColor: ['primary', 'accent', 'warn', 'basic'],
565
+ actionSize: ['small', 'medium', 'large'],
566
+ actionVariant: ['raised', 'stroked', 'flat', 'fab'],
567
+ actionType: ['button', 'submit', 'reset'],
568
+ actionsPosition: ['left', 'center', 'right', 'justified', 'split'],
569
+ actionsMobilePosition: ['left', 'center', 'right', 'justified'],
570
+ actionsOrientation: ['horizontal', 'vertical'],
571
+ actionsSpacing: ['compact', 'normal', 'spacious'],
572
+ actionsPlacement: ['insideLastSection', 'afterSections', 'top'],
573
+ columnAlign: ['start', 'center', 'end', 'stretch'],
574
+ sectionTitleStyle: ['titleLarge', 'titleMedium', 'titleSmall', 'headlineSmall'],
575
+ sectionDescriptionStyle: ['bodyLarge', 'bodyMedium', 'bodySmall'],
576
+ headerAlign: ['start', 'center'],
577
+ schemaType: ['response', 'request'],
578
+ customizationSource: ['user', 'server', 'system'],
579
+ formRuleTargetType: ['field', 'section', 'action', 'row', 'column'],
580
+ formRuleContext: ['visibility', 'readOnly', 'style', 'validation', 'notification'],
581
+ mode: ['create', 'edit', 'view'],
582
+ schemaSource: ['resource', 'filter'],
583
+ };
584
+ function generateRulePropertyCapabilities(targetType, properties) {
585
+ const result = [];
586
+ properties.forEach((prop) => {
587
+ const valueKind = prop.type === 'object'
588
+ ? 'object'
589
+ : prop.type === 'enum'
590
+ ? 'enum'
591
+ : prop.type === 'boolean'
592
+ ? 'boolean'
593
+ : prop.type === 'number'
594
+ ? 'number'
595
+ : 'string';
596
+ const capability = {
597
+ path: `formRules[].effect.properties.${prop.name}`,
598
+ category: 'rules',
599
+ valueKind,
600
+ description: `Propriedade '${prop.label}' aplicada ao ${targetType}.`,
601
+ example: prop.type === 'boolean'
602
+ ? 'true'
603
+ : prop.type === 'number'
604
+ ? '123'
605
+ : prop.type === 'enum'
606
+ ? (prop.enumValues?.[0]?.value || 'valor')
607
+ : 'valor',
608
+ safetyNotes: prop.type === 'object' ? 'O objeto deve seguir o schema esperado pelo componente alvo.' : undefined,
609
+ };
610
+ if (prop.enumValues) {
611
+ capability.allowedValues = prop.enumValues.map((ev) => ev.value);
612
+ }
613
+ result.push(capability);
614
+ });
615
+ return result;
616
+ }
617
+ function generateRulePropertyWhenFalseCapabilities(targetType, properties) {
618
+ const result = [];
619
+ properties.forEach((prop) => {
620
+ const valueKind = prop.type === 'object'
621
+ ? 'object'
622
+ : prop.type === 'enum'
623
+ ? 'enum'
624
+ : prop.type === 'boolean'
625
+ ? 'boolean'
626
+ : prop.type === 'number'
627
+ ? 'number'
628
+ : 'string';
629
+ const capability = {
630
+ path: `formRules[].effect.propertiesWhenFalse.${prop.name}`,
631
+ category: 'rules',
632
+ valueKind,
633
+ description: `Propriedade '${prop.label}' aplicada ao ${targetType} quando a condicao e falsa.`,
634
+ example: prop.type === 'boolean'
635
+ ? 'false'
636
+ : prop.type === 'number'
637
+ ? '0'
638
+ : prop.type === 'enum'
639
+ ? (prop.enumValues?.[0]?.value || 'valor')
640
+ : 'valor',
641
+ safetyNotes: prop.type === 'object' ? 'O objeto deve seguir o schema esperado pelo componente alvo.' : undefined,
642
+ };
643
+ if (prop.enumValues) {
644
+ capability.allowedValues = prop.enumValues.map((ev) => ev.value);
645
+ }
646
+ result.push(capability);
647
+ });
648
+ return result;
649
+ }
650
+ const MANUAL_FORM_AI_CAPABILITIES = {
651
+ version: 'v1.3',
652
+ enums: ENUMS,
653
+ targets: [
654
+ 'praxis-manual-form',
655
+ 'praxis-manual-form-config-editor',
656
+ 'praxis-manual-form-actions',
657
+ 'praxis-manual-form-header',
658
+ ],
659
+ notes: [
660
+ 'ManualForm usa FormConfig interno para metadados e layout.',
661
+ 'fieldMetadata[] deve seguir o catalogo base de FieldMetadata.',
662
+ 'Arrays de layout e actions devem ser mesclados por id/label, evitando replace total.',
663
+ 'ManualFormHeader usa metadata.title/metadata.description como fallback para titulo/descricao.',
664
+ 'Se controlType nao tiver catalogo dedicado, trate como FieldMetadata base.',
665
+ ],
666
+ capabilities: [
667
+ // --- Form metadata ---
668
+ {
669
+ path: 'metadata.version',
670
+ category: 'metadata',
671
+ valueKind: 'string',
672
+ description: 'Versao da configuracao do formulario.',
673
+ },
674
+ {
675
+ path: 'metadata.source',
676
+ category: 'metadata',
677
+ valueKind: 'enum',
678
+ allowedValues: ['local', 'server', 'default'],
679
+ description: 'Origem da configuracao.',
680
+ },
681
+ {
682
+ path: 'metadata.title',
683
+ category: 'metadata',
684
+ valueKind: 'string',
685
+ description: 'Titulo exibido no cabecalho quando formTitle nao for informado.',
686
+ },
687
+ {
688
+ path: 'metadata.description',
689
+ category: 'metadata',
690
+ valueKind: 'string',
691
+ description: 'Descricao exibida no cabecalho quando formDescription nao for informado.',
692
+ },
693
+ {
694
+ path: 'metadata.lastUpdated',
695
+ category: 'metadata',
696
+ valueKind: 'string',
697
+ description: 'Timestamp da ultima atualizacao.',
698
+ safetyNotes: 'Valor de auditoria; preferir preenchimento pelo host.',
699
+ },
700
+ {
701
+ path: 'metadata.schemaId',
702
+ category: 'metadata',
703
+ valueKind: 'string',
704
+ description: 'Identificador do schema usado na composicao.',
705
+ },
706
+ {
707
+ path: 'metadata.serverHash',
708
+ category: 'metadata',
709
+ valueKind: 'string',
710
+ description: 'Hash do schema/servidor para reconciliacao.',
711
+ },
712
+ {
713
+ path: 'metadata.schemaContext',
714
+ category: 'metadata',
715
+ valueKind: 'object',
716
+ description: 'Contexto do schema (path/operation/schemaType).',
717
+ },
718
+ {
719
+ path: 'metadata.schemaContext.path',
720
+ category: 'metadata',
721
+ valueKind: 'string',
722
+ description: 'Path do schema usado para compor o formulario.',
723
+ },
724
+ {
725
+ path: 'metadata.schemaContext.operation',
726
+ category: 'metadata',
727
+ valueKind: 'string',
728
+ description: 'Operacao do schema (ex: GET/POST).',
729
+ },
730
+ {
731
+ path: 'metadata.schemaContext.schemaType',
732
+ category: 'metadata',
733
+ valueKind: 'enum',
734
+ allowedValues: ENUMS.schemaType,
735
+ description: 'Tipo do schema (response/request).',
736
+ },
737
+ {
738
+ path: 'metadata.schemaContext.internal',
739
+ category: 'metadata',
740
+ valueKind: 'boolean',
741
+ description: 'Marca schema como interno.',
742
+ },
743
+ {
744
+ path: 'metadata.schemaContext.tenant',
745
+ category: 'metadata',
746
+ valueKind: 'string',
747
+ description: 'Tenant do schema.',
748
+ },
749
+ {
750
+ path: 'metadata.schemaContext.locale',
751
+ category: 'metadata',
752
+ valueKind: 'string',
753
+ description: 'Locale do schema.',
754
+ },
755
+ {
756
+ path: 'metadata.customizations',
757
+ category: 'metadata',
758
+ valueKind: 'array',
759
+ description: 'Log de customizacoes.',
760
+ safetyNotes: 'Usado para auditoria; nao gerar automaticamente.',
761
+ },
762
+ {
763
+ path: 'metadata.customizations[]',
764
+ category: 'metadata',
765
+ valueKind: 'object',
766
+ description: 'Registro de customizacao.',
767
+ },
768
+ {
769
+ path: 'metadata.customizations[].fieldName',
770
+ category: 'metadata',
771
+ valueKind: 'string',
772
+ description: 'Campo customizado.',
773
+ },
774
+ {
775
+ path: 'metadata.customizations[].property',
776
+ category: 'metadata',
777
+ valueKind: 'string',
778
+ description: 'Propriedade alterada.',
779
+ },
780
+ {
781
+ path: 'metadata.customizations[].oldValue',
782
+ category: 'metadata',
783
+ valueKind: 'object',
784
+ description: 'Valor anterior.',
785
+ },
786
+ {
787
+ path: 'metadata.customizations[].newValue',
788
+ category: 'metadata',
789
+ valueKind: 'object',
790
+ description: 'Novo valor.',
791
+ },
792
+ {
793
+ path: 'metadata.customizations[].timestamp',
794
+ category: 'metadata',
795
+ valueKind: 'string',
796
+ description: 'Timestamp da alteracao (ISO).',
797
+ },
798
+ {
799
+ path: 'metadata.customizations[].source',
800
+ category: 'metadata',
801
+ valueKind: 'enum',
802
+ allowedValues: ENUMS.customizationSource,
803
+ description: 'Origem da alteracao.',
804
+ },
805
+ // --- Field metadata ---
806
+ {
807
+ path: 'fieldMetadata',
808
+ category: 'fields',
809
+ valueKind: 'array',
810
+ critical: true,
811
+ description: 'Metadados dos campos (FieldMetadata).',
812
+ safetyNotes: 'Use o catalogo base FieldMetadata para detalhes de campos.',
813
+ },
814
+ {
815
+ path: 'fieldMetadata[]',
816
+ category: 'fields',
817
+ valueKind: 'object',
818
+ critical: true,
819
+ description: 'Item de metadados do campo (FieldMetadata).',
820
+ safetyNotes: 'Use o catalogo base FieldMetadata para detalhes de campos.',
821
+ },
822
+ // --- Layout ---
823
+ { path: 'sections[]', category: 'layout', valueKind: 'object', description: 'Secao do formulario.' },
824
+ { path: 'sections[].id', category: 'layout', valueKind: 'string', description: 'ID da secao.' },
825
+ { path: 'sections[].title', category: 'layout', valueKind: 'string', description: 'Titulo da secao.' },
826
+ { path: 'sections[].description', category: 'layout', valueKind: 'string', description: 'Descricao da secao.' },
827
+ { path: 'sections[].icon', category: 'layout', valueKind: 'string', description: 'Icone da secao.' },
828
+ { path: 'sections[].collapsible', category: 'layout', valueKind: 'boolean', description: 'Secao recolhivel.' },
829
+ { path: 'sections[].collapsed', category: 'layout', valueKind: 'boolean', description: 'Estado inicial recolhido.' },
830
+ { path: 'sections[].gapBottom', category: 'layout', valueKind: 'number', description: 'Espaco extra apos a secao (px).' },
831
+ { path: 'sections[].titleGapBottom', category: 'layout', valueKind: 'number', description: 'Espaco abaixo do titulo (px).' },
832
+ { path: 'sections[].descriptionGapBottom', category: 'layout', valueKind: 'number', description: 'Espaco abaixo da descricao (px).' },
833
+ { path: 'sections[].titleStyle', category: 'layout', valueKind: 'enum', allowedValues: ENUMS.sectionTitleStyle, description: 'Preset tipografico do titulo.' },
834
+ { path: 'sections[].descriptionStyle', category: 'layout', valueKind: 'enum', allowedValues: ENUMS.sectionDescriptionStyle, description: 'Preset tipografico da descricao.' },
835
+ { path: 'sections[].titleColor', category: 'layout', valueKind: 'string', description: 'Cor do titulo.' },
836
+ { path: 'sections[].descriptionColor', category: 'layout', valueKind: 'string', description: 'Cor da descricao.' },
837
+ { path: 'sections[].headerAlign', category: 'layout', valueKind: 'enum', allowedValues: ENUMS.headerAlign, description: 'Alinhamento do header.' },
838
+ { path: 'sections[].headerTooltip', category: 'layout', valueKind: 'string', description: 'Tooltip do header da secao.' },
839
+ { path: 'sections[].rows[]', category: 'layout', valueKind: 'object', description: 'Linha da secao.' },
840
+ { path: 'sections[].rows[].id', category: 'layout', valueKind: 'string', description: 'ID da linha.' },
841
+ { path: 'sections[].rows[].title', category: 'layout', valueKind: 'string', description: 'Titulo da linha.' },
842
+ { path: 'sections[].rows[].gap', category: 'layout', valueKind: 'number', description: 'Espaco horizontal entre colunas (px).' },
843
+ { path: 'sections[].rows[].rowGap', category: 'layout', valueKind: 'number', description: 'Espaco vertical apos a linha (px).' },
844
+ { path: 'sections[].rows[].columns[]', category: 'layout', valueKind: 'object', description: 'Coluna da linha.' },
845
+ { path: 'sections[].rows[].columns[].id', category: 'layout', valueKind: 'string', description: 'ID da coluna.' },
846
+ { path: 'sections[].rows[].columns[].title', category: 'layout', valueKind: 'string', description: 'Titulo da coluna.' },
847
+ { path: 'sections[].rows[].columns[].fields', category: 'layout', valueKind: 'array', description: 'Campos associados a coluna.' },
848
+ { path: 'sections[].rows[].columns[].fields[]', category: 'layout', valueKind: 'string', description: 'Nome do campo na coluna.', safetyNotes: 'Use nomes presentes em fieldMetadata[].name.' },
849
+ { path: 'sections[].rows[].columns[].span', category: 'layout', valueKind: 'object', description: 'Span responsivo.' },
850
+ { path: 'sections[].rows[].columns[].span.xs', category: 'layout', valueKind: 'number', description: 'Span xs (1-12).' },
851
+ { path: 'sections[].rows[].columns[].span.sm', category: 'layout', valueKind: 'number', description: 'Span sm (1-12).' },
852
+ { path: 'sections[].rows[].columns[].span.md', category: 'layout', valueKind: 'number', description: 'Span md (1-12).' },
853
+ { path: 'sections[].rows[].columns[].span.lg', category: 'layout', valueKind: 'number', description: 'Span lg (1-12).' },
854
+ { path: 'sections[].rows[].columns[].span.xl', category: 'layout', valueKind: 'number', description: 'Span xl (1-12).' },
855
+ { path: 'sections[].rows[].columns[].offset', category: 'layout', valueKind: 'object', description: 'Offset responsivo.' },
856
+ { path: 'sections[].rows[].columns[].offset.xs', category: 'layout', valueKind: 'number', description: 'Offset xs (1-12).' },
857
+ { path: 'sections[].rows[].columns[].offset.sm', category: 'layout', valueKind: 'number', description: 'Offset sm (1-12).' },
858
+ { path: 'sections[].rows[].columns[].offset.md', category: 'layout', valueKind: 'number', description: 'Offset md (1-12).' },
859
+ { path: 'sections[].rows[].columns[].offset.lg', category: 'layout', valueKind: 'number', description: 'Offset lg (1-12).' },
860
+ { path: 'sections[].rows[].columns[].offset.xl', category: 'layout', valueKind: 'number', description: 'Offset xl (1-12).' },
861
+ { path: 'sections[].rows[].columns[].order', category: 'layout', valueKind: 'object', description: 'Order responsivo.' },
862
+ { path: 'sections[].rows[].columns[].order.xs', category: 'layout', valueKind: 'number', description: 'Order xs.' },
863
+ { path: 'sections[].rows[].columns[].order.sm', category: 'layout', valueKind: 'number', description: 'Order sm.' },
864
+ { path: 'sections[].rows[].columns[].order.md', category: 'layout', valueKind: 'number', description: 'Order md.' },
865
+ { path: 'sections[].rows[].columns[].order.lg', category: 'layout', valueKind: 'number', description: 'Order lg.' },
866
+ { path: 'sections[].rows[].columns[].order.xl', category: 'layout', valueKind: 'number', description: 'Order xl.' },
867
+ { path: 'sections[].rows[].columns[].hidden', category: 'layout', valueKind: 'object', description: 'Hidden responsivo.' },
868
+ { path: 'sections[].rows[].columns[].hidden.xs', category: 'layout', valueKind: 'boolean', description: 'Ocultar no xs.' },
869
+ { path: 'sections[].rows[].columns[].hidden.sm', category: 'layout', valueKind: 'boolean', description: 'Ocultar no sm.' },
870
+ { path: 'sections[].rows[].columns[].hidden.md', category: 'layout', valueKind: 'boolean', description: 'Ocultar no md.' },
871
+ { path: 'sections[].rows[].columns[].hidden.lg', category: 'layout', valueKind: 'boolean', description: 'Ocultar no lg.' },
872
+ { path: 'sections[].rows[].columns[].hidden.xl', category: 'layout', valueKind: 'boolean', description: 'Ocultar no xl.' },
873
+ { path: 'sections[].rows[].columns[].align', category: 'layout', valueKind: 'enum', allowedValues: ENUMS.columnAlign, description: 'Alinhamento da coluna.' },
874
+ { path: 'sections[].rows[].columns[].padding', category: 'layout', valueKind: 'number', description: 'Padding da coluna (px).' },
875
+ { path: 'sections[].rows[].columns[].className', category: 'layout', valueKind: 'string', description: 'Classe CSS da coluna.' },
876
+ { path: 'sections[].rows[].columns[].testId', category: 'layout', valueKind: 'string', description: 'Test id da coluna.' },
877
+ // --- Actions ---
878
+ { path: 'actions', category: 'actions', valueKind: 'object', description: 'Layout de acoes do formulario.' },
879
+ { path: 'actions.position', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionsPosition, description: 'Posicao das acoes.' },
880
+ { path: 'actions.orientation', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionsOrientation, description: 'Orientacao das acoes.' },
881
+ { path: 'actions.spacing', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionsSpacing, description: 'Espacamento das acoes.' },
882
+ { path: 'actions.sticky', category: 'actions', valueKind: 'boolean', description: 'Fixar barra de acoes.' },
883
+ { path: 'actions.divider', category: 'actions', valueKind: 'boolean', description: 'Exibir divisor acima das acoes.' },
884
+ { path: 'actions.placement', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionsPlacement, description: 'Posicionamento estrutural das acoes.' },
885
+ { path: 'actions.containerClassName', category: 'actions', valueKind: 'string', description: 'Classe CSS do container de acoes.' },
886
+ { path: 'actions.containerStyles', category: 'actions', valueKind: 'object', description: 'Estilos inline do container.' },
887
+ { path: 'actions.mobile', category: 'actions', valueKind: 'object', description: 'Config para mobile.' },
888
+ { path: 'actions.mobile.position', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionsMobilePosition, description: 'Posicao das acoes no mobile.' },
889
+ { path: 'actions.mobile.orientation', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionsOrientation, description: 'Orientacao no mobile.' },
890
+ { path: 'actions.mobile.collapseToMenu', category: 'actions', valueKind: 'boolean', description: 'Colapsar acoes em menu.' },
891
+ { path: 'actions.showSaveButton', category: 'actions', valueKind: 'boolean', description: 'Legacy: mostrar botao salvar.' },
892
+ { path: 'actions.submitButtonLabel', category: 'actions', valueKind: 'string', description: 'Legacy: texto do submit.' },
893
+ { path: 'actions.showCancelButton', category: 'actions', valueKind: 'boolean', description: 'Legacy: mostrar cancelar.' },
894
+ { path: 'actions.cancelButtonLabel', category: 'actions', valueKind: 'string', description: 'Legacy: texto do cancelar.' },
895
+ { path: 'actions.showResetButton', category: 'actions', valueKind: 'boolean', description: 'Legacy: mostrar reset.' },
896
+ { path: 'actions.resetButtonLabel', category: 'actions', valueKind: 'string', description: 'Legacy: texto do reset.' },
897
+ { path: 'actions.submit', category: 'actions', valueKind: 'object', description: 'Botao submit.' },
898
+ { path: 'actions.cancel', category: 'actions', valueKind: 'object', description: 'Botao cancelar.' },
899
+ { path: 'actions.reset', category: 'actions', valueKind: 'object', description: 'Botao reset.' },
900
+ { path: 'actions.custom', category: 'actions', valueKind: 'array', description: 'Botoes customizados.' },
901
+ { path: 'actions.custom[]', category: 'actions', valueKind: 'object', description: 'Configuracao de botao customizado.', safetyNotes: 'Requer action handler registrado no host.' },
902
+ { path: 'actions.submit.id', category: 'actions', valueKind: 'string', description: 'ID do botao.' },
903
+ { path: 'actions.submit.visible', category: 'actions', valueKind: 'boolean', description: 'Visibilidade.' },
904
+ { path: 'actions.submit.label', category: 'actions', valueKind: 'string', description: 'Label do botao.' },
905
+ { path: 'actions.submit.icon', category: 'actions', valueKind: 'string', description: 'Icone do botao.' },
906
+ { path: 'actions.submit.className', category: 'actions', valueKind: 'string', description: 'Classe CSS.' },
907
+ { path: 'actions.submit.style', category: 'actions', valueKind: 'object', description: 'Estilo inline.' },
908
+ { path: 'actions.submit.color', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionColor, description: 'Cor do botao.' },
909
+ { path: 'actions.submit.disabled', category: 'actions', valueKind: 'boolean', description: 'Botao desabilitado.' },
910
+ { path: 'actions.submit.type', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionType, description: 'Tipo do botao.' },
911
+ { path: 'actions.submit.action', category: 'actions', valueKind: 'string', description: 'Evento custom.', safetyNotes: 'Requer handler registrado no host.' },
912
+ { path: 'actions.submit.tooltip', category: 'actions', valueKind: 'string', description: 'Tooltip.' },
913
+ { path: 'actions.submit.loading', category: 'actions', valueKind: 'boolean', description: 'Estado loading.' },
914
+ { path: 'actions.submit.size', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionSize, description: 'Tamanho do botao.' },
915
+ { path: 'actions.submit.variant', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionVariant, description: 'Variante visual.' },
916
+ { path: 'actions.submit.shortcut', category: 'actions', valueKind: 'string', description: 'Atalho de teclado.' },
917
+ // Apply same structure for cancel/reset/custom buttons
918
+ { path: 'actions.cancel.id', category: 'actions', valueKind: 'string', description: 'ID do botao cancelar.' },
919
+ { path: 'actions.cancel.visible', category: 'actions', valueKind: 'boolean', description: 'Visibilidade do botao cancelar.' },
920
+ { path: 'actions.cancel.label', category: 'actions', valueKind: 'string', description: 'Label do botao cancelar.' },
921
+ { path: 'actions.cancel.icon', category: 'actions', valueKind: 'string', description: 'Icone do botao cancelar.' },
922
+ { path: 'actions.cancel.className', category: 'actions', valueKind: 'string', description: 'Classe CSS do botao cancelar.' },
923
+ { path: 'actions.cancel.style', category: 'actions', valueKind: 'object', description: 'Estilo inline do botao cancelar.' },
924
+ { path: 'actions.cancel.color', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionColor, description: 'Cor do botao cancelar.' },
925
+ { path: 'actions.cancel.disabled', category: 'actions', valueKind: 'boolean', description: 'Estado desabilitado do botao cancelar.' },
926
+ { path: 'actions.cancel.type', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionType, description: 'Tipo HTML do botao cancelar.' },
927
+ { path: 'actions.cancel.action', category: 'actions', valueKind: 'string', description: 'Acao customizada disparada pelo botao cancelar.', safetyNotes: 'Requer handler registrado no host.' },
928
+ { path: 'actions.cancel.tooltip', category: 'actions', valueKind: 'string', description: 'Tooltip do botao cancelar.' },
929
+ { path: 'actions.cancel.loading', category: 'actions', valueKind: 'boolean', description: 'Estado de loading do botao cancelar.' },
930
+ { path: 'actions.cancel.size', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionSize, description: 'Tamanho visual do botao cancelar.' },
931
+ { path: 'actions.cancel.variant', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionVariant, description: 'Variante visual do botao cancelar.' },
932
+ { path: 'actions.cancel.shortcut', category: 'actions', valueKind: 'string', description: 'Atalho de teclado do botao cancelar.' },
933
+ { path: 'actions.reset.id', category: 'actions', valueKind: 'string', description: 'ID do botao reset.' },
934
+ { path: 'actions.reset.visible', category: 'actions', valueKind: 'boolean', description: 'Visibilidade do botao reset.' },
935
+ { path: 'actions.reset.label', category: 'actions', valueKind: 'string', description: 'Label do botao reset.' },
936
+ { path: 'actions.reset.icon', category: 'actions', valueKind: 'string', description: 'Icone do botao reset.' },
937
+ { path: 'actions.reset.className', category: 'actions', valueKind: 'string', description: 'Classe CSS do botao reset.' },
938
+ { path: 'actions.reset.style', category: 'actions', valueKind: 'object', description: 'Estilo inline do botao reset.' },
939
+ { path: 'actions.reset.color', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionColor, description: 'Cor do botao reset.' },
940
+ { path: 'actions.reset.disabled', category: 'actions', valueKind: 'boolean', description: 'Estado desabilitado do botao reset.' },
941
+ { path: 'actions.reset.type', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionType, description: 'Tipo HTML do botao reset.' },
942
+ { path: 'actions.reset.action', category: 'actions', valueKind: 'string', description: 'Acao customizada disparada pelo botao reset.', safetyNotes: 'Requer handler registrado no host.' },
943
+ { path: 'actions.reset.tooltip', category: 'actions', valueKind: 'string', description: 'Tooltip do botao reset.' },
944
+ { path: 'actions.reset.loading', category: 'actions', valueKind: 'boolean', description: 'Estado de loading do botao reset.' },
945
+ { path: 'actions.reset.size', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionSize, description: 'Tamanho visual do botao reset.' },
946
+ { path: 'actions.reset.variant', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionVariant, description: 'Variante visual do botao reset.' },
947
+ { path: 'actions.reset.shortcut', category: 'actions', valueKind: 'string', description: 'Atalho de teclado do botao reset.' },
948
+ { path: 'actions.custom[].id', category: 'actions', valueKind: 'string', description: 'ID do botao customizado.' },
949
+ { path: 'actions.custom[].visible', category: 'actions', valueKind: 'boolean', description: 'Visibilidade do botao customizado.' },
950
+ { path: 'actions.custom[].label', category: 'actions', valueKind: 'string', description: 'Label do botao customizado.' },
951
+ { path: 'actions.custom[].icon', category: 'actions', valueKind: 'string', description: 'Icone do botao customizado.' },
952
+ { path: 'actions.custom[].className', category: 'actions', valueKind: 'string', description: 'Classe CSS do botao customizado.' },
953
+ { path: 'actions.custom[].style', category: 'actions', valueKind: 'object', description: 'Estilo inline do botao customizado.' },
954
+ { path: 'actions.custom[].color', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionColor, description: 'Cor do botao customizado.' },
955
+ { path: 'actions.custom[].disabled', category: 'actions', valueKind: 'boolean', description: 'Estado desabilitado do botao customizado.' },
956
+ { path: 'actions.custom[].type', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionType, description: 'Tipo HTML do botao customizado.' },
957
+ { path: 'actions.custom[].action', category: 'actions', valueKind: 'string', description: 'Acao customizada disparada pelo botao.', safetyNotes: 'Requer handler registrado no host.' },
958
+ { path: 'actions.custom[].tooltip', category: 'actions', valueKind: 'string', description: 'Tooltip do botao customizado.' },
959
+ { path: 'actions.custom[].loading', category: 'actions', valueKind: 'boolean', description: 'Estado de loading do botao customizado.' },
960
+ { path: 'actions.custom[].size', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionSize, description: 'Tamanho visual do botao customizado.' },
961
+ { path: 'actions.custom[].variant', category: 'actions', valueKind: 'enum', allowedValues: ENUMS.actionVariant, description: 'Variante visual do botao customizado.' },
962
+ { path: 'actions.custom[].shortcut', category: 'actions', valueKind: 'string', description: 'Atalho de teclado do botao customizado.' },
963
+ // --- Behavior ---
964
+ { path: 'behavior', category: 'behavior', valueKind: 'object', description: 'Comportamento do formulario.' },
965
+ { path: 'behavior.confirmOnUnsavedChanges', category: 'behavior', valueKind: 'boolean', description: 'Solicita confirmacao ao sair com alteracoes nao salvas.' },
966
+ { path: 'behavior.trackHistory', category: 'behavior', valueKind: 'boolean', description: 'Mantem historico de alteracoes do formulario.' },
967
+ { path: 'behavior.focusFirstError', category: 'behavior', valueKind: 'boolean', description: 'Move o foco para o primeiro campo com erro.' },
968
+ { path: 'behavior.scrollToErrors', category: 'behavior', valueKind: 'boolean', description: 'Rola a tela ate a primeira area com erro de validacao.' },
969
+ { path: 'behavior.clearAfterSave', category: 'behavior', valueKind: 'boolean', description: 'Limpa o formulario apos salvar com sucesso.' },
970
+ { path: 'behavior.redirectAfterSave', category: 'behavior', valueKind: 'string', description: 'Destino de navegacao apos salvar com sucesso.' },
971
+ { path: 'behavior.reactiveValidation', category: 'behavior', valueKind: 'boolean', description: 'Ativa validacao reativa durante a digitacao.' },
972
+ { path: 'behavior.reactiveValidationDebounceMs', category: 'behavior', valueKind: 'number', description: 'Debounce em milissegundos para a validacao reativa.' },
973
+ // --- API ---
974
+ { path: 'api', category: 'api', valueKind: 'object', description: 'Configuracao de API do formulario.' },
975
+ { path: 'api.saveEndpoint', category: 'api', valueKind: 'string', description: 'Endpoint usado para persistir os dados do formulario.' },
976
+ { path: 'api.loadEndpoint', category: 'api', valueKind: 'string', description: 'Endpoint usado para carregar os dados iniciais do formulario.' },
977
+ { path: 'api.saveMethod', category: 'api', valueKind: 'enum', allowedValues: ['POST', 'PUT', 'PATCH'], description: 'Metodo HTTP usado na operacao de salvamento.' },
978
+ { path: 'api.timeout', category: 'api', valueKind: 'number', description: 'Timeout da operacao de API em milissegundos.' },
979
+ { path: 'api.headers', category: 'api', valueKind: 'object', description: 'Headers HTTP adicionais enviados nas operacoes de API.' },
980
+ { path: 'api.idField', category: 'api', valueKind: 'string', description: 'Campo que representa o identificador primario no payload remoto.' },
981
+ { path: 'api.beforeSave', category: 'api', valueKind: 'expression', description: 'Hook antes de salvar (nao serializavel).', safetyNotes: 'Deve ser fornecido pelo host.' },
982
+ { path: 'api.afterLoad', category: 'api', valueKind: 'expression', description: 'Hook apos carregar (nao serializavel).', safetyNotes: 'Deve ser fornecido pelo host.' },
983
+ // --- Messages ---
984
+ { path: 'messages', category: 'messages', valueKind: 'object', description: 'Mensagens e i18n do formulario.' },
985
+ { path: 'messages.updateRegistrySuccess', category: 'messages', valueKind: 'string', description: 'Mensagem de sucesso ao atualizar registro.' },
986
+ { path: 'messages.createRegistrySuccess', category: 'messages', valueKind: 'string', description: 'Mensagem de sucesso ao criar registro.' },
987
+ { path: 'messages.updateRegistryError', category: 'messages', valueKind: 'string', description: 'Mensagem de erro ao atualizar registro.' },
988
+ { path: 'messages.createRegistryError', category: 'messages', valueKind: 'string', description: 'Mensagem de erro ao criar registro.' },
989
+ { path: 'messages.confirmations', category: 'messages', valueKind: 'object', description: 'Mensagens de confirmacao.' },
990
+ { path: 'messages.confirmations.submit', category: 'messages', valueKind: 'string', description: 'Confirmacao de submit.' },
991
+ { path: 'messages.confirmations.cancel', category: 'messages', valueKind: 'string', description: 'Confirmacao de cancel.' },
992
+ { path: 'messages.confirmations.reset', category: 'messages', valueKind: 'string', description: 'Confirmacao de reset.' },
993
+ { path: 'messages.confirmations.[actionId]', category: 'messages', valueKind: 'string', description: 'Confirmacao para acao custom.' },
994
+ { path: 'messages.customActions', category: 'messages', valueKind: 'object', description: 'Mensagens por acao custom.' },
995
+ { path: 'messages.customActions.[actionId]', category: 'messages', valueKind: 'object', description: 'Mensagens da acao custom (success/error/confirmation).' },
996
+ { path: 'messages.customActions.[actionId].success', category: 'messages', valueKind: 'string', description: 'Sucesso para acao custom.' },
997
+ { path: 'messages.customActions.[actionId].error', category: 'messages', valueKind: 'string', description: 'Erro para acao custom.' },
998
+ { path: 'messages.customActions.[actionId].confirmation', category: 'messages', valueKind: 'string', description: 'Confirmacao para acao custom.' },
999
+ { path: 'messages.loading', category: 'messages', valueKind: 'object', description: 'Mensagens de loading.' },
1000
+ { path: 'messages.loading.submit', category: 'messages', valueKind: 'string', description: 'Mensagem de loading no submit.' },
1001
+ { path: 'messages.loading.cancel', category: 'messages', valueKind: 'string', description: 'Mensagem de loading no cancel.' },
1002
+ { path: 'messages.loading.reset', category: 'messages', valueKind: 'string', description: 'Mensagem de loading no reset.' },
1003
+ { path: 'messages.loading.[actionId]', category: 'messages', valueKind: 'string', description: 'Mensagem de loading para acao custom.' },
1004
+ // --- Rules & Hooks ---
1005
+ { path: 'formRules', category: 'rules', valueKind: 'array', description: 'Regras de layout/comportamento.' },
1006
+ { path: 'formRules[].id', category: 'rules', valueKind: 'string', description: 'ID da regra.' },
1007
+ { path: 'formRules[].name', category: 'rules', valueKind: 'string', description: 'Nome da regra.' },
1008
+ { path: 'formRules[].description', category: 'rules', valueKind: 'string', description: 'Descricao da regra.' },
1009
+ { path: 'formRules[].targetType', category: 'rules', valueKind: 'enum', allowedValues: ENUMS.formRuleTargetType, description: 'Tipo de alvo.' },
1010
+ { path: 'formRules[].targets', category: 'rules', valueKind: 'array', description: 'IDs dos alvos canonicos.' },
1011
+ { path: 'formRules[].targets[]', category: 'rules', valueKind: 'string', description: 'ID do alvo canonico.' },
1012
+ { path: 'formRules[].targetFields', category: 'rules', valueKind: 'array', description: 'Alias legado para campos alvo.' },
1013
+ { path: 'formRules[].targetFields[]', category: 'rules', valueKind: 'string', description: 'ID do campo alvo (alias legado).' },
1014
+ { path: 'formRules[].context', category: 'rules', valueKind: 'enum', allowedValues: ENUMS.formRuleContext, description: 'Contexto legado da regra.' },
1015
+ { path: 'formRules[].effect.condition', category: 'rules', valueKind: 'expression', description: 'Condicao da regra.' },
1016
+ { path: 'formRules[].effect.properties', category: 'rules', valueKind: 'object', description: 'Propriedades aplicadas quando true.' },
1017
+ ...generateRulePropertyCapabilities('field', RULE_PROPERTY_SCHEMA.field),
1018
+ ...generateRulePropertyCapabilities('section', RULE_PROPERTY_SCHEMA.section),
1019
+ ...generateRulePropertyCapabilities('action', RULE_PROPERTY_SCHEMA.action),
1020
+ ...generateRulePropertyCapabilities('row', RULE_PROPERTY_SCHEMA.row),
1021
+ ...generateRulePropertyCapabilities('column', RULE_PROPERTY_SCHEMA.column),
1022
+ { path: 'formRules[].effect.propertiesWhenFalse', category: 'rules', valueKind: 'object', description: 'Propriedades aplicadas quando false.' },
1023
+ ...generateRulePropertyWhenFalseCapabilities('field', RULE_PROPERTY_SCHEMA.field),
1024
+ ...generateRulePropertyWhenFalseCapabilities('section', RULE_PROPERTY_SCHEMA.section),
1025
+ ...generateRulePropertyWhenFalseCapabilities('action', RULE_PROPERTY_SCHEMA.action),
1026
+ ...generateRulePropertyWhenFalseCapabilities('row', RULE_PROPERTY_SCHEMA.row),
1027
+ ...generateRulePropertyWhenFalseCapabilities('column', RULE_PROPERTY_SCHEMA.column),
1028
+ { path: 'formRulesState', category: 'rules', valueKind: 'object', description: 'Estado bruto do visual builder.' },
1029
+ { path: 'hooks', category: 'hooks', valueKind: 'object', description: 'Declaracoes de hooks por estagio.' },
1030
+ { path: 'hooks.beforeInit', category: 'hooks', valueKind: 'array', description: 'Hooks beforeInit.' },
1031
+ { path: 'hooks.beforeInit[]', category: 'hooks', valueKind: 'array', description: 'Hooks beforeInit.' },
1032
+ { path: 'hooks.beforeInit[].id', category: 'hooks', valueKind: 'string', description: 'ID do hook.' },
1033
+ { path: 'hooks.beforeInit[].priority', category: 'hooks', valueKind: 'number', description: 'Prioridade do hook.' },
1034
+ { path: 'hooks.beforeInit[].timeoutMs', category: 'hooks', valueKind: 'number', description: 'Timeout do hook.' },
1035
+ { path: 'hooks.beforeInit[].args', category: 'hooks', valueKind: 'object', description: 'Args do hook.' },
1036
+ { path: 'hooks.afterInit', category: 'hooks', valueKind: 'array', description: 'Hooks afterInit.' },
1037
+ { path: 'hooks.afterInit[]', category: 'hooks', valueKind: 'array', description: 'Hooks afterInit.' },
1038
+ { path: 'hooks.afterInit[].id', category: 'hooks', valueKind: 'string', description: 'ID do hook executado apos a inicializacao.' },
1039
+ { path: 'hooks.afterInit[].priority', category: 'hooks', valueKind: 'number', description: 'Prioridade de execucao do hook apos a inicializacao.' },
1040
+ { path: 'hooks.afterInit[].timeoutMs', category: 'hooks', valueKind: 'number', description: 'Timeout do hook apos a inicializacao em milissegundos.' },
1041
+ { path: 'hooks.afterInit[].args', category: 'hooks', valueKind: 'object', description: 'Argumentos configurados para o hook apos a inicializacao.' },
1042
+ { path: 'hooks.beforeValidate', category: 'hooks', valueKind: 'array', description: 'Hooks beforeValidate.' },
1043
+ { path: 'hooks.beforeValidate[]', category: 'hooks', valueKind: 'array', description: 'Hooks beforeValidate.' },
1044
+ { path: 'hooks.beforeValidate[].id', category: 'hooks', valueKind: 'string', description: 'ID do hook executado antes da validacao.' },
1045
+ { path: 'hooks.beforeValidate[].priority', category: 'hooks', valueKind: 'number', description: 'Prioridade de execucao do hook antes da validacao.' },
1046
+ { path: 'hooks.beforeValidate[].timeoutMs', category: 'hooks', valueKind: 'number', description: 'Timeout do hook antes da validacao em milissegundos.' },
1047
+ { path: 'hooks.beforeValidate[].args', category: 'hooks', valueKind: 'object', description: 'Argumentos configurados para o hook antes da validacao.' },
1048
+ { path: 'hooks.afterValidate', category: 'hooks', valueKind: 'array', description: 'Hooks afterValidate.' },
1049
+ { path: 'hooks.afterValidate[]', category: 'hooks', valueKind: 'array', description: 'Hooks afterValidate.' },
1050
+ { path: 'hooks.afterValidate[].id', category: 'hooks', valueKind: 'string', description: 'ID do hook executado apos a validacao.' },
1051
+ { path: 'hooks.afterValidate[].priority', category: 'hooks', valueKind: 'number', description: 'Prioridade de execucao do hook apos a validacao.' },
1052
+ { path: 'hooks.afterValidate[].timeoutMs', category: 'hooks', valueKind: 'number', description: 'Timeout do hook apos a validacao em milissegundos.' },
1053
+ { path: 'hooks.afterValidate[].args', category: 'hooks', valueKind: 'object', description: 'Argumentos configurados para o hook apos a validacao.' },
1054
+ { path: 'hooks.beforeSubmit', category: 'hooks', valueKind: 'array', description: 'Hooks beforeSubmit.' },
1055
+ { path: 'hooks.beforeSubmit[]', category: 'hooks', valueKind: 'array', description: 'Hooks beforeSubmit.' },
1056
+ { path: 'hooks.beforeSubmit[].id', category: 'hooks', valueKind: 'string', description: 'ID do hook executado antes do submit.' },
1057
+ { path: 'hooks.beforeSubmit[].priority', category: 'hooks', valueKind: 'number', description: 'Prioridade de execucao do hook antes do submit.' },
1058
+ { path: 'hooks.beforeSubmit[].timeoutMs', category: 'hooks', valueKind: 'number', description: 'Timeout do hook antes do submit em milissegundos.' },
1059
+ { path: 'hooks.beforeSubmit[].args', category: 'hooks', valueKind: 'object', description: 'Argumentos configurados para o hook antes do submit.' },
1060
+ { path: 'hooks.afterSubmit', category: 'hooks', valueKind: 'array', description: 'Hooks afterSubmit.' },
1061
+ { path: 'hooks.afterSubmit[]', category: 'hooks', valueKind: 'array', description: 'Hooks afterSubmit.' },
1062
+ { path: 'hooks.afterSubmit[].id', category: 'hooks', valueKind: 'string', description: 'ID do hook executado apos o submit.' },
1063
+ { path: 'hooks.afterSubmit[].priority', category: 'hooks', valueKind: 'number', description: 'Prioridade de execucao do hook apos o submit.' },
1064
+ { path: 'hooks.afterSubmit[].timeoutMs', category: 'hooks', valueKind: 'number', description: 'Timeout do hook apos o submit em milissegundos.' },
1065
+ { path: 'hooks.afterSubmit[].args', category: 'hooks', valueKind: 'object', description: 'Argumentos configurados para o hook apos o submit.' },
1066
+ { path: 'hooks.onError', category: 'hooks', valueKind: 'array', description: 'Hooks onError.' },
1067
+ { path: 'hooks.onError[]', category: 'hooks', valueKind: 'array', description: 'Hooks onError.' },
1068
+ { path: 'hooks.onError[].id', category: 'hooks', valueKind: 'string', description: 'ID do hook executado em cenarios de erro.' },
1069
+ { path: 'hooks.onError[].priority', category: 'hooks', valueKind: 'number', description: 'Prioridade de execucao do hook em cenarios de erro.' },
1070
+ { path: 'hooks.onError[].timeoutMs', category: 'hooks', valueKind: 'number', description: 'Timeout do hook de erro em milissegundos.' },
1071
+ { path: 'hooks.onError[].args', category: 'hooks', valueKind: 'object', description: 'Argumentos configurados para o hook de erro.' },
1072
+ // --- Hints ---
1073
+ { path: 'hints.dataModes.create', category: 'hints', valueKind: 'string', description: 'Texto de apoio para o modo create de dados.' },
1074
+ { path: 'hints.dataModes.edit', category: 'hints', valueKind: 'string', description: 'Texto de apoio para o modo edit de dados.' },
1075
+ { path: 'hints.dataModes.view', category: 'hints', valueKind: 'string', description: 'Texto de apoio para o modo view de dados.' },
1076
+ { path: 'hints.uiModes.presentation', category: 'hints', valueKind: 'string', description: 'Texto de apoio para o modo de apresentacao da UI.' },
1077
+ { path: 'hints.uiModes.readonly', category: 'hints', valueKind: 'string', description: 'Texto de apoio para o modo somente leitura.' },
1078
+ { path: 'hints.uiModes.disabled', category: 'hints', valueKind: 'string', description: 'Texto de apoio para o modo desabilitado.' },
1079
+ { path: 'hints.uiModes.visible', category: 'hints', valueKind: 'string', description: 'Texto de apoio para a visibilidade dos elementos da UI.' },
1080
+ ],
1081
+ };
1082
+
1083
+ class ManualFormAiAdapter extends BaseAiAdapter {
1084
+ host;
1085
+ componentName = 'Manual Form';
1086
+ constructor(host) {
1087
+ super();
1088
+ this.host = host;
1089
+ }
1090
+ getCurrentConfig() {
1091
+ const current = this.host.instance?.currentConfig ?? { sections: [] };
1092
+ return this.cloneConfig(current);
1093
+ }
1094
+ getCapabilities() {
1095
+ return MANUAL_FORM_AI_CAPABILITIES.capabilities;
1096
+ }
1097
+ getRuntimeState() {
1098
+ const form = this.host.instance?.form;
1099
+ const fields = this.host.instance?.currentConfig?.fieldMetadata || [];
1100
+ return {
1101
+ hasInstance: !!this.host.instance,
1102
+ formId: this.safeFormId(),
1103
+ fieldsCount: fields.length,
1104
+ valid: form?.valid ?? false,
1105
+ dirty: form?.dirty ?? false,
1106
+ };
1107
+ }
1108
+ createSnapshot() {
1109
+ return this.getCurrentConfig();
1110
+ }
1111
+ async restoreSnapshot(snapshot) {
1112
+ if (!snapshot)
1113
+ return;
1114
+ this.applyConfig(this.cloneConfig(snapshot));
1115
+ }
1116
+ async applyPatch(patch, _intent) {
1117
+ if (!this.host.instance) {
1118
+ return { success: false, error: 'Manual form instance not ready.' };
1119
+ }
1120
+ try {
1121
+ const current = this.getCurrentConfig();
1122
+ const next = this.smartMergeFormConfig(current, patch);
1123
+ this.applyConfig(next);
1124
+ return { success: true };
1125
+ }
1126
+ catch (error) {
1127
+ const message = error instanceof Error ? error.message : 'Unknown error applying patch';
1128
+ return { success: false, error: message };
1129
+ }
1130
+ }
1131
+ applyConfig(config) {
1132
+ const apply = this.host.applyConfigFromAdapter;
1133
+ if (typeof apply === 'function') {
1134
+ apply.call(this.host, config);
1135
+ return;
1136
+ }
1137
+ if (this.host.instance) {
1138
+ this.host.instance.replaceConfig(config);
1139
+ this.host.metadataChange.emit(this.host.instance.currentConfig);
1140
+ }
1141
+ }
1142
+ smartMergeFormConfig(base, patch) {
1143
+ const result = deepMerge(base, patch);
1144
+ if (patch.fieldMetadata && Array.isArray(patch.fieldMetadata)) {
1145
+ const originalFields = base.fieldMetadata || [];
1146
+ const patchFields = patch.fieldMetadata.filter((field) => field &&
1147
+ typeof field.name === 'string' &&
1148
+ field.name.trim().length > 0);
1149
+ const mergedFields = originalFields.map((orig) => {
1150
+ const match = patchFields.find((candidate) => candidate.name === orig.name);
1151
+ return match ? deepMerge(orig, match) : orig;
1152
+ });
1153
+ patchFields.forEach((incoming) => {
1154
+ if (!originalFields.find((orig) => orig.name === incoming.name)) {
1155
+ mergedFields.push(incoming);
1156
+ }
1157
+ });
1158
+ result.fieldMetadata = mergedFields;
1159
+ }
1160
+ return result;
1161
+ }
1162
+ cloneConfig(config) {
1163
+ try {
1164
+ return structuredClone(config);
1165
+ }
1166
+ catch {
1167
+ return JSON.parse(JSON.stringify(config));
1168
+ }
1169
+ }
1170
+ safeFormId() {
1171
+ try {
1172
+ const idSignal = this.host.formId;
1173
+ return typeof idSignal === 'function' ? idSignal() : undefined;
1174
+ }
1175
+ catch {
1176
+ return undefined;
1177
+ }
1178
+ }
1179
+ }
503
1180
 
504
1181
  const MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE = new InjectionToken('MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE');
505
1182
  const MANUAL_FORM_CONSTRUCTOR_TO_CONTROL_TYPE = new InjectionToken('MANUAL_FORM_CONSTRUCTOR_TO_CONTROL_TYPE');
@@ -544,7 +1221,7 @@ class ManualFieldMetadataBridgeService {
544
1221
  }
545
1222
  return;
546
1223
  }
547
- const controlType = seed?.controlType;
1224
+ const controlType = resolveControlTypeAlias(seed?.controlType, FieldControlType.INPUT);
548
1225
  const title = this.buildPanelTitle(fieldName, seed);
549
1226
  const panelId = this.buildPanelId(instance, fieldName);
550
1227
  let ref;
@@ -645,8 +1322,9 @@ class ManualFieldMetadataBridgeService {
645
1322
  if (!controlType) {
646
1323
  return '';
647
1324
  }
648
- return String(controlType)
649
- .split(/[-_]/)
1325
+ return normalizeControlTypeKey(controlType)
1326
+ .toLowerCase()
1327
+ .split('_')
650
1328
  .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase())
651
1329
  .join(' ');
652
1330
  }
@@ -679,28 +1357,292 @@ class ManualFieldMetadataBridgeService {
679
1357
  CHIP_INPUT: 'sell',
680
1358
  CPF_CNPJ_INPUT: 'fingerprint',
681
1359
  };
682
- const key = String(controlType).toUpperCase();
1360
+ const key = normalizeControlTypeKey(controlType);
683
1361
  return map[key] || 'tune';
684
1362
  }
685
1363
  bindComponent(instance, fieldName, componentInstance) {
686
1364
  instance.bindComponent(fieldName, componentInstance);
687
1365
  }
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' });
1366
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFieldMetadataBridgeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1367
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFieldMetadataBridgeService, providedIn: 'root' });
1368
+ }
1369
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFieldMetadataBridgeService, decorators: [{
1370
+ type: Injectable,
1371
+ args: [{ providedIn: 'root' }]
1372
+ }] });
1373
+
1374
+ class ManualFieldKeyService {
1375
+ resolveFieldName(input, instance) {
1376
+ if (!instance) {
1377
+ return { status: 'missing' };
1378
+ }
1379
+ const fieldNames = (instance.currentConfig.fieldMetadata || [])
1380
+ .map((meta) => meta.name)
1381
+ .filter((name) => !!name);
1382
+ const raw = Array.isArray(input) ? input.join('.') : input;
1383
+ const normalized = String(raw || '').trim();
1384
+ if (!normalized) {
1385
+ return { status: 'missing' };
1386
+ }
1387
+ if (fieldNames.includes(normalized)) {
1388
+ return { status: 'ok', key: normalized };
1389
+ }
1390
+ const lastSegment = Array.isArray(input)
1391
+ ? input[input.length - 1]
1392
+ : normalized.split('.').pop();
1393
+ if (!lastSegment) {
1394
+ return { status: 'missing' };
1395
+ }
1396
+ const matches = fieldNames.filter((name) => name === lastSegment || name.endsWith(`.${lastSegment}`));
1397
+ if (matches.length === 1) {
1398
+ return { status: 'ok', key: matches[0] };
1399
+ }
1400
+ if (matches.length > 1) {
1401
+ return { status: 'ambiguous', candidates: matches };
1402
+ }
1403
+ return { status: 'missing' };
1404
+ }
1405
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFieldKeyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1406
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFieldKeyService, providedIn: 'root' });
690
1407
  }
691
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFieldMetadataBridgeService, decorators: [{
1408
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFieldKeyService, decorators: [{
692
1409
  type: Injectable,
693
1410
  args: [{ providedIn: 'root' }]
694
1411
  }] });
695
1412
 
1413
+ class ManualFieldToolbarComponent {
1414
+ metadata = null;
1415
+ toggleRequired = new EventEmitter();
1416
+ toggleReadonly = new EventEmitter();
1417
+ toggleHidden = new EventEmitter();
1418
+ toggleDisabled = new EventEmitter();
1419
+ openEditor = new EventEmitter();
1420
+ requestClose = new EventEmitter();
1421
+ firstAction;
1422
+ focusFirstAction() {
1423
+ this.firstAction?.nativeElement?.focus();
1424
+ }
1425
+ get isRequired() {
1426
+ const meta = this.metadata;
1427
+ return !!meta?.required || !!meta?.validators?.required;
1428
+ }
1429
+ get isReadOnly() {
1430
+ return !!this.metadata?.readOnly;
1431
+ }
1432
+ onKeydown(event) {
1433
+ if (event.key === 'Escape' || event.key === 'Esc') {
1434
+ event.stopPropagation();
1435
+ event.preventDefault();
1436
+ this.requestClose.emit();
1437
+ }
1438
+ }
1439
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFieldToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1440
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: ManualFieldToolbarComponent, isStandalone: true, selector: "praxis-manual-field-toolbar", inputs: { metadata: "metadata" }, outputs: { toggleRequired: "toggleRequired", toggleReadonly: "toggleReadonly", toggleHidden: "toggleHidden", toggleDisabled: "toggleDisabled", openEditor: "openEditor", requestClose: "requestClose" }, host: { listeners: { "keydown": "onKeydown($event)" } }, viewQueries: [{ propertyName: "firstAction", first: true, predicate: ["firstAction"], descendants: true, static: true }], ngImport: i0, template: `
1441
+ <div
1442
+ class="pdx-manual-field-toolbar"
1443
+ role="toolbar"
1444
+ aria-label="Acoes do campo"
1445
+ >
1446
+ <button
1447
+ #firstAction
1448
+ type="button"
1449
+ class="toolbar-btn"
1450
+ [attr.aria-pressed]="isRequired ? 'true' : 'false'"
1451
+ aria-label="Alternar obrigatorio"
1452
+ title="Alternar obrigatorio"
1453
+ (click)="toggleRequired.emit()"
1454
+ >
1455
+ <span class="material-symbols-outlined toolbar-icon" [class.toolbar-icon--filled]="isRequired">
1456
+ star
1457
+ </span>
1458
+ </button>
1459
+ <button
1460
+ type="button"
1461
+ class="toolbar-btn"
1462
+ [attr.aria-pressed]="isReadOnly ? 'true' : 'false'"
1463
+ aria-label="Alternar somente leitura"
1464
+ title="Alternar somente leitura"
1465
+ (click)="toggleReadonly.emit()"
1466
+ >
1467
+ <span class="material-symbols-outlined">
1468
+ {{ isReadOnly ? 'lock' : 'lock_open' }}
1469
+ </span>
1470
+ </button>
1471
+ <button
1472
+ type="button"
1473
+ class="toolbar-btn"
1474
+ [attr.aria-pressed]="metadata?.hidden ? 'true' : 'false'"
1475
+ aria-label="Alternar visibilidade"
1476
+ title="Alternar visibilidade"
1477
+ (click)="toggleHidden.emit()"
1478
+ >
1479
+ <span class="material-symbols-outlined">
1480
+ {{ metadata?.hidden ? 'visibility_off' : 'visibility' }}
1481
+ </span>
1482
+ </button>
1483
+ <button
1484
+ type="button"
1485
+ class="toolbar-btn"
1486
+ [attr.aria-pressed]="metadata?.disabled ? 'true' : 'false'"
1487
+ aria-label="Alternar desabilitado"
1488
+ title="Alternar desabilitado"
1489
+ (click)="toggleDisabled.emit()"
1490
+ >
1491
+ <span class="material-symbols-outlined">
1492
+ {{ metadata?.disabled ? 'toggle_on' : 'toggle_off' }}
1493
+ </span>
1494
+ </button>
1495
+ <button
1496
+ type="button"
1497
+ class="toolbar-btn toolbar-btn--accent"
1498
+ aria-label="Configurar campo"
1499
+ title="Configurar campo"
1500
+ (click)="openEditor.emit()"
1501
+ >
1502
+ <span class="material-symbols-outlined">tune</span>
1503
+ </button>
1504
+ <button
1505
+ type="button"
1506
+ class="toolbar-btn toolbar-btn--ghost"
1507
+ aria-label="Fechar"
1508
+ title="Fechar"
1509
+ (click)="requestClose.emit()"
1510
+ >
1511
+ <span class="material-symbols-outlined">close</span>
1512
+ </button>
1513
+ </div>
1514
+ `, isInline: true, styles: [":host{display:block;pointer-events:auto;font-family:inherit}.pdx-manual-field-toolbar{display:inline-flex;align-items:center;gap:6px;padding:6px;border-radius:12px;background:var(--md-sys-color-surface-container-low);border:1px solid var(--md-sys-color-outline-variant);box-shadow:var(--md-sys-elevation-level3);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}.toolbar-btn{appearance:none;border:0;background:transparent;color:var(--md-sys-color-on-surface);display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:10px;cursor:pointer;transition:background-color .12s ease,transform .12s ease}.toolbar-btn:hover{background:var(--md-sys-color-primary-container)}.toolbar-btn:active{transform:scale(.97)}.toolbar-btn:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.toolbar-btn--accent{background:var(--md-sys-color-primary-container)}.toolbar-btn--ghost{opacity:.7}.material-symbols-outlined{font-size:18px;line-height:18px}.toolbar-icon{font-variation-settings:\"FILL\" 0,\"wght\" 400,\"GRAD\" 0,\"opsz\" 20}.toolbar-icon--filled{font-variation-settings:\"FILL\" 1,\"wght\" 600,\"GRAD\" 0,\"opsz\" 20}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
1515
+ }
1516
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFieldToolbarComponent, decorators: [{
1517
+ type: Component,
1518
+ args: [{ selector: 'praxis-manual-field-toolbar', standalone: true, imports: [CommonModule], template: `
1519
+ <div
1520
+ class="pdx-manual-field-toolbar"
1521
+ role="toolbar"
1522
+ aria-label="Acoes do campo"
1523
+ >
1524
+ <button
1525
+ #firstAction
1526
+ type="button"
1527
+ class="toolbar-btn"
1528
+ [attr.aria-pressed]="isRequired ? 'true' : 'false'"
1529
+ aria-label="Alternar obrigatorio"
1530
+ title="Alternar obrigatorio"
1531
+ (click)="toggleRequired.emit()"
1532
+ >
1533
+ <span class="material-symbols-outlined toolbar-icon" [class.toolbar-icon--filled]="isRequired">
1534
+ star
1535
+ </span>
1536
+ </button>
1537
+ <button
1538
+ type="button"
1539
+ class="toolbar-btn"
1540
+ [attr.aria-pressed]="isReadOnly ? 'true' : 'false'"
1541
+ aria-label="Alternar somente leitura"
1542
+ title="Alternar somente leitura"
1543
+ (click)="toggleReadonly.emit()"
1544
+ >
1545
+ <span class="material-symbols-outlined">
1546
+ {{ isReadOnly ? 'lock' : 'lock_open' }}
1547
+ </span>
1548
+ </button>
1549
+ <button
1550
+ type="button"
1551
+ class="toolbar-btn"
1552
+ [attr.aria-pressed]="metadata?.hidden ? 'true' : 'false'"
1553
+ aria-label="Alternar visibilidade"
1554
+ title="Alternar visibilidade"
1555
+ (click)="toggleHidden.emit()"
1556
+ >
1557
+ <span class="material-symbols-outlined">
1558
+ {{ metadata?.hidden ? 'visibility_off' : 'visibility' }}
1559
+ </span>
1560
+ </button>
1561
+ <button
1562
+ type="button"
1563
+ class="toolbar-btn"
1564
+ [attr.aria-pressed]="metadata?.disabled ? 'true' : 'false'"
1565
+ aria-label="Alternar desabilitado"
1566
+ title="Alternar desabilitado"
1567
+ (click)="toggleDisabled.emit()"
1568
+ >
1569
+ <span class="material-symbols-outlined">
1570
+ {{ metadata?.disabled ? 'toggle_on' : 'toggle_off' }}
1571
+ </span>
1572
+ </button>
1573
+ <button
1574
+ type="button"
1575
+ class="toolbar-btn toolbar-btn--accent"
1576
+ aria-label="Configurar campo"
1577
+ title="Configurar campo"
1578
+ (click)="openEditor.emit()"
1579
+ >
1580
+ <span class="material-symbols-outlined">tune</span>
1581
+ </button>
1582
+ <button
1583
+ type="button"
1584
+ class="toolbar-btn toolbar-btn--ghost"
1585
+ aria-label="Fechar"
1586
+ title="Fechar"
1587
+ (click)="requestClose.emit()"
1588
+ >
1589
+ <span class="material-symbols-outlined">close</span>
1590
+ </button>
1591
+ </div>
1592
+ `, styles: [":host{display:block;pointer-events:auto;font-family:inherit}.pdx-manual-field-toolbar{display:inline-flex;align-items:center;gap:6px;padding:6px;border-radius:12px;background:var(--md-sys-color-surface-container-low);border:1px solid var(--md-sys-color-outline-variant);box-shadow:var(--md-sys-elevation-level3);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}.toolbar-btn{appearance:none;border:0;background:transparent;color:var(--md-sys-color-on-surface);display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:10px;cursor:pointer;transition:background-color .12s ease,transform .12s ease}.toolbar-btn:hover{background:var(--md-sys-color-primary-container)}.toolbar-btn:active{transform:scale(.97)}.toolbar-btn:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.toolbar-btn--accent{background:var(--md-sys-color-primary-container)}.toolbar-btn--ghost{opacity:.7}.material-symbols-outlined{font-size:18px;line-height:18px}.toolbar-icon{font-variation-settings:\"FILL\" 0,\"wght\" 400,\"GRAD\" 0,\"opsz\" 20}.toolbar-icon--filled{font-variation-settings:\"FILL\" 1,\"wght\" 600,\"GRAD\" 0,\"opsz\" 20}\n"] }]
1593
+ }], propDecorators: { metadata: [{
1594
+ type: Input
1595
+ }], toggleRequired: [{
1596
+ type: Output
1597
+ }], toggleReadonly: [{
1598
+ type: Output
1599
+ }], toggleHidden: [{
1600
+ type: Output
1601
+ }], toggleDisabled: [{
1602
+ type: Output
1603
+ }], openEditor: [{
1604
+ type: Output
1605
+ }], requestClose: [{
1606
+ type: Output
1607
+ }], firstAction: [{
1608
+ type: ViewChild,
1609
+ args: ['firstAction', { static: true }]
1610
+ }], onKeydown: [{
1611
+ type: HostListener,
1612
+ args: ['keydown', ['$event']]
1613
+ }] } });
1614
+
696
1615
  class ManualFormComponent {
697
1616
  instanceFactory = inject(ManualFormInstanceFactory);
698
1617
  cdr = inject(ChangeDetectorRef);
699
1618
  destroyRef = inject(DestroyRef);
700
1619
  metadataBridge = inject(ManualFieldMetadataBridgeService);
701
1620
  settingsPanel = inject(SettingsPanelService);
702
- selectorToControlType = inject(MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE, { optional: true }) ?? DEFAULT_SELECTOR_TO_CONTROL_TYPE;
1621
+ hooksRegistry = inject(FormHooksRegistry, { optional: true });
1622
+ componentKeys = inject(ComponentKeyService);
1623
+ overlay = inject(Overlay);
1624
+ fieldKeyService = inject(ManualFieldKeyService);
1625
+ platformId = inject(PLATFORM_ID);
1626
+ route = (() => { try {
1627
+ return inject(ActivatedRoute);
1628
+ }
1629
+ catch {
1630
+ return undefined;
1631
+ } })();
1632
+ warnedMissingId = false;
1633
+ disableSelectorDefaults = inject(FIELD_SELECTOR_REGISTRY_DISABLE_DEFAULTS, { optional: true }) ?? false;
1634
+ selectorToControlType = inject(MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE, { optional: true }) ??
1635
+ (this.disableSelectorDefaults ? {} : DEFAULT_SELECTOR_TO_CONTROL_TYPE);
703
1636
  constructorToControlType = inject(MANUAL_FORM_CONSTRUCTOR_TO_CONTROL_TYPE, { optional: true }) ?? DEFAULT_CONSTRUCTOR_TO_CONTROL_TYPE;
1637
+ selectorRegistry = inject(FieldSelectorRegistry, { optional: true });
1638
+ toolbarOverlayRef;
1639
+ toolbarComponentRef;
1640
+ toolbarFieldKey;
1641
+ toolbarAnchor;
1642
+ toolbarSubscriptions = [];
1643
+ toolbarActionSubscriptions = [];
1644
+ metadataSubscription;
1645
+ outsideClickSubscription;
704
1646
  formId = input.required(...(ngDevMode ? [{ debugName: "formId" }] : []));
705
1647
  formTitle = input(...(ngDevMode ? [undefined, { debugName: "formTitle" }] : []));
706
1648
  formDescription = input(...(ngDevMode ? [undefined, { debugName: "formDescription" }] : []));
@@ -708,8 +1650,9 @@ class ManualFormComponent {
708
1650
  showHeader = input(true, ...(ngDevMode ? [{ debugName: "showHeader" }] : []));
709
1651
  showActions = input(true, ...(ngDevMode ? [{ debugName: "showActions" }] : []));
710
1652
  enableAutoSave = input(true, ...(ngDevMode ? [{ debugName: "enableAutoSave" }] : []));
1653
+ componentInstanceId = input(...(ngDevMode ? [undefined, { debugName: "componentInstanceId" }] : []));
711
1654
  // Allows editor affordances (e.g., opening metadata editor on interactions)
712
- editModeEnabled = input(false, ...(ngDevMode ? [{ debugName: "editModeEnabled" }] : []));
1655
+ enableCustomization = input(false, ...(ngDevMode ? [{ debugName: "enableCustomization" }] : []));
713
1656
  persistenceOptions = input(...(ngDevMode ? [undefined, { debugName: "persistenceOptions" }] : []));
714
1657
  // When true, uses FormControlName.path (joined by '.') as FieldMetadata.name.
715
1658
  // Requires nested path support (already present in DynamicFormService).
@@ -725,6 +1668,7 @@ class ManualFormComponent {
725
1668
  hostFormGroupDirective = inject(FormGroupDirective, { self: true, optional: true });
726
1669
  instance;
727
1670
  resolvedActions;
1671
+ aiAdapter = new ManualFormAiAdapter(this);
728
1672
  formGroup = new FormGroup({});
729
1673
  registeredDirectives = [];
730
1674
  constructor() {
@@ -747,17 +1691,35 @@ class ManualFormComponent {
747
1691
  if (isDevMode())
748
1692
  console.debug('[ManualForm] No host FormGroupDirective detected at construction (dynamic mode)');
749
1693
  }
1694
+ effect(() => {
1695
+ const enabled = this.enableCustomization();
1696
+ if (!enabled) {
1697
+ this.closeToolbar();
1698
+ return;
1699
+ }
1700
+ if (this.instance && this.formControls) {
1701
+ const fields = this.collectFields();
1702
+ if (fields.length) {
1703
+ this.registerToolbarBindings(fields);
1704
+ }
1705
+ }
1706
+ });
750
1707
  }
751
1708
  ngAfterContentInit() {
1709
+ this.syncHostFormGroupReference();
752
1710
  if (this.formControls) {
753
1711
  this.formControls.changes
754
1712
  .pipe(takeUntilDestroyed(this.destroyRef))
755
- .subscribe(() => this.initialize(true));
1713
+ .subscribe(() => { void this.initialize(true); });
756
1714
  }
757
- queueMicrotask(() => this.initialize(false));
1715
+ queueMicrotask(() => { void this.initialize(false); });
758
1716
  }
759
1717
  ngOnDestroy() {
760
- // Output signals do not need manual completion
1718
+ this.toolbarSubscriptions.forEach((sub) => sub.unsubscribe());
1719
+ this.toolbarActionSubscriptions.forEach((sub) => sub.unsubscribe());
1720
+ this.metadataSubscription?.unsubscribe();
1721
+ this.toolbarOverlayRef?.dispose();
1722
+ this.outsideClickSubscription?.unsubscribe();
761
1723
  }
762
1724
  // =============================
763
1725
  // ControlContainer interface
@@ -774,6 +1736,7 @@ class ManualFormComponent {
774
1736
  return [];
775
1737
  }
776
1738
  addControl(dir) {
1739
+ this.syncHostFormGroupReference();
777
1740
  const name = typeof dir.name === 'string' ? dir.name : String(dir.name);
778
1741
  const pathSegs = Array.isArray(dir.path) && dir.path.length
779
1742
  ? dir.path
@@ -810,13 +1773,39 @@ class ManualFormComponent {
810
1773
  dir.control = ctrl;
811
1774
  return ctrl;
812
1775
  }
1776
+ addFormGroup(dir) {
1777
+ this.syncHostFormGroupReference();
1778
+ if (this.hostFormGroupDirective && typeof this.hostFormGroupDirective.addFormGroup === 'function') {
1779
+ this.hostFormGroupDirective.addFormGroup(dir);
1780
+ return;
1781
+ }
1782
+ const path = this.getDirPath(dir);
1783
+ this.ensureGroupPath(path);
1784
+ }
1785
+ removeFormGroup(dir) {
1786
+ this.syncHostFormGroupReference();
1787
+ if (this.hostFormGroupDirective && typeof this.hostFormGroupDirective.removeFormGroup === 'function') {
1788
+ this.hostFormGroupDirective.removeFormGroup(dir);
1789
+ }
1790
+ }
1791
+ getFormGroup(dir) {
1792
+ this.syncHostFormGroupReference();
1793
+ if (this.hostFormGroupDirective && typeof this.hostFormGroupDirective.getFormGroup === 'function') {
1794
+ return this.hostFormGroupDirective.getFormGroup(dir);
1795
+ }
1796
+ const path = this.getDirPath(dir);
1797
+ const control = this.formGroup.get(path);
1798
+ return control instanceof FormGroup ? control : this.ensureGroupPath(path);
1799
+ }
813
1800
  removeControl(dir) {
1801
+ this.syncHostFormGroupReference();
814
1802
  this.hostFormGroupDirective?.removeControl(dir);
815
1803
  const idx = this.registeredDirectives.indexOf(dir);
816
1804
  if (idx >= 0)
817
1805
  this.registeredDirectives.splice(idx, 1);
818
1806
  }
819
1807
  getControl(dir) {
1808
+ this.syncHostFormGroupReference();
820
1809
  return this.formGroup.get(dir.path);
821
1810
  }
822
1811
  updateModel(dir, value) {
@@ -842,15 +1831,20 @@ class ManualFormComponent {
842
1831
  this.metadataChange.emit(this.instance.currentConfig);
843
1832
  }
844
1833
  }
845
- handleSubmit() {
1834
+ async handleSubmit() {
846
1835
  if (!this.instance) {
847
1836
  return;
848
1837
  }
849
1838
  const value = this.formGroup.getRawValue();
1839
+ const beforeSubmitOk = await this.runHooks('beforeSubmit', { value });
1840
+ if (!beforeSubmitOk) {
1841
+ return;
1842
+ }
850
1843
  if (this.enableAutoSave()) {
851
1844
  this.instance.saveDraft(value);
852
1845
  }
853
1846
  this.submitted.emit({ value, instance: this.instance });
1847
+ await this.runHooks('afterSubmit', { value });
854
1848
  }
855
1849
  handleSave() {
856
1850
  if (!this.instance) {
@@ -862,6 +1856,10 @@ class ManualFormComponent {
862
1856
  this.saved.emit(this.instance);
863
1857
  this.metadataChange.emit(this.instance.currentConfig);
864
1858
  }
1859
+ /** True when host applied [formGroup] directly on <praxis-manual-form>. */
1860
+ isHostTyped() {
1861
+ return !!this.hostFormGroupDirective?.form;
1862
+ }
865
1863
  handleReset() {
866
1864
  if (!this.instance) {
867
1865
  return;
@@ -870,12 +1868,39 @@ class ManualFormComponent {
870
1868
  this.hostFormGroupDirective?.resetForm(this.instance.form.getRawValue());
871
1869
  this.resetEvent.emit(this.instance);
872
1870
  }
1871
+ async runHooks(stage, extras, configOverride) {
1872
+ const registry = this.hooksRegistry;
1873
+ const config = configOverride ?? this.instance?.currentConfig;
1874
+ if (!registry || !config?.hooks) {
1875
+ return true;
1876
+ }
1877
+ const declarations = config.hooks[stage];
1878
+ if (!declarations || declarations.length === 0) {
1879
+ return true;
1880
+ }
1881
+ try {
1882
+ const result = await registry.run(stage, { formGroup: this.formGroup, formConfig: config, extras }, declarations);
1883
+ if (result.stopped && stage !== 'onError' && config.hooks.onError?.length) {
1884
+ await registry.run('onError', { formGroup: this.formGroup, formConfig: config, extras: { ...extras, stopped: result.stopped } }, config.hooks.onError);
1885
+ }
1886
+ return !result.stopped;
1887
+ }
1888
+ catch {
1889
+ if (stage !== 'onError' && config.hooks.onError?.length) {
1890
+ try {
1891
+ await registry.run('onError', { formGroup: this.formGroup, formConfig: config, extras: { ...extras, error: 'hook-exception' } }, config.hooks.onError);
1892
+ }
1893
+ catch { }
1894
+ }
1895
+ return false;
1896
+ }
1897
+ }
873
1898
  /**
874
1899
  * Attempts to open the field metadata editor respecting the component's
875
- * editModeEnabled input. No-ops when disabled or without an instance.
1900
+ * enableCustomization input. No-ops when disabled or without an instance.
876
1901
  */
877
1902
  tryOpenFieldEditor(fieldName) {
878
- if (!this.editModeEnabled()) {
1903
+ if (!this.enableCustomization()) {
879
1904
  return;
880
1905
  }
881
1906
  const instance = this.instance;
@@ -889,7 +1914,7 @@ class ManualFormComponent {
889
1914
  }
890
1915
  /** Opens a simple form-level editor listing the fields and their visibility. */
891
1916
  async openFormEditor() {
892
- if (!this.editModeEnabled() || !this.instance)
1917
+ if (!this.enableCustomization() || !this.instance)
893
1918
  return;
894
1919
  let ManualFormConfigEditorComponent;
895
1920
  try {
@@ -900,8 +1925,9 @@ class ManualFormComponent {
900
1925
  return;
901
1926
  }
902
1927
  const formId = this.formId();
1928
+ const keyId = this.componentKeyId();
903
1929
  this.settingsPanel.open({
904
- id: formId ? `manual-form-editor.${formId}` : 'manual-form-editor',
1930
+ id: keyId ? `manual-form-editor.${keyId}` : (formId ? `manual-form-editor.${formId}` : 'manual-form-editor'),
905
1931
  title: this.formTitle() || 'Editor do Formulário',
906
1932
  titleIcon: 'tune',
907
1933
  content: {
@@ -910,7 +1936,17 @@ class ManualFormComponent {
910
1936
  },
911
1937
  });
912
1938
  }
913
- initialize(fromChanges) {
1939
+ applyConfigFromAdapter(config) {
1940
+ if (!this.instance) {
1941
+ return;
1942
+ }
1943
+ this.instance.replaceConfig(config);
1944
+ this.resolvedActions = this.normalizeActions(config.actions ?? null);
1945
+ this.metadataChange.emit(this.instance.currentConfig);
1946
+ this.cdr.markForCheck();
1947
+ }
1948
+ async initialize(fromChanges) {
1949
+ this.syncHostFormGroupReference();
914
1950
  if (!this.formId()) {
915
1951
  return;
916
1952
  }
@@ -921,11 +1957,22 @@ class ManualFormComponent {
921
1957
  return;
922
1958
  }
923
1959
  const seed = this.createSeedFromFields(fields);
1960
+ const beforeInitOk = await this.runHooks('beforeInit', { seed }, seed.config);
1961
+ if (!beforeInitOk) {
1962
+ return;
1963
+ }
1964
+ const persistence = { ...(this.persistenceOptions() ?? {}) };
1965
+ const keyId = this.componentKeyId();
1966
+ if (keyId) {
1967
+ persistence.storageKey = `manual-form:${keyId}`;
1968
+ }
924
1969
  // Create runtime instance and let it populate our existing FormGroup
925
- this.instance = this.instanceFactory.create(seed, this.persistenceOptions() ?? {}, this.formGroup);
1970
+ this.instance = this.instanceFactory.create(seed, persistence, this.formGroup);
926
1971
  if (isDevMode())
927
1972
  console.debug('[ManualForm] instance created with seed; current controls', Object.keys(this.formGroup.controls || {}));
928
1973
  this.applyInstanceToTemplate(fields);
1974
+ this.registerToolbarBindings(fields);
1975
+ this.attachMetadataSync();
929
1976
  // Auto-save value changes with debounce
930
1977
  if (this.enableAutoSave() && this.instance) {
931
1978
  this.instance.form.valueChanges
@@ -937,6 +1984,7 @@ class ManualFormComponent {
937
1984
  catch { }
938
1985
  });
939
1986
  }
1987
+ await this.runHooks('afterInit', { seed });
940
1988
  this.cdr.markForCheck();
941
1989
  }
942
1990
  collectFields() {
@@ -960,10 +2008,30 @@ class ManualFormComponent {
960
2008
  control: dir.control,
961
2009
  component,
962
2010
  selector: this.resolveSelector(component, dir),
2011
+ element: this.resolveHostElement(component, dir),
963
2012
  });
964
2013
  }
965
2014
  return result;
966
2015
  }
2016
+ componentKeyId() {
2017
+ const key = this.componentKeys.buildComponentId({
2018
+ componentType: 'praxis-manual-form',
2019
+ componentId: this.formId(),
2020
+ instanceKey: this.componentInstanceId(),
2021
+ componentRef: this,
2022
+ route: this.route,
2023
+ requireComponentId: true,
2024
+ });
2025
+ if (!key)
2026
+ this.warnMissingId();
2027
+ return key;
2028
+ }
2029
+ warnMissingId() {
2030
+ if (this.warnedMissingId)
2031
+ return;
2032
+ this.warnedMissingId = true;
2033
+ console.warn('[ManualForm] formId is required for config persistence.');
2034
+ }
967
2035
  resolveValueAccessor(dir) {
968
2036
  const accessor = dir.valueAccessor;
969
2037
  if (!accessor) {
@@ -986,6 +2054,10 @@ class ManualFormComponent {
986
2054
  ?? dir?._elementRef?.nativeElement;
987
2055
  return element?.tagName?.toLowerCase();
988
2056
  }
2057
+ resolveHostElement(component, dir) {
2058
+ return component?.elementRef?.nativeElement
2059
+ ?? dir?._elementRef?.nativeElement;
2060
+ }
989
2061
  createSeedFromFields(fields) {
990
2062
  const metadataList = fields.map((field) => this.buildMetadataForField(field));
991
2063
  const actions = this.normalizeActions(this.actions() ?? null);
@@ -1038,6 +2110,234 @@ class ManualFormComponent {
1038
2110
  }
1039
2111
  }
1040
2112
  }
2113
+ registerToolbarBindings(fields) {
2114
+ this.toolbarSubscriptions.forEach((sub) => sub.unsubscribe());
2115
+ this.toolbarSubscriptions = [];
2116
+ if (!this.enableCustomization()) {
2117
+ return;
2118
+ }
2119
+ for (const field of fields) {
2120
+ const host = field.element;
2121
+ if (!host) {
2122
+ continue;
2123
+ }
2124
+ const clickSub = fromEvent(host, 'click')
2125
+ .pipe(takeUntilDestroyed(this.destroyRef))
2126
+ .subscribe((event) => {
2127
+ this.openToolbarForField(field, false);
2128
+ });
2129
+ const keySub = fromEvent(host, 'keydown')
2130
+ .pipe(takeUntilDestroyed(this.destroyRef))
2131
+ .subscribe((event) => {
2132
+ if (!this.isToolbarShortcut(event)) {
2133
+ return;
2134
+ }
2135
+ event.preventDefault();
2136
+ event.stopPropagation();
2137
+ this.openToolbarForField(field, true);
2138
+ });
2139
+ this.toolbarSubscriptions.push(clickSub, keySub);
2140
+ }
2141
+ }
2142
+ isToolbarShortcut(event) {
2143
+ return event.key === 'F2' || (event.altKey && event.key === 'F10');
2144
+ }
2145
+ openToolbarForField(field, openedByKeyboard) {
2146
+ if (!this.enableCustomization()) {
2147
+ return;
2148
+ }
2149
+ const instance = this.instance;
2150
+ if (!instance || !field.element) {
2151
+ return;
2152
+ }
2153
+ const resolution = this.fieldKeyService.resolveFieldName(field.name, instance);
2154
+ if (resolution.status !== 'ok') {
2155
+ return;
2156
+ }
2157
+ const metadata = instance.getFieldMetadata(resolution.key);
2158
+ if (!metadata) {
2159
+ return;
2160
+ }
2161
+ if (!this.toolbarOverlayRef) {
2162
+ this.createToolbarOverlay(field.element);
2163
+ }
2164
+ else if (this.toolbarAnchor !== field.element) {
2165
+ this.updateToolbarAnchor(field.element);
2166
+ }
2167
+ this.toolbarFieldKey = resolution.key;
2168
+ this.toolbarAnchor = field.element;
2169
+ this.updateToolbarMetadata(metadata, openedByKeyboard);
2170
+ this.toolbarOverlayRef?.updatePosition();
2171
+ }
2172
+ createToolbarOverlay(anchor) {
2173
+ const positionStrategy = this.overlay
2174
+ .position()
2175
+ .flexibleConnectedTo(anchor)
2176
+ .withPositions([
2177
+ {
2178
+ originX: 'center',
2179
+ originY: 'top',
2180
+ overlayX: 'center',
2181
+ overlayY: 'bottom',
2182
+ offsetY: -10,
2183
+ },
2184
+ {
2185
+ originX: 'center',
2186
+ originY: 'bottom',
2187
+ overlayX: 'center',
2188
+ overlayY: 'top',
2189
+ offsetY: 10,
2190
+ },
2191
+ ])
2192
+ .withPush(true)
2193
+ .withFlexibleDimensions(false)
2194
+ .withViewportMargin(8);
2195
+ this.toolbarOverlayRef = this.overlay.create({
2196
+ positionStrategy,
2197
+ scrollStrategy: this.overlay.scrollStrategies.reposition(),
2198
+ panelClass: 'pdx-manual-toolbar-panel',
2199
+ });
2200
+ this.toolbarOverlayRef.overlayElement.style.setProperty('z-index', 'var(--pdx-manual-toolbar-z, 2001)');
2201
+ this.toolbarOverlayRef
2202
+ .detachments()
2203
+ .pipe(takeUntilDestroyed(this.destroyRef))
2204
+ .subscribe(() => this.resetToolbarState());
2205
+ const portal = new ComponentPortal(ManualFieldToolbarComponent);
2206
+ this.toolbarComponentRef = this.toolbarOverlayRef.attach(portal);
2207
+ this.bindToolbarActions();
2208
+ this.attachOutsideClickHandler();
2209
+ }
2210
+ updateToolbarAnchor(anchor) {
2211
+ if (!this.toolbarOverlayRef) {
2212
+ return;
2213
+ }
2214
+ const positionStrategy = this.overlay
2215
+ .position()
2216
+ .flexibleConnectedTo(anchor)
2217
+ .withPositions([
2218
+ {
2219
+ originX: 'center',
2220
+ originY: 'top',
2221
+ overlayX: 'center',
2222
+ overlayY: 'bottom',
2223
+ offsetY: -10,
2224
+ },
2225
+ {
2226
+ originX: 'center',
2227
+ originY: 'bottom',
2228
+ overlayX: 'center',
2229
+ overlayY: 'top',
2230
+ offsetY: 10,
2231
+ },
2232
+ ])
2233
+ .withPush(true)
2234
+ .withFlexibleDimensions(false)
2235
+ .withViewportMargin(8);
2236
+ this.toolbarOverlayRef.updatePositionStrategy(positionStrategy);
2237
+ }
2238
+ bindToolbarActions() {
2239
+ this.toolbarActionSubscriptions.forEach((sub) => sub.unsubscribe());
2240
+ this.toolbarActionSubscriptions = [];
2241
+ if (!this.toolbarComponentRef) {
2242
+ return;
2243
+ }
2244
+ const instance = this.toolbarComponentRef.instance;
2245
+ this.toolbarActionSubscriptions.push(instance.toggleRequired.subscribe(() => this.toggleFieldFlag('required')), instance.toggleReadonly.subscribe(() => this.toggleFieldFlag('readOnly')), instance.toggleHidden.subscribe(() => this.toggleFieldFlag('hidden')), instance.toggleDisabled.subscribe(() => this.toggleFieldFlag('disabled')), instance.openEditor.subscribe(() => this.openToolbarEditor()), instance.requestClose.subscribe(() => this.closeToolbar()));
2246
+ }
2247
+ toggleFieldFlag(field) {
2248
+ if (!this.instance || !this.toolbarFieldKey) {
2249
+ return;
2250
+ }
2251
+ const current = this.instance.getFieldMetadata(this.toolbarFieldKey);
2252
+ if (!current) {
2253
+ return;
2254
+ }
2255
+ if (field === 'required') {
2256
+ const currentRequired = !!current.required || !!current.validators?.required;
2257
+ const nextRequired = !currentRequired;
2258
+ this.instance.patchFieldMetadata(this.toolbarFieldKey, {
2259
+ required: nextRequired,
2260
+ validators: { ...(current.validators ?? {}), required: nextRequired },
2261
+ });
2262
+ return;
2263
+ }
2264
+ const patch = {
2265
+ [field]: !current[field],
2266
+ };
2267
+ this.instance.patchFieldMetadata(this.toolbarFieldKey, patch);
2268
+ }
2269
+ openToolbarEditor() {
2270
+ if (!this.toolbarFieldKey || !this.instance) {
2271
+ return;
2272
+ }
2273
+ this.tryOpenFieldEditor(this.toolbarFieldKey);
2274
+ }
2275
+ updateToolbarMetadata(metadata, openedByKeyboard) {
2276
+ if (!this.toolbarComponentRef) {
2277
+ return;
2278
+ }
2279
+ this.toolbarComponentRef.setInput('metadata', metadata);
2280
+ this.toolbarComponentRef.changeDetectorRef.detectChanges();
2281
+ if (openedByKeyboard) {
2282
+ this.toolbarComponentRef.instance.focusFirstAction();
2283
+ }
2284
+ }
2285
+ attachOutsideClickHandler() {
2286
+ this.outsideClickSubscription?.unsubscribe();
2287
+ if (!isPlatformBrowser(this.platformId) || typeof document === 'undefined') {
2288
+ return;
2289
+ }
2290
+ this.outsideClickSubscription = fromEvent(document, 'mousedown')
2291
+ .pipe(takeUntilDestroyed(this.destroyRef))
2292
+ .subscribe((event) => {
2293
+ const target = event.target;
2294
+ if (!target) {
2295
+ return;
2296
+ }
2297
+ const overlayEl = this.toolbarOverlayRef?.overlayElement;
2298
+ if (overlayEl && overlayEl.contains(target)) {
2299
+ return;
2300
+ }
2301
+ if (this.toolbarAnchor && this.toolbarAnchor.contains(target)) {
2302
+ return;
2303
+ }
2304
+ this.closeToolbar();
2305
+ });
2306
+ }
2307
+ closeToolbar() {
2308
+ this.toolbarOverlayRef?.detach();
2309
+ this.toolbarOverlayRef?.dispose();
2310
+ this.toolbarOverlayRef = undefined;
2311
+ this.toolbarComponentRef = undefined;
2312
+ this.outsideClickSubscription?.unsubscribe();
2313
+ this.outsideClickSubscription = undefined;
2314
+ this.resetToolbarState();
2315
+ }
2316
+ resetToolbarState() {
2317
+ this.toolbarFieldKey = undefined;
2318
+ this.toolbarAnchor = undefined;
2319
+ }
2320
+ attachMetadataSync() {
2321
+ this.metadataSubscription?.unsubscribe();
2322
+ const instance = this.instance;
2323
+ if (!instance) {
2324
+ return;
2325
+ }
2326
+ this.metadataSubscription = instance
2327
+ .metadataChanges()
2328
+ .pipe(takeUntilDestroyed(this.destroyRef))
2329
+ .subscribe((map) => {
2330
+ if (!this.toolbarFieldKey) {
2331
+ return;
2332
+ }
2333
+ const meta = map.get(this.toolbarFieldKey);
2334
+ if (!meta) {
2335
+ this.closeToolbar();
2336
+ return;
2337
+ }
2338
+ this.updateToolbarMetadata(meta, false);
2339
+ });
2340
+ }
1041
2341
  buildMetadataForField(field) {
1042
2342
  const existing = this.extractExistingMetadata(field.component);
1043
2343
  const label = existing?.label ?? this.inferLabel(field, existing);
@@ -1051,18 +2351,34 @@ class ManualFormComponent {
1051
2351
  validators: { ...existing?.validators, ...validators },
1052
2352
  };
1053
2353
  }
2354
+ syncHostFormGroupReference() {
2355
+ const hostForm = this.hostFormGroupDirective?.form;
2356
+ if (hostForm && this.formGroup !== hostForm) {
2357
+ this.formGroup = hostForm;
2358
+ if (isDevMode())
2359
+ console.debug('[ManualForm] Synced FormGroup from host FormGroupDirective');
2360
+ }
2361
+ }
1054
2362
  getControlByPath(path) {
1055
2363
  if (!Array.isArray(path) || path.length === 0)
1056
2364
  return null;
1057
2365
  return this.formGroup.get(path);
1058
2366
  }
1059
- ensureControlPath(path) {
1060
- if (!Array.isArray(path) || path.length === 0)
1061
- return;
1062
- const leaf = path[path.length - 1];
2367
+ getDirPath(dir) {
2368
+ if (Array.isArray(dir?.path) && dir.path.length) {
2369
+ return dir.path;
2370
+ }
2371
+ if (typeof dir?.name === 'string' && dir.name.length) {
2372
+ return [dir.name];
2373
+ }
2374
+ return [];
2375
+ }
2376
+ ensureGroupPath(path) {
1063
2377
  let group = this.formGroup;
1064
- for (let i = 0; i < path.length - 1; i++) {
1065
- const seg = path[i];
2378
+ if (!Array.isArray(path) || path.length === 0) {
2379
+ return group;
2380
+ }
2381
+ for (const seg of path) {
1066
2382
  const existing = group.get(seg);
1067
2383
  if (existing instanceof FormGroup) {
1068
2384
  group = existing;
@@ -1073,6 +2389,13 @@ class ManualFormComponent {
1073
2389
  group = next;
1074
2390
  }
1075
2391
  }
2392
+ return group;
2393
+ }
2394
+ ensureControlPath(path) {
2395
+ if (!Array.isArray(path) || path.length === 0)
2396
+ return;
2397
+ const leaf = path[path.length - 1];
2398
+ const group = this.ensureGroupPath(path.slice(0, -1));
1076
2399
  if (!group.get(leaf)) {
1077
2400
  group.addControl(leaf, new FormControl());
1078
2401
  }
@@ -1112,8 +2435,13 @@ class ManualFormComponent {
1112
2435
  }
1113
2436
  inferControlType(field) {
1114
2437
  const selector = field.selector?.toLowerCase();
1115
- if (selector && this.selectorToControlType[selector]) {
1116
- return this.selectorToControlType[selector];
2438
+ if (selector) {
2439
+ const fromLocal = this.selectorToControlType[selector];
2440
+ if (fromLocal)
2441
+ return fromLocal;
2442
+ const fromRegistry = this.selectorRegistry?.resolve(selector);
2443
+ if (fromRegistry)
2444
+ return fromRegistry;
1117
2445
  }
1118
2446
  const ctorName = field.component?.constructor?.name ?? '';
1119
2447
  for (const [key, value] of Object.entries(this.constructorToControlType)) {
@@ -1183,15 +2511,15 @@ class ManualFormComponent {
1183
2511
  };
1184
2512
  }
1185
2513
  normalizeActions(source) {
1186
- const base = deepClone(DEFAULT_ACTIONS);
2514
+ const base = deepClone(DEFAULT_ACTIONS$1);
1187
2515
  if (!source) {
1188
2516
  return base;
1189
2517
  }
1190
2518
  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,
2519
+ submit: mergeAction$1(base.submit, source.submit),
2520
+ cancel: mergeAction$1(base.cancel, source.cancel),
2521
+ reset: mergeAction$1(base.reset, source.reset),
2522
+ custom: source.custom ? source.custom.map(makeAction$1) : base.custom,
1195
2523
  position: source.position ?? base.position,
1196
2524
  orientation: source.orientation ?? base.orientation,
1197
2525
  spacing: source.spacing ?? base.spacing,
@@ -1208,37 +2536,38 @@ class ManualFormComponent {
1208
2536
  resetButtonLabel: source.resetButtonLabel ?? base.resetButtonLabel,
1209
2537
  };
1210
2538
  }
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: [
2539
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2540
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", 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 }, componentInstanceId: { classPropertyName: "componentInstanceId", publicName: "componentInstanceId", isSignal: true, isRequired: false, transformFunction: null }, enableCustomization: { classPropertyName: "enableCustomization", publicName: "enableCustomization", 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
2541
  // Provide a ControlContainer at the host boundary so projected formControlName can resolve it
1214
2542
  { 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 });
2543
+ ], queries: [{ propertyName: "formControls", predicate: FormControlName, descendants: true }], ngImport: i0, template: "<div class=\"pdx-manual-form\">\n @if (showHeader()) {\n <praxis-manual-form-header [instance]=\"instance\" [title]=\"formTitle()\" [description]=\"formDescription()\"\n [enableCustomization]=\"enableCustomization()\" (editForm)=\"openFormEditor()\" (save)=\"handleSave()\"\n (reset)=\"handleReset()\"></praxis-manual-form-header>\n }\n\n @if (enableCustomization()) {\n <div class=\"pdx-manual-form__assistant\">\n <praxis-ai-assistant [adapter]=\"aiAdapter\"></praxis-ai-assistant>\n </div>\n }\n\n <form class=\"pdx-manual-form__form\" [formGroup]=\"formGroup\" (submit)=\"handleSubmit(); $event.preventDefault()\">\n <ng-content></ng-content>\n </form>\n\n @if (showActions() && resolvedActions) {\n <praxis-manual-form-actions [actions]=\"resolvedActions\"\n (actionClick)=\"handleAction($event)\"></praxis-manual-form-actions>\n }\n</div>\n", styles: [".pdx-manual-form{display:flex;flex-direction:column;gap:var(--pdx-manual-form-gap, 24px);color:var(--md-sys-color-on-surface)}.pdx-manual-form__form{display:grid;gap:var(--pdx-manual-form-field-gap, 16px);padding:var(--pdx-manual-form-padding, 20px);border-radius:var(--pdx-manual-form-radius, 16px);background:var(--pdx-manual-form-surface, var(--md-sys-color-surface-container));border:1px solid var(--pdx-manual-form-outline, var(--md-sys-color-outline-variant));box-shadow:var(--pdx-manual-form-shadow, var(--md-sys-elevation-level1, none))}.pdx-manual-form__form:focus-within{border-color:var(--pdx-manual-form-focus, var(--md-sys-color-primary));box-shadow:0 0 0 2px var(--md-sys-color-primary),var(--pdx-manual-form-shadow, var(--md-sys-elevation-level1, none))}.pdx-manual-form__assistant{display:flex;justify-content:flex-end}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: PraxisAiAssistantComponent, selector: "praxis-ai-assistant", inputs: ["adapter", "riskPolicy", "allowManualPatchEdit"] }, { kind: "component", type: ManualFormHeaderComponent, selector: "praxis-manual-form-header", inputs: ["instance", "title", "description", "saveLabel", "resetLabel", "enableCustomization", "editFormLabel"], outputs: ["save", "reset", "editForm"] }, { kind: "component", type: ManualFormActionsComponent, selector: "praxis-manual-form-actions", inputs: ["actions", "trackByFn"], outputs: ["actionClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1216
2544
  }
1217
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormComponent, decorators: [{
2545
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormComponent, decorators: [{
1218
2546
  type: Component,
1219
2547
  args: [{ selector: 'praxis-manual-form', standalone: true, imports: [
1220
2548
  CommonModule,
1221
2549
  ReactiveFormsModule,
2550
+ PraxisAiAssistantComponent,
1222
2551
  ManualFormHeaderComponent,
1223
2552
  ManualFormActionsComponent,
1224
2553
  ], providers: [
1225
2554
  // Provide a ControlContainer at the host boundary so projected formControlName can resolve it
1226
2555
  { 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: [{
2556
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"pdx-manual-form\">\n @if (showHeader()) {\n <praxis-manual-form-header [instance]=\"instance\" [title]=\"formTitle()\" [description]=\"formDescription()\"\n [enableCustomization]=\"enableCustomization()\" (editForm)=\"openFormEditor()\" (save)=\"handleSave()\"\n (reset)=\"handleReset()\"></praxis-manual-form-header>\n }\n\n @if (enableCustomization()) {\n <div class=\"pdx-manual-form__assistant\">\n <praxis-ai-assistant [adapter]=\"aiAdapter\"></praxis-ai-assistant>\n </div>\n }\n\n <form class=\"pdx-manual-form__form\" [formGroup]=\"formGroup\" (submit)=\"handleSubmit(); $event.preventDefault()\">\n <ng-content></ng-content>\n </form>\n\n @if (showActions() && resolvedActions) {\n <praxis-manual-form-actions [actions]=\"resolvedActions\"\n (actionClick)=\"handleAction($event)\"></praxis-manual-form-actions>\n }\n</div>\n", styles: [".pdx-manual-form{display:flex;flex-direction:column;gap:var(--pdx-manual-form-gap, 24px);color:var(--md-sys-color-on-surface)}.pdx-manual-form__form{display:grid;gap:var(--pdx-manual-form-field-gap, 16px);padding:var(--pdx-manual-form-padding, 20px);border-radius:var(--pdx-manual-form-radius, 16px);background:var(--pdx-manual-form-surface, var(--md-sys-color-surface-container));border:1px solid var(--pdx-manual-form-outline, var(--md-sys-color-outline-variant));box-shadow:var(--pdx-manual-form-shadow, var(--md-sys-elevation-level1, none))}.pdx-manual-form__form:focus-within{border-color:var(--pdx-manual-form-focus, var(--md-sys-color-primary));box-shadow:0 0 0 2px var(--md-sys-color-primary),var(--pdx-manual-form-shadow, var(--md-sys-elevation-level1, none))}.pdx-manual-form__assistant{display:flex;justify-content:flex-end}\n"] }]
2557
+ }], ctorParameters: () => [], propDecorators: { formId: [{ type: i0.Input, args: [{ isSignal: true, alias: "formId", required: true }] }], formTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "formTitle", required: false }] }], formDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "formDescription", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], showActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showActions", required: false }] }], enableAutoSave: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableAutoSave", required: false }] }], componentInstanceId: [{ type: i0.Input, args: [{ isSignal: true, alias: "componentInstanceId", required: false }] }], enableCustomization: [{ type: i0.Input, args: [{ isSignal: true, alias: "enableCustomization", required: false }] }], persistenceOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "persistenceOptions", required: false }] }], usePathNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "usePathNames", required: false }] }], autoSaveDebounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoSaveDebounceMs", required: false }] }], submitted: [{ type: i0.Output, args: ["submitted"] }], saved: [{ type: i0.Output, args: ["saved"] }], resetEvent: [{ type: i0.Output, args: ["reset"] }], metadataChange: [{ type: i0.Output, args: ["metadataChange"] }], formControls: [{
1229
2558
  type: ContentChildren,
1230
2559
  args: [FormControlName, { descendants: true }]
1231
2560
  }] } });
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' }),
2561
+ const DEFAULT_ACTIONS$1 = {
2562
+ submit: makeAction$1({ id: 'submit', label: 'Salvar', visible: true, type: 'submit', color: 'primary' }),
2563
+ cancel: makeAction$1({ id: 'cancel', label: 'Cancelar', visible: false, type: 'button' }),
2564
+ reset: makeAction$1({ id: 'reset', label: 'Restaurar', visible: true, type: 'reset' }),
1236
2565
  custom: [],
1237
2566
  position: 'right',
1238
2567
  orientation: 'horizontal',
1239
2568
  spacing: 'normal',
1240
2569
  };
1241
- function makeAction(action) {
2570
+ function makeAction$1(action) {
1242
2571
  return {
1243
2572
  id: action.id ?? 'action',
1244
2573
  label: action.label ?? 'Ação',
@@ -1255,7 +2584,7 @@ function makeAction(action) {
1255
2584
  shortcut: action.shortcut,
1256
2585
  };
1257
2586
  }
1258
- function mergeAction(base, override) {
2587
+ function mergeAction$1(base, override) {
1259
2588
  if (!override) {
1260
2589
  return base;
1261
2590
  }
@@ -1265,24 +2594,7 @@ function mergeAction(base, override) {
1265
2594
  visible: override.visible !== undefined ? override.visible : base.visible,
1266
2595
  };
1267
2596
  }
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
- };
2597
+ const DEFAULT_SELECTOR_TO_CONTROL_TYPE = DEFAULT_FIELD_SELECTOR_CONTROL_TYPE_MAP;
1286
2598
  const DEFAULT_CONSTRUCTOR_TO_CONTROL_TYPE = {
1287
2599
  TextInput: FieldControlType.INPUT,
1288
2600
  Textarea: FieldControlType.TEXTAREA,
@@ -1308,6 +2620,30 @@ class ManualFormConfigEditorComponent {
1308
2620
  all = [];
1309
2621
  filtered = [];
1310
2622
  onlyHidden = false;
2623
+ actionsModel = deepClone(DEFAULT_ACTIONS);
2624
+ messagesModel = {};
2625
+ behaviorModel = {};
2626
+ hintsModel = createDefaultHints();
2627
+ hooksText = createDefaultHooksText();
2628
+ hooksErrors = {};
2629
+ formRulesText = '';
2630
+ formRulesStateText = '';
2631
+ formRulesError = '';
2632
+ formRulesStateError = '';
2633
+ hookStages = [
2634
+ 'beforeInit',
2635
+ 'afterInit',
2636
+ 'beforeValidate',
2637
+ 'afterValidate',
2638
+ 'beforeSubmit',
2639
+ 'afterSubmit',
2640
+ 'onError',
2641
+ ];
2642
+ actionBlocks = [
2643
+ { key: 'submit', label: 'Salvar' },
2644
+ { key: 'cancel', label: 'Cancelar' },
2645
+ { key: 'reset', label: 'Resetar' },
2646
+ ];
1311
2647
  // SettingsValueProvider observables
1312
2648
  isDirty$ = new BehaviorSubject(false);
1313
2649
  isValid$ = new BehaviorSubject(true);
@@ -1364,6 +2700,206 @@ class ManualFormConfigEditorComponent {
1364
2700
  }
1365
2701
  catch { }
1366
2702
  }
2703
+ updateActionField(key, field, value) {
2704
+ const current = this.actionsModel[key];
2705
+ this.actionsModel = {
2706
+ ...this.actionsModel,
2707
+ [key]: {
2708
+ ...current,
2709
+ [field]: value,
2710
+ },
2711
+ };
2712
+ this.applyConfigPatch({ actions: this.actionsModel });
2713
+ }
2714
+ updateActionsLayout(field, value) {
2715
+ this.actionsModel = {
2716
+ ...this.actionsModel,
2717
+ [field]: value,
2718
+ };
2719
+ this.applyConfigPatch({ actions: this.actionsModel });
2720
+ }
2721
+ addCustomAction() {
2722
+ const nextId = this.generateCustomId();
2723
+ const nextAction = makeAction({ id: nextId, label: 'Ação' });
2724
+ const custom = [...(this.actionsModel.custom || []), nextAction];
2725
+ this.actionsModel = { ...this.actionsModel, custom };
2726
+ this.applyConfigPatch({ actions: this.actionsModel });
2727
+ }
2728
+ removeCustomAction(action) {
2729
+ const custom = (this.actionsModel.custom || []).filter((item) => item !== action);
2730
+ this.actionsModel = { ...this.actionsModel, custom };
2731
+ this.applyConfigPatch({ actions: this.actionsModel });
2732
+ }
2733
+ updateCustomActionField(action, field, value) {
2734
+ const custom = (this.actionsModel.custom || []).map((item) => {
2735
+ if (item !== action)
2736
+ return item;
2737
+ return { ...item, [field]: value };
2738
+ });
2739
+ this.actionsModel = { ...this.actionsModel, custom };
2740
+ this.applyConfigPatch({ actions: this.actionsModel });
2741
+ }
2742
+ updateMessage(key, value) {
2743
+ this.messagesModel = {
2744
+ ...this.messagesModel,
2745
+ [key]: value,
2746
+ };
2747
+ this.applyConfigPatch({ messages: this.messagesModel });
2748
+ }
2749
+ updateConfirmation(key, value) {
2750
+ this.messagesModel = {
2751
+ ...this.messagesModel,
2752
+ confirmations: {
2753
+ ...(this.messagesModel.confirmations || {}),
2754
+ [key]: value,
2755
+ },
2756
+ };
2757
+ this.applyConfigPatch({ messages: this.messagesModel });
2758
+ }
2759
+ updateLoading(key, value) {
2760
+ this.messagesModel = {
2761
+ ...this.messagesModel,
2762
+ loading: {
2763
+ ...(this.messagesModel.loading || {}),
2764
+ [key]: value,
2765
+ },
2766
+ };
2767
+ this.applyConfigPatch({ messages: this.messagesModel });
2768
+ }
2769
+ updateCustomMessage(action, kind, value) {
2770
+ const actionId = action.id || '';
2771
+ this.messagesModel = {
2772
+ ...this.messagesModel,
2773
+ customActions: {
2774
+ ...(this.messagesModel.customActions || {}),
2775
+ [actionId]: {
2776
+ ...(this.messagesModel.customActions || {})[actionId],
2777
+ [kind]: value,
2778
+ },
2779
+ },
2780
+ };
2781
+ this.applyConfigPatch({ messages: this.messagesModel });
2782
+ }
2783
+ updateBehavior(key, value) {
2784
+ this.behaviorModel = {
2785
+ ...this.behaviorModel,
2786
+ [key]: value,
2787
+ };
2788
+ this.applyConfigPatch({ behavior: this.behaviorModel });
2789
+ }
2790
+ updateHints(scope, key, value) {
2791
+ this.hintsModel = {
2792
+ ...this.hintsModel,
2793
+ [scope]: {
2794
+ ...this.hintsModel[scope],
2795
+ [key]: value,
2796
+ },
2797
+ };
2798
+ this.applyConfigPatch({ hints: this.hintsModel });
2799
+ }
2800
+ hookLabel(stage) {
2801
+ const map = {
2802
+ beforeInit: 'beforeInit',
2803
+ afterInit: 'afterInit',
2804
+ beforeValidate: 'beforeValidate',
2805
+ afterValidate: 'afterValidate',
2806
+ beforeSubmit: 'beforeSubmit',
2807
+ afterSubmit: 'afterSubmit',
2808
+ onError: 'onError',
2809
+ };
2810
+ return map[stage] || stage;
2811
+ }
2812
+ onHookTextChange(stage, value) {
2813
+ this.hooksText = { ...this.hooksText, [stage]: value };
2814
+ }
2815
+ resetHooks() {
2816
+ this.hooksText = buildHooksText(this.instance.currentConfig.hooks);
2817
+ this.hooksErrors = {};
2818
+ }
2819
+ applyHooks() {
2820
+ const next = {};
2821
+ const errors = {};
2822
+ for (const stage of this.hookStages) {
2823
+ const raw = (this.hooksText[stage] || '').trim();
2824
+ if (!raw)
2825
+ continue;
2826
+ try {
2827
+ const parsed = JSON.parse(raw);
2828
+ if (!Array.isArray(parsed)) {
2829
+ errors[stage] = 'Precisa ser um array JSON.';
2830
+ continue;
2831
+ }
2832
+ next[stage] = parsed;
2833
+ }
2834
+ catch {
2835
+ errors[stage] = 'JSON inválido.';
2836
+ }
2837
+ }
2838
+ this.hooksErrors = errors;
2839
+ if (Object.keys(errors).length > 0) {
2840
+ return;
2841
+ }
2842
+ this.applyConfigPatch({ hooks: next });
2843
+ }
2844
+ onRulesTextChange(value) {
2845
+ this.formRulesText = value;
2846
+ }
2847
+ onRulesStateTextChange(value) {
2848
+ this.formRulesStateText = value;
2849
+ }
2850
+ applyRules() {
2851
+ this.formRulesError = '';
2852
+ this.formRulesStateError = '';
2853
+ let rules;
2854
+ let rulesState;
2855
+ const rulesRaw = this.formRulesText.trim();
2856
+ if (rulesRaw) {
2857
+ try {
2858
+ const parsed = JSON.parse(rulesRaw);
2859
+ if (!Array.isArray(parsed)) {
2860
+ this.formRulesError = 'formRules precisa ser um array JSON.';
2861
+ return;
2862
+ }
2863
+ rules = parsed;
2864
+ }
2865
+ catch {
2866
+ this.formRulesError = 'JSON inválido em formRules.';
2867
+ return;
2868
+ }
2869
+ }
2870
+ const stateRaw = this.formRulesStateText.trim();
2871
+ if (stateRaw) {
2872
+ try {
2873
+ rulesState = JSON.parse(stateRaw);
2874
+ }
2875
+ catch {
2876
+ this.formRulesStateError = 'JSON inválido em formRulesState.';
2877
+ return;
2878
+ }
2879
+ }
2880
+ this.applyConfigPatch({ formRules: rules, formRulesState: rulesState });
2881
+ }
2882
+ applyCascadePatch(patch) {
2883
+ const entries = Object.entries(patch || {});
2884
+ if (!entries.length) {
2885
+ return;
2886
+ }
2887
+ for (const [fieldName, fieldPatch] of entries) {
2888
+ try {
2889
+ this.instance.patchFieldMetadata(fieldName, fieldPatch);
2890
+ }
2891
+ catch { }
2892
+ }
2893
+ try {
2894
+ this.instance.saveDraft();
2895
+ this.refresh();
2896
+ this.isDirty$.next(true);
2897
+ }
2898
+ catch { }
2899
+ }
2900
+ get cascadeFields() {
2901
+ return this.instance.currentConfig.fieldMetadata || [];
2902
+ }
1367
2903
  getSettingsValue() {
1368
2904
  return {
1369
2905
  fieldStates: (this.instance.currentConfig.fieldMetadata || []).map((f) => ({
@@ -1373,6 +2909,13 @@ class ManualFormConfigEditorComponent {
1373
2909
  readOnly: !!f.readOnly,
1374
2910
  disabled: !!f.disabled,
1375
2911
  })),
2912
+ actions: deepClone(this.actionsModel),
2913
+ messages: deepClone(this.messagesModel),
2914
+ behavior: deepClone(this.behaviorModel),
2915
+ hints: deepClone(this.hintsModel),
2916
+ hooks: deepClone(this.instance.currentConfig.hooks || {}),
2917
+ formRules: deepClone(this.instance.currentConfig.formRules || []),
2918
+ formRulesState: deepClone(this.instance.currentConfig.formRulesState),
1376
2919
  };
1377
2920
  }
1378
2921
  onSave() {
@@ -1383,127 +2926,2038 @@ class ManualFormConfigEditorComponent {
1383
2926
  }
1384
2927
  refresh() {
1385
2928
  this.all = (this.instance.currentConfig.fieldMetadata || []).map(f => ({ ...f }));
2929
+ this.actionsModel = this.normalizeActions(this.instance.currentConfig.actions);
2930
+ this.messagesModel = deepClone(this.instance.currentConfig.messages || {});
2931
+ this.behaviorModel = deepClone(this.instance.currentConfig.behavior || {});
2932
+ this.hintsModel = createHints(this.instance.currentConfig.hints);
2933
+ this.hooksText = buildHooksText(this.instance.currentConfig.hooks);
2934
+ this.formRulesText = stringifyJson(this.instance.currentConfig.formRules, true);
2935
+ this.formRulesStateText = stringifyJson(this.instance.currentConfig.formRulesState, false);
1386
2936
  this.applyFilter();
1387
2937
  }
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: `
2938
+ normalizeActions(source) {
2939
+ const base = deepClone(DEFAULT_ACTIONS);
2940
+ if (!source) {
2941
+ return base;
2942
+ }
2943
+ return {
2944
+ submit: mergeAction(base.submit, source.submit),
2945
+ cancel: mergeAction(base.cancel, source.cancel),
2946
+ reset: mergeAction(base.reset, source.reset),
2947
+ custom: source.custom ? source.custom.map((item, index) => makeAction(item, index)) : base.custom,
2948
+ position: source.position ?? base.position,
2949
+ orientation: source.orientation ?? base.orientation,
2950
+ spacing: source.spacing ?? base.spacing,
2951
+ sticky: source.sticky ?? base.sticky,
2952
+ divider: source.divider ?? base.divider,
2953
+ placement: source.placement ?? base.placement,
2954
+ mobile: source.mobile ?? base.mobile,
2955
+ containerClassName: source.containerClassName ?? base.containerClassName,
2956
+ containerStyles: source.containerStyles ?? base.containerStyles,
2957
+ showSaveButton: source.showSaveButton ?? base.showSaveButton,
2958
+ submitButtonLabel: source.submitButtonLabel ?? base.submitButtonLabel,
2959
+ showCancelButton: source.showCancelButton ?? base.showCancelButton,
2960
+ cancelButtonLabel: source.cancelButtonLabel ?? base.cancelButtonLabel,
2961
+ showResetButton: source.showResetButton ?? base.showResetButton,
2962
+ resetButtonLabel: source.resetButtonLabel ?? base.resetButtonLabel,
2963
+ };
2964
+ }
2965
+ applyConfigPatch(patch) {
2966
+ try {
2967
+ const config = deepClone(this.instance.currentConfig);
2968
+ const next = { ...config, ...patch };
2969
+ this.instance.replaceConfig(next);
2970
+ this.instance.saveDraft();
2971
+ this.refresh();
2972
+ this.isDirty$.next(true);
2973
+ }
2974
+ catch { }
2975
+ }
2976
+ generateCustomId() {
2977
+ const existing = new Set((this.actionsModel.custom || []).map((item) => item.id).filter(Boolean));
2978
+ let counter = existing.size + 1;
2979
+ let next = `custom-${counter}`;
2980
+ while (existing.has(next)) {
2981
+ counter += 1;
2982
+ next = `custom-${counter}`;
2983
+ }
2984
+ return next;
2985
+ }
2986
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormConfigEditorComponent, deps: [{ token: SETTINGS_PANEL_DATA }, { token: SETTINGS_PANEL_REF }], target: i0.ɵɵFactoryTarget.Component });
2987
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ManualFormConfigEditorComponent, isStandalone: true, selector: "praxis-manual-form-config-editor", ngImport: i0, template: `
1390
2988
  <div class="mf-editor">
1391
2989
  <div class="mf-editor__toolbar">
1392
- <label><input type="checkbox" [(ngModel)]="onlyHidden" (change)="applyFilter()" /> Mostrar apenas ocultos</label>
2990
+ <div class="mf-editor__title">
2991
+ <span>Configurações do formulário</span>
2992
+ <small class="mf-editor__subtitle">Campos, ações, mensagens e comportamento</small>
2993
+ </div>
1393
2994
  <div class="spacer"></div>
1394
- <button type="button" (click)="close()">Fechar</button>
2995
+ <button type="button" class="mf-btn" (click)="close()">Fechar</button>
1395
2996
  </div>
1396
2997
 
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>
2998
+ <mat-tab-group class="mf-tabs">
2999
+ <mat-tab label="Campos">
3000
+ <div class="mf-tab">
3001
+ <label class="mf-editor__filter">
3002
+ <input type="checkbox" [(ngModel)]="onlyHidden" (change)="applyFilter()" />
3003
+ <span>Mostrar apenas ocultos</span>
1416
3004
  </label>
3005
+ <div class="mf-editor__list">
3006
+ <div class="mf-editor__row mf-editor__row--head">
3007
+ <div class="col col--name">Campo</div>
3008
+ <div class="col col--label">Rótulo</div>
3009
+ <div class="col col--type">Tipo</div>
3010
+ <div class="col col--vis">Visível</div>
3011
+ <div class="col col--req">Obrigatório</div>
3012
+ <div class="col col--ro">Som. leitura</div>
3013
+ <div class="col col--dis">Desabilitado</div>
3014
+ </div>
3015
+
3016
+ @for (f of filtered; track f.name) {
3017
+ <div class="mf-editor__row">
3018
+ <div class="col col--name"><code>{{ f.name }}</code></div>
3019
+ <div class="col col--label">{{ f.label || f.name }}</div>
3020
+ <div class="col col--type">{{ f.controlType }}</div>
3021
+ <div class="col col--vis">
3022
+ <label class="toggle">
3023
+ <input type="checkbox" [checked]="!f.hidden" (change)="toggleVisibility(f, $any($event.target).checked)" />
3024
+ <span>{{ f.hidden ? 'Oculto' : 'Visível' }}</span>
3025
+ </label>
3026
+ </div>
3027
+ <div class="col col--req">
3028
+ <label class="toggle">
3029
+ <input type="checkbox" [checked]="!!f.required" (change)="toggleRequired(f, $any($event.target).checked)" />
3030
+ <span>{{ f.required ? 'Sim' : 'Não' }}</span>
3031
+ </label>
3032
+ </div>
3033
+ <div class="col col--ro">
3034
+ <label class="toggle">
3035
+ <input type="checkbox" [checked]="!!f.readOnly" (change)="toggleReadOnly(f, $any($event.target).checked)" />
3036
+ <span>{{ f.readOnly ? 'Sim' : 'Não' }}</span>
3037
+ </label>
3038
+ </div>
3039
+ <div class="col col--dis">
3040
+ <label class="toggle">
3041
+ <input type="checkbox" [checked]="!!f.disabled" (change)="toggleDisabled(f, $any($event.target).checked)" />
3042
+ <span>{{ f.disabled ? 'Sim' : 'Não' }}</span>
3043
+ </label>
3044
+ </div>
3045
+ </div>
3046
+ }
3047
+ </div>
1417
3048
  </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>
3049
+ </mat-tab>
3050
+
3051
+ <mat-tab label="Ações">
3052
+ <div class="mf-tab">
3053
+ <section class="mf-section">
3054
+ <div class="mf-section__header">
3055
+ <div>
3056
+ <h3>Botões padrão</h3>
3057
+ <p class="mf-help">Controle rótulos, visibilidade e estilo dos botões básicos.</p>
3058
+ </div>
3059
+ </div>
3060
+ <div class="mf-actions-grid">
3061
+ @for (block of actionBlocks; track block.key) {
3062
+ <div class="mf-action-card">
3063
+ <div class="mf-action-card__title">{{ block.label }}</div>
3064
+ <mat-slide-toggle
3065
+ [checked]="actionsModel[block.key].visible"
3066
+ (change)="updateActionField(block.key, 'visible', $event.checked)"
3067
+ >
3068
+ Visível
3069
+ </mat-slide-toggle>
3070
+ <mat-form-field appearance="outline">
3071
+ <mat-label>Label</mat-label>
3072
+ <input
3073
+ matInput
3074
+ [value]="actionsModel[block.key].label"
3075
+ (input)="updateActionField(block.key, 'label', $any($event.target).value)"
3076
+ />
3077
+ </mat-form-field>
3078
+ <mat-form-field appearance="outline">
3079
+ <mat-label>Ícone</mat-label>
3080
+ <input
3081
+ matInput
3082
+ [value]="actionsModel[block.key].icon || ''"
3083
+ (input)="updateActionField(block.key, 'icon', $any($event.target).value)"
3084
+ />
3085
+ </mat-form-field>
3086
+ <mat-form-field appearance="outline">
3087
+ <mat-label>Variante</mat-label>
3088
+ <mat-select
3089
+ [value]="actionsModel[block.key].variant || null"
3090
+ (selectionChange)="updateActionField(block.key, 'variant', $event.value)"
3091
+ >
3092
+ <mat-option [value]="null">Padrão</mat-option>
3093
+ <mat-option value="raised">Elevado</mat-option>
3094
+ <mat-option value="stroked">Contornado</mat-option>
3095
+ <mat-option value="flat">Plano</mat-option>
3096
+ <mat-option value="fab">Flutuante</mat-option>
3097
+ </mat-select>
3098
+ </mat-form-field>
3099
+ <mat-form-field appearance="outline">
3100
+ <mat-label>Cor</mat-label>
3101
+ <mat-select
3102
+ [value]="actionsModel[block.key].color || null"
3103
+ (selectionChange)="updateActionField(block.key, 'color', $event.value)"
3104
+ >
3105
+ <mat-option [value]="null">Padrão</mat-option>
3106
+ <mat-option value="primary">Primary</mat-option>
3107
+ <mat-option value="accent">Accent</mat-option>
3108
+ <mat-option value="warn">Warn</mat-option>
3109
+ <mat-option value="basic">Basic</mat-option>
3110
+ </mat-select>
3111
+ </mat-form-field>
3112
+ <mat-form-field appearance="outline">
3113
+ <mat-label>Atalho</mat-label>
3114
+ <input
3115
+ matInput
3116
+ [value]="actionsModel[block.key].shortcut || ''"
3117
+ (input)="updateActionField(block.key, 'shortcut', $any($event.target).value)"
3118
+ />
3119
+ </mat-form-field>
3120
+ </div>
3121
+ }
3122
+ </div>
3123
+ </section>
3124
+
3125
+ <section class="mf-section">
3126
+ <div class="mf-section__header">
3127
+ <div>
3128
+ <h3>Layout das ações</h3>
3129
+ <p class="mf-help">Define alinhamento e disposição da barra de ações.</p>
3130
+ </div>
3131
+ </div>
3132
+ <div class="mf-actions-layout">
3133
+ <mat-form-field appearance="outline">
3134
+ <mat-label>Posição</mat-label>
3135
+ <mat-select
3136
+ [value]="actionsModel.position || null"
3137
+ (selectionChange)="updateActionsLayout('position', $event.value)"
3138
+ >
3139
+ <mat-option value="left">Esquerda</mat-option>
3140
+ <mat-option value="center">Centro</mat-option>
3141
+ <mat-option value="right">Direita</mat-option>
3142
+ <mat-option value="justified">Justificado</mat-option>
3143
+ <mat-option value="split">Split</mat-option>
3144
+ </mat-select>
3145
+ </mat-form-field>
3146
+ <mat-form-field appearance="outline">
3147
+ <mat-label>Orientação</mat-label>
3148
+ <mat-select
3149
+ [value]="actionsModel.orientation || null"
3150
+ (selectionChange)="updateActionsLayout('orientation', $event.value)"
3151
+ >
3152
+ <mat-option value="horizontal">Horizontal</mat-option>
3153
+ <mat-option value="vertical">Vertical</mat-option>
3154
+ </mat-select>
3155
+ </mat-form-field>
3156
+ <mat-form-field appearance="outline">
3157
+ <mat-label>Espaçamento</mat-label>
3158
+ <mat-select
3159
+ [value]="actionsModel.spacing || null"
3160
+ (selectionChange)="updateActionsLayout('spacing', $event.value)"
3161
+ >
3162
+ <mat-option value="compact">Compacto</mat-option>
3163
+ <mat-option value="normal">Normal</mat-option>
3164
+ <mat-option value="spacious">Espaçoso</mat-option>
3165
+ </mat-select>
3166
+ </mat-form-field>
3167
+ <mat-form-field appearance="outline">
3168
+ <mat-label>Posicionamento</mat-label>
3169
+ <mat-select
3170
+ [value]="actionsModel.placement || null"
3171
+ (selectionChange)="updateActionsLayout('placement', $event.value)"
3172
+ >
3173
+ <mat-option value="afterSections">Após seções</mat-option>
3174
+ <mat-option value="insideLastSection">Dentro da última seção</mat-option>
3175
+ <mat-option value="top">Topo</mat-option>
3176
+ </mat-select>
3177
+ </mat-form-field>
3178
+ <mat-slide-toggle
3179
+ [checked]="!!actionsModel.sticky"
3180
+ (change)="updateActionsLayout('sticky', $event.checked)"
3181
+ >
3182
+ Ações fixas
3183
+ </mat-slide-toggle>
3184
+ <mat-slide-toggle
3185
+ [checked]="!!actionsModel.divider"
3186
+ (change)="updateActionsLayout('divider', $event.checked)"
3187
+ >
3188
+ Mostrar divisor
3189
+ </mat-slide-toggle>
3190
+ </div>
3191
+ </section>
3192
+
3193
+ <section class="mf-section">
3194
+ <div class="mf-section__header mf-section__header--row">
3195
+ <div>
3196
+ <h3>Ações customizadas</h3>
3197
+ <p class="mf-help">Crie botões extras com evento próprio.</p>
3198
+ </div>
3199
+ <button mat-stroked-button type="button" (click)="addCustomAction()">
3200
+ <mat-icon>add</mat-icon>
3201
+ Adicionar ação
3202
+ </button>
3203
+ </div>
3204
+ <div class="mf-custom-actions">
3205
+ @if (actionsModel.custom?.length) {
3206
+ @for (action of actionsModel.custom || []; track action.id) {
3207
+ <div class="mf-custom-card">
3208
+ <div class="mf-custom-card__header">
3209
+ <div class="mf-custom-card__title">{{ action.label || action.id }}</div>
3210
+ <button mat-icon-button type="button" (click)="removeCustomAction(action)">
3211
+ <mat-icon>delete</mat-icon>
3212
+ </button>
3213
+ </div>
3214
+ <div class="mf-custom-card__grid">
3215
+ <mat-form-field appearance="outline">
3216
+ <mat-label>ID</mat-label>
3217
+ <input
3218
+ matInput
3219
+ [value]="action.id || ''"
3220
+ (input)="updateCustomActionField(action, 'id', $any($event.target).value)"
3221
+ />
3222
+ </mat-form-field>
3223
+ <mat-form-field appearance="outline">
3224
+ <mat-label>Label</mat-label>
3225
+ <input
3226
+ matInput
3227
+ [value]="action.label || ''"
3228
+ (input)="updateCustomActionField(action, 'label', $any($event.target).value)"
3229
+ />
3230
+ </mat-form-field>
3231
+ <mat-form-field appearance="outline">
3232
+ <mat-label>Evento (action)</mat-label>
3233
+ <input
3234
+ matInput
3235
+ [value]="action.action || ''"
3236
+ (input)="updateCustomActionField(action, 'action', $any($event.target).value)"
3237
+ />
3238
+ </mat-form-field>
3239
+ <mat-form-field appearance="outline">
3240
+ <mat-label>Ícone</mat-label>
3241
+ <input
3242
+ matInput
3243
+ [value]="action.icon || ''"
3244
+ (input)="updateCustomActionField(action, 'icon', $any($event.target).value)"
3245
+ />
3246
+ </mat-form-field>
3247
+ <mat-form-field appearance="outline">
3248
+ <mat-label>Variante</mat-label>
3249
+ <mat-select
3250
+ [value]="action.variant || null"
3251
+ (selectionChange)="updateCustomActionField(action, 'variant', $event.value)"
3252
+ >
3253
+ <mat-option [value]="null">Padrão</mat-option>
3254
+ <mat-option value="raised">Elevado</mat-option>
3255
+ <mat-option value="stroked">Contornado</mat-option>
3256
+ <mat-option value="flat">Plano</mat-option>
3257
+ <mat-option value="fab">Flutuante</mat-option>
3258
+ </mat-select>
3259
+ </mat-form-field>
3260
+ <mat-form-field appearance="outline">
3261
+ <mat-label>Cor</mat-label>
3262
+ <mat-select
3263
+ [value]="action.color || null"
3264
+ (selectionChange)="updateCustomActionField(action, 'color', $event.value)"
3265
+ >
3266
+ <mat-option [value]="null">Padrão</mat-option>
3267
+ <mat-option value="primary">Primary</mat-option>
3268
+ <mat-option value="accent">Accent</mat-option>
3269
+ <mat-option value="warn">Warn</mat-option>
3270
+ <mat-option value="basic">Basic</mat-option>
3271
+ </mat-select>
3272
+ </mat-form-field>
3273
+ <mat-form-field appearance="outline">
3274
+ <mat-label>Atalho</mat-label>
3275
+ <input
3276
+ matInput
3277
+ [value]="action.shortcut || ''"
3278
+ (input)="updateCustomActionField(action, 'shortcut', $any($event.target).value)"
3279
+ />
3280
+ </mat-form-field>
3281
+ <mat-form-field appearance="outline">
3282
+ <mat-label>Tooltip</mat-label>
3283
+ <input
3284
+ matInput
3285
+ [value]="action.tooltip || ''"
3286
+ (input)="updateCustomActionField(action, 'tooltip', $any($event.target).value)"
3287
+ />
3288
+ </mat-form-field>
3289
+ <mat-slide-toggle
3290
+ [checked]="action.visible !== false"
3291
+ (change)="updateCustomActionField(action, 'visible', $event.checked)"
3292
+ >
3293
+ Visível
3294
+ </mat-slide-toggle>
3295
+ </div>
3296
+ </div>
3297
+ }
3298
+ } @else {
3299
+ <p class="mf-empty">Nenhuma ação customizada cadastrada.</p>
3300
+ }
3301
+ </div>
3302
+ </section>
1423
3303
  </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>
3304
+ </mat-tab>
3305
+
3306
+ <mat-tab label="Mensagens">
3307
+ <div class="mf-tab">
3308
+ <section class="mf-section">
3309
+ <div class="mf-section__header">
3310
+ <div>
3311
+ <h3>Feedback padrão</h3>
3312
+ <p class="mf-help">Mensagens de sucesso/erro exibidas pelo host.</p>
3313
+ </div>
3314
+ </div>
3315
+ <div class="mf-messages-grid">
3316
+ <mat-form-field appearance="outline">
3317
+ <mat-label>Sucesso ao criar</mat-label>
3318
+ <input
3319
+ matInput
3320
+ [value]="messagesModel.createRegistrySuccess || ''"
3321
+ (input)="updateMessage('createRegistrySuccess', $any($event.target).value)"
3322
+ />
3323
+ </mat-form-field>
3324
+ <mat-form-field appearance="outline">
3325
+ <mat-label>Erro ao criar</mat-label>
3326
+ <input
3327
+ matInput
3328
+ [value]="messagesModel.createRegistryError || ''"
3329
+ (input)="updateMessage('createRegistryError', $any($event.target).value)"
3330
+ />
3331
+ </mat-form-field>
3332
+ <mat-form-field appearance="outline">
3333
+ <mat-label>Sucesso ao atualizar</mat-label>
3334
+ <input
3335
+ matInput
3336
+ [value]="messagesModel.updateRegistrySuccess || ''"
3337
+ (input)="updateMessage('updateRegistrySuccess', $any($event.target).value)"
3338
+ />
3339
+ </mat-form-field>
3340
+ <mat-form-field appearance="outline">
3341
+ <mat-label>Erro ao atualizar</mat-label>
3342
+ <input
3343
+ matInput
3344
+ [value]="messagesModel.updateRegistryError || ''"
3345
+ (input)="updateMessage('updateRegistryError', $any($event.target).value)"
3346
+ />
3347
+ </mat-form-field>
3348
+ </div>
3349
+ </section>
3350
+
3351
+ <section class="mf-section">
3352
+ <div class="mf-section__header">
3353
+ <div>
3354
+ <h3>Confirmações</h3>
3355
+ <p class="mf-help">Textos mostrados antes de ações críticas.</p>
3356
+ </div>
3357
+ </div>
3358
+ <div class="mf-messages-grid">
3359
+ <mat-form-field appearance="outline">
3360
+ <mat-label>Enviar</mat-label>
3361
+ <input
3362
+ matInput
3363
+ [value]="messagesModel.confirmations?.submit || ''"
3364
+ (input)="updateConfirmation('submit', $any($event.target).value)"
3365
+ />
3366
+ </mat-form-field>
3367
+ <mat-form-field appearance="outline">
3368
+ <mat-label>Cancelar</mat-label>
3369
+ <input
3370
+ matInput
3371
+ [value]="messagesModel.confirmations?.cancel || ''"
3372
+ (input)="updateConfirmation('cancel', $any($event.target).value)"
3373
+ />
3374
+ </mat-form-field>
3375
+ <mat-form-field appearance="outline">
3376
+ <mat-label>Resetar</mat-label>
3377
+ <input
3378
+ matInput
3379
+ [value]="messagesModel.confirmations?.reset || ''"
3380
+ (input)="updateConfirmation('reset', $any($event.target).value)"
3381
+ />
3382
+ </mat-form-field>
3383
+ </div>
3384
+ </section>
3385
+
3386
+ <section class="mf-section">
3387
+ <div class="mf-section__header">
3388
+ <div>
3389
+ <h3>Loading</h3>
3390
+ <p class="mf-help">Mensagens exibidas durante operações.</p>
3391
+ </div>
3392
+ </div>
3393
+ <div class="mf-messages-grid">
3394
+ <mat-form-field appearance="outline">
3395
+ <mat-label>Salvar</mat-label>
3396
+ <input
3397
+ matInput
3398
+ [value]="messagesModel.loading?.submit || ''"
3399
+ (input)="updateLoading('submit', $any($event.target).value)"
3400
+ />
3401
+ </mat-form-field>
3402
+ <mat-form-field appearance="outline">
3403
+ <mat-label>Cancelar</mat-label>
3404
+ <input
3405
+ matInput
3406
+ [value]="messagesModel.loading?.cancel || ''"
3407
+ (input)="updateLoading('cancel', $any($event.target).value)"
3408
+ />
3409
+ </mat-form-field>
3410
+ <mat-form-field appearance="outline">
3411
+ <mat-label>Resetar</mat-label>
3412
+ <input
3413
+ matInput
3414
+ [value]="messagesModel.loading?.reset || ''"
3415
+ (input)="updateLoading('reset', $any($event.target).value)"
3416
+ />
3417
+ </mat-form-field>
3418
+ </div>
3419
+ </section>
3420
+
3421
+ <section class="mf-section">
3422
+ <div class="mf-section__header">
3423
+ <div>
3424
+ <h3>Mensagens por ação customizada</h3>
3425
+ <p class="mf-help">Personalize textos por ID de ação.</p>
3426
+ </div>
3427
+ </div>
3428
+ <div class="mf-custom-messages">
3429
+ @if (actionsModel.custom?.length) {
3430
+ @for (action of actionsModel.custom || []; track action.id) {
3431
+ <div class="mf-custom-message-card">
3432
+ <div class="mf-custom-card__title">{{ action.label || action.id }}</div>
3433
+ <div class="mf-custom-card__grid">
3434
+ <mat-form-field appearance="outline">
3435
+ <mat-label>Confirmação</mat-label>
3436
+ <input
3437
+ matInput
3438
+ [value]="messagesModel.customActions?.[action.id || '']?.confirmation || ''"
3439
+ (input)="updateCustomMessage(action, 'confirmation', $any($event.target).value)"
3440
+ />
3441
+ </mat-form-field>
3442
+ <mat-form-field appearance="outline">
3443
+ <mat-label>Sucesso</mat-label>
3444
+ <input
3445
+ matInput
3446
+ [value]="messagesModel.customActions?.[action.id || '']?.success || ''"
3447
+ (input)="updateCustomMessage(action, 'success', $any($event.target).value)"
3448
+ />
3449
+ </mat-form-field>
3450
+ <mat-form-field appearance="outline">
3451
+ <mat-label>Erro</mat-label>
3452
+ <input
3453
+ matInput
3454
+ [value]="messagesModel.customActions?.[action.id || '']?.error || ''"
3455
+ (input)="updateCustomMessage(action, 'error', $any($event.target).value)"
3456
+ />
3457
+ </mat-form-field>
3458
+ </div>
3459
+ </div>
3460
+ }
3461
+ } @else {
3462
+ <p class="mf-empty">Cadastre ações customizadas para configurar mensagens específicas.</p>
3463
+ }
3464
+ </div>
3465
+ </section>
1429
3466
  </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>
3467
+ </mat-tab>
3468
+
3469
+ <mat-tab label="Comportamento">
3470
+ <div class="mf-tab">
3471
+ <section class="mf-section">
3472
+ <div class="mf-section__header">
3473
+ <div>
3474
+ <h3>Comportamento do formulário</h3>
3475
+ <p class="mf-help">Preferências aplicadas pelo host ou runtime.</p>
3476
+ </div>
3477
+ </div>
3478
+ <div class="mf-note">Estas opções são persistidas no config; o host decide como aplicar.</div>
3479
+ <div class="mf-toggle-grid">
3480
+ <mat-slide-toggle
3481
+ [checked]="!!behaviorModel.confirmOnUnsavedChanges"
3482
+ (change)="updateBehavior('confirmOnUnsavedChanges', $event.checked)"
3483
+ >
3484
+ Confirmar ao sair com alterações não salvas
3485
+ </mat-slide-toggle>
3486
+ <mat-slide-toggle
3487
+ [checked]="!!behaviorModel.trackHistory"
3488
+ (change)="updateBehavior('trackHistory', $event.checked)"
3489
+ >
3490
+ Rastrear histórico de alterações
3491
+ </mat-slide-toggle>
3492
+ <mat-slide-toggle
3493
+ [checked]="!!behaviorModel.focusFirstError"
3494
+ (change)="updateBehavior('focusFirstError', $event.checked)"
3495
+ >
3496
+ Focar no primeiro erro ao submeter
3497
+ </mat-slide-toggle>
3498
+ <mat-slide-toggle
3499
+ [checked]="!!behaviorModel.scrollToErrors"
3500
+ (change)="updateBehavior('scrollToErrors', $event.checked)"
3501
+ >
3502
+ Rolar até os erros ao submeter
3503
+ </mat-slide-toggle>
3504
+ <mat-slide-toggle
3505
+ [checked]="!!behaviorModel.clearAfterSave"
3506
+ (change)="updateBehavior('clearAfterSave', $event.checked)"
3507
+ >
3508
+ Limpar formulário após salvar
3509
+ </mat-slide-toggle>
3510
+ <mat-slide-toggle
3511
+ [checked]="!!behaviorModel.reactiveValidation"
3512
+ (change)="updateBehavior('reactiveValidation', $event.checked)"
3513
+ >
3514
+ Validação reativa
3515
+ </mat-slide-toggle>
3516
+ </div>
3517
+ <div class="mf-actions-layout">
3518
+ <mat-form-field appearance="outline">
3519
+ <mat-label>Redirecionar após salvar (URL)</mat-label>
3520
+ <input
3521
+ matInput
3522
+ [value]="behaviorModel.redirectAfterSave || ''"
3523
+ (input)="updateBehavior('redirectAfterSave', $any($event.target).value)"
3524
+ />
3525
+ </mat-form-field>
3526
+ <mat-form-field appearance="outline">
3527
+ <mat-label>Debounce validação reativa (ms)</mat-label>
3528
+ <input
3529
+ matInput
3530
+ type="number"
3531
+ min="0"
3532
+ [value]="behaviorModel.reactiveValidationDebounceMs || 0"
3533
+ (input)="updateBehavior('reactiveValidationDebounceMs', $any($event.target).valueAsNumber || 0)"
3534
+ />
3535
+ </mat-form-field>
3536
+ </div>
3537
+ </section>
1435
3538
  </div>
1436
- </div>
1437
- </div>
3539
+ </mat-tab>
3540
+
3541
+ <mat-tab label="Dicas">
3542
+ <div class="mf-tab">
3543
+ <section class="mf-section">
3544
+ <div class="mf-section__header">
3545
+ <div>
3546
+ <h3>Mensagens de modo</h3>
3547
+ <p class="mf-help">Textos auxiliares exibidos pelo host.</p>
3548
+ </div>
3549
+ </div>
3550
+ <div class="mf-note">Útil para i18n e orientação do usuário.</div>
3551
+ <div class="mf-messages-grid">
3552
+ <mat-form-field appearance="outline">
3553
+ <mat-label>Criar</mat-label>
3554
+ <input
3555
+ matInput
3556
+ [value]="hintsModel.dataModes.create"
3557
+ (input)="updateHints('dataModes', 'create', $any($event.target).value)"
3558
+ />
3559
+ </mat-form-field>
3560
+ <mat-form-field appearance="outline">
3561
+ <mat-label>Editar</mat-label>
3562
+ <input
3563
+ matInput
3564
+ [value]="hintsModel.dataModes.edit"
3565
+ (input)="updateHints('dataModes', 'edit', $any($event.target).value)"
3566
+ />
3567
+ </mat-form-field>
3568
+ <mat-form-field appearance="outline">
3569
+ <mat-label>Visualizar</mat-label>
3570
+ <input
3571
+ matInput
3572
+ [value]="hintsModel.dataModes.view"
3573
+ (input)="updateHints('dataModes', 'view', $any($event.target).value)"
3574
+ />
3575
+ </mat-form-field>
3576
+ </div>
3577
+ </section>
3578
+
3579
+ <section class="mf-section">
3580
+ <div class="mf-section__header">
3581
+ <div>
3582
+ <h3>Dicas de UI</h3>
3583
+ <p class="mf-help">Ajuda contextual para estados do formulário.</p>
3584
+ </div>
3585
+ </div>
3586
+ <div class="mf-messages-grid">
3587
+ <mat-form-field appearance="outline">
3588
+ <mat-label>Apresentação</mat-label>
3589
+ <textarea
3590
+ matInput
3591
+ rows="2"
3592
+ [value]="hintsModel.uiModes.presentation"
3593
+ (input)="updateHints('uiModes', 'presentation', $any($event.target).value)"
3594
+ ></textarea>
3595
+ </mat-form-field>
3596
+ <mat-form-field appearance="outline">
3597
+ <mat-label>Somente leitura</mat-label>
3598
+ <textarea
3599
+ matInput
3600
+ rows="2"
3601
+ [value]="hintsModel.uiModes.readonly"
3602
+ (input)="updateHints('uiModes', 'readonly', $any($event.target).value)"
3603
+ ></textarea>
3604
+ </mat-form-field>
3605
+ <mat-form-field appearance="outline">
3606
+ <mat-label>Desabilitado</mat-label>
3607
+ <textarea
3608
+ matInput
3609
+ rows="2"
3610
+ [value]="hintsModel.uiModes.disabled"
3611
+ (input)="updateHints('uiModes', 'disabled', $any($event.target).value)"
3612
+ ></textarea>
3613
+ </mat-form-field>
3614
+ <mat-form-field appearance="outline">
3615
+ <mat-label>Visível</mat-label>
3616
+ <textarea
3617
+ matInput
3618
+ rows="2"
3619
+ [value]="hintsModel.uiModes.visible"
3620
+ (input)="updateHints('uiModes', 'visible', $any($event.target).value)"
3621
+ ></textarea>
3622
+ </mat-form-field>
3623
+ </div>
3624
+ </section>
3625
+ </div>
3626
+ </mat-tab>
3627
+
3628
+ <mat-tab label="Hooks">
3629
+ <div class="mf-tab">
3630
+ <section class="mf-section">
3631
+ <div class="mf-section__header">
3632
+ <div>
3633
+ <h3>Hooks de ciclo de vida</h3>
3634
+ <p class="mf-help">Defina ações automatizadas por estágio.</p>
3635
+ </div>
3636
+ <div class="mf-actions-row">
3637
+ <button mat-stroked-button type="button" (click)="resetHooks()">
3638
+ Restaurar
3639
+ </button>
3640
+ <button mat-flat-button color="primary" type="button" (click)="applyHooks()">
3641
+ Aplicar hooks
3642
+ </button>
3643
+ </div>
3644
+ </div>
3645
+ <div class="mf-note">Executa apenas se o host registrar hooks disponíveis.</div>
3646
+ <div class="mf-hooks-grid">
3647
+ @for (stage of hookStages; track stage) {
3648
+ <mat-form-field appearance="outline" class="mf-hook-field">
3649
+ <mat-label>{{ hookLabel(stage) }}</mat-label>
3650
+ <textarea
3651
+ matInput
3652
+ rows="4"
3653
+ [value]="hooksText[stage]"
3654
+ (input)="onHookTextChange(stage, $any($event.target).value)"
3655
+ placeholder='[{ "id": "notifySuccess", "priority": 0, "args": {} }]'
3656
+ ></textarea>
3657
+ <button
3658
+ mat-icon-button
3659
+ matSuffix
3660
+ class="help-icon-button"
3661
+ type="button"
3662
+ [matTooltip]="'Array JSON de hooks para ' + stage + ' (id, priority, timeoutMs, args).'"
3663
+ matTooltipPosition="above"
3664
+ >
3665
+ <mat-icon>help_outline</mat-icon>
3666
+ </button>
3667
+ @if (hooksErrors[stage]) {
3668
+ <mat-error>{{ hooksErrors[stage] }}</mat-error>
3669
+ }
3670
+ </mat-form-field>
3671
+ }
3672
+ </div>
3673
+ </section>
3674
+ </div>
3675
+ </mat-tab>
3676
+
3677
+ <mat-tab label="Regras">
3678
+ <div class="mf-tab">
3679
+ <section class="mf-section">
3680
+ <div class="mf-section__header">
3681
+ <div>
3682
+ <h3>Regras de layout</h3>
3683
+ <p class="mf-help">Regras avançadas para visibilidade e comportamento.</p>
3684
+ </div>
3685
+ <div class="mf-actions-row">
3686
+ <button mat-flat-button color="primary" type="button" (click)="applyRules()">
3687
+ Aplicar regras
3688
+ </button>
3689
+ </div>
3690
+ </div>
3691
+ <div class="mf-note">Persistido no config; a execução depende do host.</div>
3692
+ <mat-form-field appearance="outline">
3693
+ <mat-label>formRules (JSON)</mat-label>
3694
+ <textarea
3695
+ matInput
3696
+ rows="6"
3697
+ [value]="formRulesText"
3698
+ (input)="onRulesTextChange($any($event.target).value)"
3699
+ placeholder='[{"id":"rule-1","name":"Obrigatoriedade","targetType":"field","targets":["campo"],"effect":{"condition":"{campo} != null","properties":{"required":true}}}]'
3700
+ ></textarea>
3701
+ <button
3702
+ mat-icon-button
3703
+ matSuffix
3704
+ class="help-icon-button"
3705
+ type="button"
3706
+ [matTooltip]="'Array JSON com regras (id, targetType, targets, effect).'"
3707
+ matTooltipPosition="above"
3708
+ >
3709
+ <mat-icon>help_outline</mat-icon>
3710
+ </button>
3711
+ @if (formRulesError) {
3712
+ <mat-error>{{ formRulesError }}</mat-error>
3713
+ }
3714
+ </mat-form-field>
3715
+ <mat-form-field appearance="outline">
3716
+ <mat-label>formRulesState (JSON)</mat-label>
3717
+ <textarea
3718
+ matInput
3719
+ rows="6"
3720
+ [value]="formRulesStateText"
3721
+ (input)="onRulesStateTextChange($any($event.target).value)"
3722
+ placeholder='{"nodes":[],"edges":[]}'
3723
+ ></textarea>
3724
+ <button
3725
+ mat-icon-button
3726
+ matSuffix
3727
+ class="help-icon-button"
3728
+ type="button"
3729
+ [matTooltip]="'Estado bruto do editor visual (opcional).'"
3730
+ matTooltipPosition="above"
3731
+ >
3732
+ <mat-icon>help_outline</mat-icon>
3733
+ </button>
3734
+ @if (formRulesStateError) {
3735
+ <mat-error>{{ formRulesStateError }}</mat-error>
3736
+ }
3737
+ </mat-form-field>
3738
+ <div class="mf-actions-row mf-actions-row--end">
3739
+ <button mat-flat-button color="primary" type="button" (click)="applyRules()">
3740
+ Aplicar regras
3741
+ </button>
3742
+ </div>
3743
+ </section>
3744
+ </div>
3745
+ </mat-tab>
3746
+
3747
+ <mat-tab label="Cascatas">
3748
+ <div class="mf-tab">
3749
+ <section class="mf-section mf-section--full">
3750
+ <div class="mf-section__header">
3751
+ <div>
3752
+ <h3>Dependências entre campos</h3>
3753
+ <p class="mf-help">Configure cascatas nativas baseadas em metadados.</p>
3754
+ </div>
3755
+ </div>
3756
+ <div class="mf-note">As alterações atualizam metadados e são salvas no draft.</div>
3757
+ <praxis-cascade-manager-tab
3758
+ [fields]="cascadeFields"
3759
+ (apply)="applyCascadePatch($event)"
3760
+ ></praxis-cascade-manager-tab>
3761
+ </section>
3762
+ </div>
3763
+ </mat-tab>
3764
+ </mat-tab-group>
1438
3765
  </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"] }] });
3766
+ `, isInline: true, styles: [".mf-editor{display:grid;gap:16px;padding:16px;color:var(--md-sys-color-on-surface)}.mf-editor__toolbar{display:flex;align-items:center;gap:12px;padding:12px 14px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface-container)}.mf-editor__title{display:flex;flex-direction:column;gap:2px;font-weight:600}.mf-editor__subtitle{font-weight:400;color:var(--md-sys-color-on-surface-variant)}.mf-editor__toolbar .spacer{flex:1}.mf-tabs{min-height:420px}.mf-tab{display:grid;gap:16px;padding:14px 6px 0}.mf-editor__list{display:grid;gap:8px;overflow-x:auto}.mf-editor__row{display:grid;grid-template-columns:2.2fr 2fr 1fr repeat(4,minmax(90px,.8fr));align-items:center;gap:8px;padding:10px 12px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;background:var(--md-sys-color-surface);min-width:720px}.mf-editor__row--head{font-weight:600;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface-container)}.mf-editor__filter{display:inline-flex;align-items:center;gap:8px;font-weight:500}.mf-editor__filter input{accent-color:var(--md-sys-color-primary)}.col code{display:inline-block;font-family:inherit;font-size:.85rem;padding:2px 8px;border-radius:6px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.mf-btn{min-height:36px;padding:0 12px;border-radius:8px;border:1px solid var(--md-sys-color-outline);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);cursor:pointer;transition:background-color .18s ease,border-color .18s ease,color .18s ease}.mf-btn:hover{background:var(--md-sys-color-surface-container)}.mf-btn:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.toggle{display:inline-flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface-variant)}.mf-section{display:grid;gap:12px;padding:16px;border-radius:12px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.mf-section--full{padding:10px}.mf-section__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.mf-section__header h3{margin:0;font-size:1.05rem;font-weight:600}.mf-help{margin:6px 0 0;font-size:.9rem;color:var(--md-sys-color-on-surface-variant)}.mf-note{padding:10px 12px;border-radius:10px;border:1px dashed var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant);font-size:.9rem}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;margin-right:-4px}.help-icon-button mat-icon{font-size:18px;width:18px;height:18px}.mat-mdc-form-field-icon-suffix{align-self:center}.mf-actions-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px}.mf-action-card,.mf-custom-card,.mf-custom-message-card{display:grid;gap:10px;padding:14px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-high)}.mf-action-card__title,.mf-custom-card__title{font-weight:600;color:var(--md-sys-color-on-surface)}.mf-actions-layout{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;align-items:center}.mf-toggle-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px;align-items:start}.mf-hooks-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}.mf-hook-field textarea{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.85rem}.mf-actions-row{display:inline-flex;gap:8px;align-items:center}.mf-actions-row--end{justify-content:flex-end;width:100%}.mf-custom-actions,.mf-custom-messages{display:grid;gap:12px}.mf-custom-card__header{display:flex;align-items:center;justify-content:space-between;gap:8px}.mf-custom-card__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;align-items:center}.mf-messages-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px}.mf-empty{margin:0;color:var(--md-sys-color-on-surface-variant)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i6.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i7.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i7.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i9.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: CascadeManagerTabComponent, selector: "praxis-cascade-manager-tab", inputs: ["fields", "connections"], outputs: ["apply", "cancel"] }] });
1440
3767
  }
1441
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFormConfigEditorComponent, decorators: [{
3768
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormConfigEditorComponent, decorators: [{
1442
3769
  type: Component,
1443
- args: [{ selector: 'praxis-manual-form-config-editor', standalone: true, imports: [CommonModule, FormsModule], template: `
3770
+ args: [{ selector: 'praxis-manual-form-config-editor', standalone: true, imports: [
3771
+ CommonModule,
3772
+ FormsModule,
3773
+ MatTabsModule,
3774
+ MatFormFieldModule,
3775
+ MatInputModule,
3776
+ MatSelectModule,
3777
+ MatSlideToggleModule,
3778
+ MatButtonModule,
3779
+ MatIconModule,
3780
+ MatTooltipModule,
3781
+ CascadeManagerTabComponent,
3782
+ ], template: `
1444
3783
  <div class="mf-editor">
1445
3784
  <div class="mf-editor__toolbar">
1446
- <label><input type="checkbox" [(ngModel)]="onlyHidden" (change)="applyFilter()" /> Mostrar apenas ocultos</label>
3785
+ <div class="mf-editor__title">
3786
+ <span>Configurações do formulário</span>
3787
+ <small class="mf-editor__subtitle">Campos, ações, mensagens e comportamento</small>
3788
+ </div>
1447
3789
  <div class="spacer"></div>
1448
- <button type="button" (click)="close()">Fechar</button>
3790
+ <button type="button" class="mf-btn" (click)="close()">Fechar</button>
1449
3791
  </div>
1450
3792
 
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>
3793
+ <mat-tab-group class="mf-tabs">
3794
+ <mat-tab label="Campos">
3795
+ <div class="mf-tab">
3796
+ <label class="mf-editor__filter">
3797
+ <input type="checkbox" [(ngModel)]="onlyHidden" (change)="applyFilter()" />
3798
+ <span>Mostrar apenas ocultos</span>
1470
3799
  </label>
3800
+ <div class="mf-editor__list">
3801
+ <div class="mf-editor__row mf-editor__row--head">
3802
+ <div class="col col--name">Campo</div>
3803
+ <div class="col col--label">Rótulo</div>
3804
+ <div class="col col--type">Tipo</div>
3805
+ <div class="col col--vis">Visível</div>
3806
+ <div class="col col--req">Obrigatório</div>
3807
+ <div class="col col--ro">Som. leitura</div>
3808
+ <div class="col col--dis">Desabilitado</div>
3809
+ </div>
3810
+
3811
+ @for (f of filtered; track f.name) {
3812
+ <div class="mf-editor__row">
3813
+ <div class="col col--name"><code>{{ f.name }}</code></div>
3814
+ <div class="col col--label">{{ f.label || f.name }}</div>
3815
+ <div class="col col--type">{{ f.controlType }}</div>
3816
+ <div class="col col--vis">
3817
+ <label class="toggle">
3818
+ <input type="checkbox" [checked]="!f.hidden" (change)="toggleVisibility(f, $any($event.target).checked)" />
3819
+ <span>{{ f.hidden ? 'Oculto' : 'Visível' }}</span>
3820
+ </label>
3821
+ </div>
3822
+ <div class="col col--req">
3823
+ <label class="toggle">
3824
+ <input type="checkbox" [checked]="!!f.required" (change)="toggleRequired(f, $any($event.target).checked)" />
3825
+ <span>{{ f.required ? 'Sim' : 'Não' }}</span>
3826
+ </label>
3827
+ </div>
3828
+ <div class="col col--ro">
3829
+ <label class="toggle">
3830
+ <input type="checkbox" [checked]="!!f.readOnly" (change)="toggleReadOnly(f, $any($event.target).checked)" />
3831
+ <span>{{ f.readOnly ? 'Sim' : 'Não' }}</span>
3832
+ </label>
3833
+ </div>
3834
+ <div class="col col--dis">
3835
+ <label class="toggle">
3836
+ <input type="checkbox" [checked]="!!f.disabled" (change)="toggleDisabled(f, $any($event.target).checked)" />
3837
+ <span>{{ f.disabled ? 'Sim' : 'Não' }}</span>
3838
+ </label>
3839
+ </div>
3840
+ </div>
3841
+ }
3842
+ </div>
1471
3843
  </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>
3844
+ </mat-tab>
3845
+
3846
+ <mat-tab label="Ações">
3847
+ <div class="mf-tab">
3848
+ <section class="mf-section">
3849
+ <div class="mf-section__header">
3850
+ <div>
3851
+ <h3>Botões padrão</h3>
3852
+ <p class="mf-help">Controle rótulos, visibilidade e estilo dos botões básicos.</p>
3853
+ </div>
3854
+ </div>
3855
+ <div class="mf-actions-grid">
3856
+ @for (block of actionBlocks; track block.key) {
3857
+ <div class="mf-action-card">
3858
+ <div class="mf-action-card__title">{{ block.label }}</div>
3859
+ <mat-slide-toggle
3860
+ [checked]="actionsModel[block.key].visible"
3861
+ (change)="updateActionField(block.key, 'visible', $event.checked)"
3862
+ >
3863
+ Visível
3864
+ </mat-slide-toggle>
3865
+ <mat-form-field appearance="outline">
3866
+ <mat-label>Label</mat-label>
3867
+ <input
3868
+ matInput
3869
+ [value]="actionsModel[block.key].label"
3870
+ (input)="updateActionField(block.key, 'label', $any($event.target).value)"
3871
+ />
3872
+ </mat-form-field>
3873
+ <mat-form-field appearance="outline">
3874
+ <mat-label>Ícone</mat-label>
3875
+ <input
3876
+ matInput
3877
+ [value]="actionsModel[block.key].icon || ''"
3878
+ (input)="updateActionField(block.key, 'icon', $any($event.target).value)"
3879
+ />
3880
+ </mat-form-field>
3881
+ <mat-form-field appearance="outline">
3882
+ <mat-label>Variante</mat-label>
3883
+ <mat-select
3884
+ [value]="actionsModel[block.key].variant || null"
3885
+ (selectionChange)="updateActionField(block.key, 'variant', $event.value)"
3886
+ >
3887
+ <mat-option [value]="null">Padrão</mat-option>
3888
+ <mat-option value="raised">Elevado</mat-option>
3889
+ <mat-option value="stroked">Contornado</mat-option>
3890
+ <mat-option value="flat">Plano</mat-option>
3891
+ <mat-option value="fab">Flutuante</mat-option>
3892
+ </mat-select>
3893
+ </mat-form-field>
3894
+ <mat-form-field appearance="outline">
3895
+ <mat-label>Cor</mat-label>
3896
+ <mat-select
3897
+ [value]="actionsModel[block.key].color || null"
3898
+ (selectionChange)="updateActionField(block.key, 'color', $event.value)"
3899
+ >
3900
+ <mat-option [value]="null">Padrão</mat-option>
3901
+ <mat-option value="primary">Primary</mat-option>
3902
+ <mat-option value="accent">Accent</mat-option>
3903
+ <mat-option value="warn">Warn</mat-option>
3904
+ <mat-option value="basic">Basic</mat-option>
3905
+ </mat-select>
3906
+ </mat-form-field>
3907
+ <mat-form-field appearance="outline">
3908
+ <mat-label>Atalho</mat-label>
3909
+ <input
3910
+ matInput
3911
+ [value]="actionsModel[block.key].shortcut || ''"
3912
+ (input)="updateActionField(block.key, 'shortcut', $any($event.target).value)"
3913
+ />
3914
+ </mat-form-field>
3915
+ </div>
3916
+ }
3917
+ </div>
3918
+ </section>
3919
+
3920
+ <section class="mf-section">
3921
+ <div class="mf-section__header">
3922
+ <div>
3923
+ <h3>Layout das ações</h3>
3924
+ <p class="mf-help">Define alinhamento e disposição da barra de ações.</p>
3925
+ </div>
3926
+ </div>
3927
+ <div class="mf-actions-layout">
3928
+ <mat-form-field appearance="outline">
3929
+ <mat-label>Posição</mat-label>
3930
+ <mat-select
3931
+ [value]="actionsModel.position || null"
3932
+ (selectionChange)="updateActionsLayout('position', $event.value)"
3933
+ >
3934
+ <mat-option value="left">Esquerda</mat-option>
3935
+ <mat-option value="center">Centro</mat-option>
3936
+ <mat-option value="right">Direita</mat-option>
3937
+ <mat-option value="justified">Justificado</mat-option>
3938
+ <mat-option value="split">Split</mat-option>
3939
+ </mat-select>
3940
+ </mat-form-field>
3941
+ <mat-form-field appearance="outline">
3942
+ <mat-label>Orientação</mat-label>
3943
+ <mat-select
3944
+ [value]="actionsModel.orientation || null"
3945
+ (selectionChange)="updateActionsLayout('orientation', $event.value)"
3946
+ >
3947
+ <mat-option value="horizontal">Horizontal</mat-option>
3948
+ <mat-option value="vertical">Vertical</mat-option>
3949
+ </mat-select>
3950
+ </mat-form-field>
3951
+ <mat-form-field appearance="outline">
3952
+ <mat-label>Espaçamento</mat-label>
3953
+ <mat-select
3954
+ [value]="actionsModel.spacing || null"
3955
+ (selectionChange)="updateActionsLayout('spacing', $event.value)"
3956
+ >
3957
+ <mat-option value="compact">Compacto</mat-option>
3958
+ <mat-option value="normal">Normal</mat-option>
3959
+ <mat-option value="spacious">Espaçoso</mat-option>
3960
+ </mat-select>
3961
+ </mat-form-field>
3962
+ <mat-form-field appearance="outline">
3963
+ <mat-label>Posicionamento</mat-label>
3964
+ <mat-select
3965
+ [value]="actionsModel.placement || null"
3966
+ (selectionChange)="updateActionsLayout('placement', $event.value)"
3967
+ >
3968
+ <mat-option value="afterSections">Após seções</mat-option>
3969
+ <mat-option value="insideLastSection">Dentro da última seção</mat-option>
3970
+ <mat-option value="top">Topo</mat-option>
3971
+ </mat-select>
3972
+ </mat-form-field>
3973
+ <mat-slide-toggle
3974
+ [checked]="!!actionsModel.sticky"
3975
+ (change)="updateActionsLayout('sticky', $event.checked)"
3976
+ >
3977
+ Ações fixas
3978
+ </mat-slide-toggle>
3979
+ <mat-slide-toggle
3980
+ [checked]="!!actionsModel.divider"
3981
+ (change)="updateActionsLayout('divider', $event.checked)"
3982
+ >
3983
+ Mostrar divisor
3984
+ </mat-slide-toggle>
3985
+ </div>
3986
+ </section>
3987
+
3988
+ <section class="mf-section">
3989
+ <div class="mf-section__header mf-section__header--row">
3990
+ <div>
3991
+ <h3>Ações customizadas</h3>
3992
+ <p class="mf-help">Crie botões extras com evento próprio.</p>
3993
+ </div>
3994
+ <button mat-stroked-button type="button" (click)="addCustomAction()">
3995
+ <mat-icon>add</mat-icon>
3996
+ Adicionar ação
3997
+ </button>
3998
+ </div>
3999
+ <div class="mf-custom-actions">
4000
+ @if (actionsModel.custom?.length) {
4001
+ @for (action of actionsModel.custom || []; track action.id) {
4002
+ <div class="mf-custom-card">
4003
+ <div class="mf-custom-card__header">
4004
+ <div class="mf-custom-card__title">{{ action.label || action.id }}</div>
4005
+ <button mat-icon-button type="button" (click)="removeCustomAction(action)">
4006
+ <mat-icon>delete</mat-icon>
4007
+ </button>
4008
+ </div>
4009
+ <div class="mf-custom-card__grid">
4010
+ <mat-form-field appearance="outline">
4011
+ <mat-label>ID</mat-label>
4012
+ <input
4013
+ matInput
4014
+ [value]="action.id || ''"
4015
+ (input)="updateCustomActionField(action, 'id', $any($event.target).value)"
4016
+ />
4017
+ </mat-form-field>
4018
+ <mat-form-field appearance="outline">
4019
+ <mat-label>Label</mat-label>
4020
+ <input
4021
+ matInput
4022
+ [value]="action.label || ''"
4023
+ (input)="updateCustomActionField(action, 'label', $any($event.target).value)"
4024
+ />
4025
+ </mat-form-field>
4026
+ <mat-form-field appearance="outline">
4027
+ <mat-label>Evento (action)</mat-label>
4028
+ <input
4029
+ matInput
4030
+ [value]="action.action || ''"
4031
+ (input)="updateCustomActionField(action, 'action', $any($event.target).value)"
4032
+ />
4033
+ </mat-form-field>
4034
+ <mat-form-field appearance="outline">
4035
+ <mat-label>Ícone</mat-label>
4036
+ <input
4037
+ matInput
4038
+ [value]="action.icon || ''"
4039
+ (input)="updateCustomActionField(action, 'icon', $any($event.target).value)"
4040
+ />
4041
+ </mat-form-field>
4042
+ <mat-form-field appearance="outline">
4043
+ <mat-label>Variante</mat-label>
4044
+ <mat-select
4045
+ [value]="action.variant || null"
4046
+ (selectionChange)="updateCustomActionField(action, 'variant', $event.value)"
4047
+ >
4048
+ <mat-option [value]="null">Padrão</mat-option>
4049
+ <mat-option value="raised">Elevado</mat-option>
4050
+ <mat-option value="stroked">Contornado</mat-option>
4051
+ <mat-option value="flat">Plano</mat-option>
4052
+ <mat-option value="fab">Flutuante</mat-option>
4053
+ </mat-select>
4054
+ </mat-form-field>
4055
+ <mat-form-field appearance="outline">
4056
+ <mat-label>Cor</mat-label>
4057
+ <mat-select
4058
+ [value]="action.color || null"
4059
+ (selectionChange)="updateCustomActionField(action, 'color', $event.value)"
4060
+ >
4061
+ <mat-option [value]="null">Padrão</mat-option>
4062
+ <mat-option value="primary">Primary</mat-option>
4063
+ <mat-option value="accent">Accent</mat-option>
4064
+ <mat-option value="warn">Warn</mat-option>
4065
+ <mat-option value="basic">Basic</mat-option>
4066
+ </mat-select>
4067
+ </mat-form-field>
4068
+ <mat-form-field appearance="outline">
4069
+ <mat-label>Atalho</mat-label>
4070
+ <input
4071
+ matInput
4072
+ [value]="action.shortcut || ''"
4073
+ (input)="updateCustomActionField(action, 'shortcut', $any($event.target).value)"
4074
+ />
4075
+ </mat-form-field>
4076
+ <mat-form-field appearance="outline">
4077
+ <mat-label>Tooltip</mat-label>
4078
+ <input
4079
+ matInput
4080
+ [value]="action.tooltip || ''"
4081
+ (input)="updateCustomActionField(action, 'tooltip', $any($event.target).value)"
4082
+ />
4083
+ </mat-form-field>
4084
+ <mat-slide-toggle
4085
+ [checked]="action.visible !== false"
4086
+ (change)="updateCustomActionField(action, 'visible', $event.checked)"
4087
+ >
4088
+ Visível
4089
+ </mat-slide-toggle>
4090
+ </div>
4091
+ </div>
4092
+ }
4093
+ } @else {
4094
+ <p class="mf-empty">Nenhuma ação customizada cadastrada.</p>
4095
+ }
4096
+ </div>
4097
+ </section>
1477
4098
  </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>
4099
+ </mat-tab>
4100
+
4101
+ <mat-tab label="Mensagens">
4102
+ <div class="mf-tab">
4103
+ <section class="mf-section">
4104
+ <div class="mf-section__header">
4105
+ <div>
4106
+ <h3>Feedback padrão</h3>
4107
+ <p class="mf-help">Mensagens de sucesso/erro exibidas pelo host.</p>
4108
+ </div>
4109
+ </div>
4110
+ <div class="mf-messages-grid">
4111
+ <mat-form-field appearance="outline">
4112
+ <mat-label>Sucesso ao criar</mat-label>
4113
+ <input
4114
+ matInput
4115
+ [value]="messagesModel.createRegistrySuccess || ''"
4116
+ (input)="updateMessage('createRegistrySuccess', $any($event.target).value)"
4117
+ />
4118
+ </mat-form-field>
4119
+ <mat-form-field appearance="outline">
4120
+ <mat-label>Erro ao criar</mat-label>
4121
+ <input
4122
+ matInput
4123
+ [value]="messagesModel.createRegistryError || ''"
4124
+ (input)="updateMessage('createRegistryError', $any($event.target).value)"
4125
+ />
4126
+ </mat-form-field>
4127
+ <mat-form-field appearance="outline">
4128
+ <mat-label>Sucesso ao atualizar</mat-label>
4129
+ <input
4130
+ matInput
4131
+ [value]="messagesModel.updateRegistrySuccess || ''"
4132
+ (input)="updateMessage('updateRegistrySuccess', $any($event.target).value)"
4133
+ />
4134
+ </mat-form-field>
4135
+ <mat-form-field appearance="outline">
4136
+ <mat-label>Erro ao atualizar</mat-label>
4137
+ <input
4138
+ matInput
4139
+ [value]="messagesModel.updateRegistryError || ''"
4140
+ (input)="updateMessage('updateRegistryError', $any($event.target).value)"
4141
+ />
4142
+ </mat-form-field>
4143
+ </div>
4144
+ </section>
4145
+
4146
+ <section class="mf-section">
4147
+ <div class="mf-section__header">
4148
+ <div>
4149
+ <h3>Confirmações</h3>
4150
+ <p class="mf-help">Textos mostrados antes de ações críticas.</p>
4151
+ </div>
4152
+ </div>
4153
+ <div class="mf-messages-grid">
4154
+ <mat-form-field appearance="outline">
4155
+ <mat-label>Enviar</mat-label>
4156
+ <input
4157
+ matInput
4158
+ [value]="messagesModel.confirmations?.submit || ''"
4159
+ (input)="updateConfirmation('submit', $any($event.target).value)"
4160
+ />
4161
+ </mat-form-field>
4162
+ <mat-form-field appearance="outline">
4163
+ <mat-label>Cancelar</mat-label>
4164
+ <input
4165
+ matInput
4166
+ [value]="messagesModel.confirmations?.cancel || ''"
4167
+ (input)="updateConfirmation('cancel', $any($event.target).value)"
4168
+ />
4169
+ </mat-form-field>
4170
+ <mat-form-field appearance="outline">
4171
+ <mat-label>Resetar</mat-label>
4172
+ <input
4173
+ matInput
4174
+ [value]="messagesModel.confirmations?.reset || ''"
4175
+ (input)="updateConfirmation('reset', $any($event.target).value)"
4176
+ />
4177
+ </mat-form-field>
4178
+ </div>
4179
+ </section>
4180
+
4181
+ <section class="mf-section">
4182
+ <div class="mf-section__header">
4183
+ <div>
4184
+ <h3>Loading</h3>
4185
+ <p class="mf-help">Mensagens exibidas durante operações.</p>
4186
+ </div>
4187
+ </div>
4188
+ <div class="mf-messages-grid">
4189
+ <mat-form-field appearance="outline">
4190
+ <mat-label>Salvar</mat-label>
4191
+ <input
4192
+ matInput
4193
+ [value]="messagesModel.loading?.submit || ''"
4194
+ (input)="updateLoading('submit', $any($event.target).value)"
4195
+ />
4196
+ </mat-form-field>
4197
+ <mat-form-field appearance="outline">
4198
+ <mat-label>Cancelar</mat-label>
4199
+ <input
4200
+ matInput
4201
+ [value]="messagesModel.loading?.cancel || ''"
4202
+ (input)="updateLoading('cancel', $any($event.target).value)"
4203
+ />
4204
+ </mat-form-field>
4205
+ <mat-form-field appearance="outline">
4206
+ <mat-label>Resetar</mat-label>
4207
+ <input
4208
+ matInput
4209
+ [value]="messagesModel.loading?.reset || ''"
4210
+ (input)="updateLoading('reset', $any($event.target).value)"
4211
+ />
4212
+ </mat-form-field>
4213
+ </div>
4214
+ </section>
4215
+
4216
+ <section class="mf-section">
4217
+ <div class="mf-section__header">
4218
+ <div>
4219
+ <h3>Mensagens por ação customizada</h3>
4220
+ <p class="mf-help">Personalize textos por ID de ação.</p>
4221
+ </div>
4222
+ </div>
4223
+ <div class="mf-custom-messages">
4224
+ @if (actionsModel.custom?.length) {
4225
+ @for (action of actionsModel.custom || []; track action.id) {
4226
+ <div class="mf-custom-message-card">
4227
+ <div class="mf-custom-card__title">{{ action.label || action.id }}</div>
4228
+ <div class="mf-custom-card__grid">
4229
+ <mat-form-field appearance="outline">
4230
+ <mat-label>Confirmação</mat-label>
4231
+ <input
4232
+ matInput
4233
+ [value]="messagesModel.customActions?.[action.id || '']?.confirmation || ''"
4234
+ (input)="updateCustomMessage(action, 'confirmation', $any($event.target).value)"
4235
+ />
4236
+ </mat-form-field>
4237
+ <mat-form-field appearance="outline">
4238
+ <mat-label>Sucesso</mat-label>
4239
+ <input
4240
+ matInput
4241
+ [value]="messagesModel.customActions?.[action.id || '']?.success || ''"
4242
+ (input)="updateCustomMessage(action, 'success', $any($event.target).value)"
4243
+ />
4244
+ </mat-form-field>
4245
+ <mat-form-field appearance="outline">
4246
+ <mat-label>Erro</mat-label>
4247
+ <input
4248
+ matInput
4249
+ [value]="messagesModel.customActions?.[action.id || '']?.error || ''"
4250
+ (input)="updateCustomMessage(action, 'error', $any($event.target).value)"
4251
+ />
4252
+ </mat-form-field>
4253
+ </div>
4254
+ </div>
4255
+ }
4256
+ } @else {
4257
+ <p class="mf-empty">Cadastre ações customizadas para configurar mensagens específicas.</p>
4258
+ }
4259
+ </div>
4260
+ </section>
1483
4261
  </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>
4262
+ </mat-tab>
4263
+
4264
+ <mat-tab label="Comportamento">
4265
+ <div class="mf-tab">
4266
+ <section class="mf-section">
4267
+ <div class="mf-section__header">
4268
+ <div>
4269
+ <h3>Comportamento do formulário</h3>
4270
+ <p class="mf-help">Preferências aplicadas pelo host ou runtime.</p>
4271
+ </div>
4272
+ </div>
4273
+ <div class="mf-note">Estas opções são persistidas no config; o host decide como aplicar.</div>
4274
+ <div class="mf-toggle-grid">
4275
+ <mat-slide-toggle
4276
+ [checked]="!!behaviorModel.confirmOnUnsavedChanges"
4277
+ (change)="updateBehavior('confirmOnUnsavedChanges', $event.checked)"
4278
+ >
4279
+ Confirmar ao sair com alterações não salvas
4280
+ </mat-slide-toggle>
4281
+ <mat-slide-toggle
4282
+ [checked]="!!behaviorModel.trackHistory"
4283
+ (change)="updateBehavior('trackHistory', $event.checked)"
4284
+ >
4285
+ Rastrear histórico de alterações
4286
+ </mat-slide-toggle>
4287
+ <mat-slide-toggle
4288
+ [checked]="!!behaviorModel.focusFirstError"
4289
+ (change)="updateBehavior('focusFirstError', $event.checked)"
4290
+ >
4291
+ Focar no primeiro erro ao submeter
4292
+ </mat-slide-toggle>
4293
+ <mat-slide-toggle
4294
+ [checked]="!!behaviorModel.scrollToErrors"
4295
+ (change)="updateBehavior('scrollToErrors', $event.checked)"
4296
+ >
4297
+ Rolar até os erros ao submeter
4298
+ </mat-slide-toggle>
4299
+ <mat-slide-toggle
4300
+ [checked]="!!behaviorModel.clearAfterSave"
4301
+ (change)="updateBehavior('clearAfterSave', $event.checked)"
4302
+ >
4303
+ Limpar formulário após salvar
4304
+ </mat-slide-toggle>
4305
+ <mat-slide-toggle
4306
+ [checked]="!!behaviorModel.reactiveValidation"
4307
+ (change)="updateBehavior('reactiveValidation', $event.checked)"
4308
+ >
4309
+ Validação reativa
4310
+ </mat-slide-toggle>
4311
+ </div>
4312
+ <div class="mf-actions-layout">
4313
+ <mat-form-field appearance="outline">
4314
+ <mat-label>Redirecionar após salvar (URL)</mat-label>
4315
+ <input
4316
+ matInput
4317
+ [value]="behaviorModel.redirectAfterSave || ''"
4318
+ (input)="updateBehavior('redirectAfterSave', $any($event.target).value)"
4319
+ />
4320
+ </mat-form-field>
4321
+ <mat-form-field appearance="outline">
4322
+ <mat-label>Debounce validação reativa (ms)</mat-label>
4323
+ <input
4324
+ matInput
4325
+ type="number"
4326
+ min="0"
4327
+ [value]="behaviorModel.reactiveValidationDebounceMs || 0"
4328
+ (input)="updateBehavior('reactiveValidationDebounceMs', $any($event.target).valueAsNumber || 0)"
4329
+ />
4330
+ </mat-form-field>
4331
+ </div>
4332
+ </section>
1489
4333
  </div>
1490
- </div>
1491
- </div>
4334
+ </mat-tab>
4335
+
4336
+ <mat-tab label="Dicas">
4337
+ <div class="mf-tab">
4338
+ <section class="mf-section">
4339
+ <div class="mf-section__header">
4340
+ <div>
4341
+ <h3>Mensagens de modo</h3>
4342
+ <p class="mf-help">Textos auxiliares exibidos pelo host.</p>
4343
+ </div>
4344
+ </div>
4345
+ <div class="mf-note">Útil para i18n e orientação do usuário.</div>
4346
+ <div class="mf-messages-grid">
4347
+ <mat-form-field appearance="outline">
4348
+ <mat-label>Criar</mat-label>
4349
+ <input
4350
+ matInput
4351
+ [value]="hintsModel.dataModes.create"
4352
+ (input)="updateHints('dataModes', 'create', $any($event.target).value)"
4353
+ />
4354
+ </mat-form-field>
4355
+ <mat-form-field appearance="outline">
4356
+ <mat-label>Editar</mat-label>
4357
+ <input
4358
+ matInput
4359
+ [value]="hintsModel.dataModes.edit"
4360
+ (input)="updateHints('dataModes', 'edit', $any($event.target).value)"
4361
+ />
4362
+ </mat-form-field>
4363
+ <mat-form-field appearance="outline">
4364
+ <mat-label>Visualizar</mat-label>
4365
+ <input
4366
+ matInput
4367
+ [value]="hintsModel.dataModes.view"
4368
+ (input)="updateHints('dataModes', 'view', $any($event.target).value)"
4369
+ />
4370
+ </mat-form-field>
4371
+ </div>
4372
+ </section>
4373
+
4374
+ <section class="mf-section">
4375
+ <div class="mf-section__header">
4376
+ <div>
4377
+ <h3>Dicas de UI</h3>
4378
+ <p class="mf-help">Ajuda contextual para estados do formulário.</p>
4379
+ </div>
4380
+ </div>
4381
+ <div class="mf-messages-grid">
4382
+ <mat-form-field appearance="outline">
4383
+ <mat-label>Apresentação</mat-label>
4384
+ <textarea
4385
+ matInput
4386
+ rows="2"
4387
+ [value]="hintsModel.uiModes.presentation"
4388
+ (input)="updateHints('uiModes', 'presentation', $any($event.target).value)"
4389
+ ></textarea>
4390
+ </mat-form-field>
4391
+ <mat-form-field appearance="outline">
4392
+ <mat-label>Somente leitura</mat-label>
4393
+ <textarea
4394
+ matInput
4395
+ rows="2"
4396
+ [value]="hintsModel.uiModes.readonly"
4397
+ (input)="updateHints('uiModes', 'readonly', $any($event.target).value)"
4398
+ ></textarea>
4399
+ </mat-form-field>
4400
+ <mat-form-field appearance="outline">
4401
+ <mat-label>Desabilitado</mat-label>
4402
+ <textarea
4403
+ matInput
4404
+ rows="2"
4405
+ [value]="hintsModel.uiModes.disabled"
4406
+ (input)="updateHints('uiModes', 'disabled', $any($event.target).value)"
4407
+ ></textarea>
4408
+ </mat-form-field>
4409
+ <mat-form-field appearance="outline">
4410
+ <mat-label>Visível</mat-label>
4411
+ <textarea
4412
+ matInput
4413
+ rows="2"
4414
+ [value]="hintsModel.uiModes.visible"
4415
+ (input)="updateHints('uiModes', 'visible', $any($event.target).value)"
4416
+ ></textarea>
4417
+ </mat-form-field>
4418
+ </div>
4419
+ </section>
4420
+ </div>
4421
+ </mat-tab>
4422
+
4423
+ <mat-tab label="Hooks">
4424
+ <div class="mf-tab">
4425
+ <section class="mf-section">
4426
+ <div class="mf-section__header">
4427
+ <div>
4428
+ <h3>Hooks de ciclo de vida</h3>
4429
+ <p class="mf-help">Defina ações automatizadas por estágio.</p>
4430
+ </div>
4431
+ <div class="mf-actions-row">
4432
+ <button mat-stroked-button type="button" (click)="resetHooks()">
4433
+ Restaurar
4434
+ </button>
4435
+ <button mat-flat-button color="primary" type="button" (click)="applyHooks()">
4436
+ Aplicar hooks
4437
+ </button>
4438
+ </div>
4439
+ </div>
4440
+ <div class="mf-note">Executa apenas se o host registrar hooks disponíveis.</div>
4441
+ <div class="mf-hooks-grid">
4442
+ @for (stage of hookStages; track stage) {
4443
+ <mat-form-field appearance="outline" class="mf-hook-field">
4444
+ <mat-label>{{ hookLabel(stage) }}</mat-label>
4445
+ <textarea
4446
+ matInput
4447
+ rows="4"
4448
+ [value]="hooksText[stage]"
4449
+ (input)="onHookTextChange(stage, $any($event.target).value)"
4450
+ placeholder='[{ "id": "notifySuccess", "priority": 0, "args": {} }]'
4451
+ ></textarea>
4452
+ <button
4453
+ mat-icon-button
4454
+ matSuffix
4455
+ class="help-icon-button"
4456
+ type="button"
4457
+ [matTooltip]="'Array JSON de hooks para ' + stage + ' (id, priority, timeoutMs, args).'"
4458
+ matTooltipPosition="above"
4459
+ >
4460
+ <mat-icon>help_outline</mat-icon>
4461
+ </button>
4462
+ @if (hooksErrors[stage]) {
4463
+ <mat-error>{{ hooksErrors[stage] }}</mat-error>
4464
+ }
4465
+ </mat-form-field>
4466
+ }
4467
+ </div>
4468
+ </section>
4469
+ </div>
4470
+ </mat-tab>
4471
+
4472
+ <mat-tab label="Regras">
4473
+ <div class="mf-tab">
4474
+ <section class="mf-section">
4475
+ <div class="mf-section__header">
4476
+ <div>
4477
+ <h3>Regras de layout</h3>
4478
+ <p class="mf-help">Regras avançadas para visibilidade e comportamento.</p>
4479
+ </div>
4480
+ <div class="mf-actions-row">
4481
+ <button mat-flat-button color="primary" type="button" (click)="applyRules()">
4482
+ Aplicar regras
4483
+ </button>
4484
+ </div>
4485
+ </div>
4486
+ <div class="mf-note">Persistido no config; a execução depende do host.</div>
4487
+ <mat-form-field appearance="outline">
4488
+ <mat-label>formRules (JSON)</mat-label>
4489
+ <textarea
4490
+ matInput
4491
+ rows="6"
4492
+ [value]="formRulesText"
4493
+ (input)="onRulesTextChange($any($event.target).value)"
4494
+ placeholder='[{"id":"rule-1","name":"Obrigatoriedade","targetType":"field","targets":["campo"],"effect":{"condition":"{campo} != null","properties":{"required":true}}}]'
4495
+ ></textarea>
4496
+ <button
4497
+ mat-icon-button
4498
+ matSuffix
4499
+ class="help-icon-button"
4500
+ type="button"
4501
+ [matTooltip]="'Array JSON com regras (id, targetType, targets, effect).'"
4502
+ matTooltipPosition="above"
4503
+ >
4504
+ <mat-icon>help_outline</mat-icon>
4505
+ </button>
4506
+ @if (formRulesError) {
4507
+ <mat-error>{{ formRulesError }}</mat-error>
4508
+ }
4509
+ </mat-form-field>
4510
+ <mat-form-field appearance="outline">
4511
+ <mat-label>formRulesState (JSON)</mat-label>
4512
+ <textarea
4513
+ matInput
4514
+ rows="6"
4515
+ [value]="formRulesStateText"
4516
+ (input)="onRulesStateTextChange($any($event.target).value)"
4517
+ placeholder='{"nodes":[],"edges":[]}'
4518
+ ></textarea>
4519
+ <button
4520
+ mat-icon-button
4521
+ matSuffix
4522
+ class="help-icon-button"
4523
+ type="button"
4524
+ [matTooltip]="'Estado bruto do editor visual (opcional).'"
4525
+ matTooltipPosition="above"
4526
+ >
4527
+ <mat-icon>help_outline</mat-icon>
4528
+ </button>
4529
+ @if (formRulesStateError) {
4530
+ <mat-error>{{ formRulesStateError }}</mat-error>
4531
+ }
4532
+ </mat-form-field>
4533
+ <div class="mf-actions-row mf-actions-row--end">
4534
+ <button mat-flat-button color="primary" type="button" (click)="applyRules()">
4535
+ Aplicar regras
4536
+ </button>
4537
+ </div>
4538
+ </section>
4539
+ </div>
4540
+ </mat-tab>
4541
+
4542
+ <mat-tab label="Cascatas">
4543
+ <div class="mf-tab">
4544
+ <section class="mf-section mf-section--full">
4545
+ <div class="mf-section__header">
4546
+ <div>
4547
+ <h3>Dependências entre campos</h3>
4548
+ <p class="mf-help">Configure cascatas nativas baseadas em metadados.</p>
4549
+ </div>
4550
+ </div>
4551
+ <div class="mf-note">As alterações atualizam metadados e são salvas no draft.</div>
4552
+ <praxis-cascade-manager-tab
4553
+ [fields]="cascadeFields"
4554
+ (apply)="applyCascadePatch($event)"
4555
+ ></praxis-cascade-manager-tab>
4556
+ </section>
4557
+ </div>
4558
+ </mat-tab>
4559
+ </mat-tab-group>
1492
4560
  </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"] }]
4561
+ `, styles: [".mf-editor{display:grid;gap:16px;padding:16px;color:var(--md-sys-color-on-surface)}.mf-editor__toolbar{display:flex;align-items:center;gap:12px;padding:12px 14px;border:1px solid var(--md-sys-color-outline-variant);border-radius:12px;background:var(--md-sys-color-surface-container)}.mf-editor__title{display:flex;flex-direction:column;gap:2px;font-weight:600}.mf-editor__subtitle{font-weight:400;color:var(--md-sys-color-on-surface-variant)}.mf-editor__toolbar .spacer{flex:1}.mf-tabs{min-height:420px}.mf-tab{display:grid;gap:16px;padding:14px 6px 0}.mf-editor__list{display:grid;gap:8px;overflow-x:auto}.mf-editor__row{display:grid;grid-template-columns:2.2fr 2fr 1fr repeat(4,minmax(90px,.8fr));align-items:center;gap:8px;padding:10px 12px;border:1px solid var(--md-sys-color-outline-variant);border-radius:10px;background:var(--md-sys-color-surface);min-width:720px}.mf-editor__row--head{font-weight:600;color:var(--md-sys-color-on-surface);background:var(--md-sys-color-surface-container)}.mf-editor__filter{display:inline-flex;align-items:center;gap:8px;font-weight:500}.mf-editor__filter input{accent-color:var(--md-sys-color-primary)}.col code{display:inline-block;font-family:inherit;font-size:.85rem;padding:2px 8px;border-radius:6px;background:var(--md-sys-color-surface-container-high);color:var(--md-sys-color-on-surface-variant)}.mf-btn{min-height:36px;padding:0 12px;border-radius:8px;border:1px solid var(--md-sys-color-outline);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface);cursor:pointer;transition:background-color .18s ease,border-color .18s ease,color .18s ease}.mf-btn:hover{background:var(--md-sys-color-surface-container)}.mf-btn:focus-visible{outline:2px solid var(--md-sys-color-primary);outline-offset:2px}.toggle{display:inline-flex;align-items:center;gap:6px;color:var(--md-sys-color-on-surface-variant)}.mf-section{display:grid;gap:12px;padding:16px;border-radius:12px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.mf-section--full{padding:10px}.mf-section__header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.mf-section__header h3{margin:0;font-size:1.05rem;font-weight:600}.mf-help{margin:6px 0 0;font-size:.9rem;color:var(--md-sys-color-on-surface-variant)}.mf-note{padding:10px 12px;border-radius:10px;border:1px dashed var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant);font-size:.9rem}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;margin-right:-4px}.help-icon-button mat-icon{font-size:18px;width:18px;height:18px}.mat-mdc-form-field-icon-suffix{align-self:center}.mf-actions-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px}.mf-action-card,.mf-custom-card,.mf-custom-message-card{display:grid;gap:10px;padding:14px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container-high)}.mf-action-card__title,.mf-custom-card__title{font-weight:600;color:var(--md-sys-color-on-surface)}.mf-actions-layout{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;align-items:center}.mf-toggle-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px;align-items:start}.mf-hooks-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}.mf-hook-field textarea{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.85rem}.mf-actions-row{display:inline-flex;gap:8px;align-items:center}.mf-actions-row--end{justify-content:flex-end;width:100%}.mf-custom-actions,.mf-custom-messages{display:grid;gap:12px}.mf-custom-card__header{display:flex;align-items:center;justify-content:space-between;gap:8px}.mf-custom-card__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;align-items:center}.mf-messages-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px}.mf-empty{margin:0;color:var(--md-sys-color-on-surface-variant)}\n"] }]
1494
4562
  }], ctorParameters: () => [{ type: undefined, decorators: [{
1495
4563
  type: Inject,
1496
4564
  args: [SETTINGS_PANEL_DATA]
1497
- }] }, { type: i3.SettingsPanelRef, decorators: [{
4565
+ }] }, { type: i10.SettingsPanelRef, decorators: [{
1498
4566
  type: Inject,
1499
4567
  args: [SETTINGS_PANEL_REF]
1500
4568
  }] }] });
4569
+ const DEFAULT_ACTIONS = {
4570
+ submit: makeAction({ id: 'submit', label: 'Salvar', visible: true, type: 'submit', color: 'primary' }),
4571
+ cancel: makeAction({ id: 'cancel', label: 'Cancelar', visible: false, type: 'button' }),
4572
+ reset: makeAction({ id: 'reset', label: 'Restaurar', visible: true, type: 'reset' }),
4573
+ custom: [],
4574
+ position: 'right',
4575
+ orientation: 'horizontal',
4576
+ spacing: 'normal',
4577
+ };
4578
+ function makeAction(action, index) {
4579
+ const fallbackId = typeof index === 'number' ? `custom-${index + 1}` : 'action';
4580
+ return {
4581
+ id: action.id ?? fallbackId,
4582
+ label: action.label ?? 'Ação',
4583
+ visible: action.visible !== false,
4584
+ type: action.type ?? 'button',
4585
+ color: action.color,
4586
+ disabled: action.disabled,
4587
+ icon: action.icon,
4588
+ action: action.action,
4589
+ tooltip: action.tooltip,
4590
+ variant: action.variant,
4591
+ size: action.size,
4592
+ loading: action.loading,
4593
+ shortcut: action.shortcut,
4594
+ };
4595
+ }
4596
+ function mergeAction(base, override) {
4597
+ if (!override) {
4598
+ return base;
4599
+ }
4600
+ return {
4601
+ ...base,
4602
+ ...override,
4603
+ visible: override.visible !== undefined ? override.visible : base.visible,
4604
+ };
4605
+ }
4606
+ function createDefaultHints() {
4607
+ return {
4608
+ dataModes: { create: '', edit: '', view: '' },
4609
+ uiModes: { presentation: '', readonly: '', disabled: '', visible: '' },
4610
+ };
4611
+ }
4612
+ function createHints(source) {
4613
+ if (!source) {
4614
+ return createDefaultHints();
4615
+ }
4616
+ return {
4617
+ dataModes: {
4618
+ create: source.dataModes?.create ?? '',
4619
+ edit: source.dataModes?.edit ?? '',
4620
+ view: source.dataModes?.view ?? '',
4621
+ },
4622
+ uiModes: {
4623
+ presentation: source.uiModes?.presentation ?? '',
4624
+ readonly: source.uiModes?.readonly ?? '',
4625
+ disabled: source.uiModes?.disabled ?? '',
4626
+ visible: source.uiModes?.visible ?? '',
4627
+ },
4628
+ };
4629
+ }
4630
+ function createDefaultHooksText() {
4631
+ return {
4632
+ beforeInit: '[]',
4633
+ afterInit: '[]',
4634
+ beforeValidate: '[]',
4635
+ afterValidate: '[]',
4636
+ beforeSubmit: '[]',
4637
+ afterSubmit: '[]',
4638
+ onError: '[]',
4639
+ };
4640
+ }
4641
+ function buildHooksText(source) {
4642
+ const base = createDefaultHooksText();
4643
+ if (!source)
4644
+ return base;
4645
+ for (const stage of Object.keys(base)) {
4646
+ const list = source[stage];
4647
+ base[stage] = list && list.length ? JSON.stringify(list, null, 2) : '[]';
4648
+ }
4649
+ return base;
4650
+ }
4651
+ function stringifyJson(value, pretty) {
4652
+ if (value === undefined)
4653
+ return '';
4654
+ try {
4655
+ return JSON.stringify(value, null, pretty ? 2 : 0);
4656
+ }
4657
+ catch {
4658
+ return '';
4659
+ }
4660
+ }
1501
4661
 
1502
4662
  var manualFormConfigEditor_component = /*#__PURE__*/Object.freeze({
1503
4663
  __proto__: null,
1504
4664
  ManualFormConfigEditorComponent: ManualFormConfigEditorComponent
1505
4665
  });
1506
4666
 
4667
+ let nextDocExampleId = 0;
4668
+ class ManualFormDocExampleComponent {
4669
+ title = input('Exemplo didatico', ...(ngDevMode ? [{ debugName: "title" }] : []));
4670
+ subtitle = input(null, ...(ngDevMode ? [{ debugName: "subtitle" }] : []));
4671
+ level = input('Basico', ...(ngDevMode ? [{ debugName: "level" }] : []));
4672
+ bannerTitle = input(null, ...(ngDevMode ? [{ debugName: "bannerTitle" }] : []));
4673
+ bannerDescription = input(null, ...(ngDevMode ? [{ debugName: "bannerDescription" }] : []));
4674
+ templateCode = input('', ...(ngDevMode ? [{ debugName: "templateCode" }] : []));
4675
+ tsCode = input('', ...(ngDevMode ? [{ debugName: "tsCode" }] : []));
4676
+ configCode = input('', ...(ngDevMode ? [{ debugName: "configCode" }] : []));
4677
+ customizationEnabled = input(false, ...(ngDevMode ? [{ debugName: "customizationEnabled" }] : []));
4678
+ showCustomizationToggle = input(true, ...(ngDevMode ? [{ debugName: "showCustomizationToggle" }] : []));
4679
+ important = input(null, ...(ngDevMode ? [{ debugName: "important" }] : []));
4680
+ note = input(null, ...(ngDevMode ? [{ debugName: "note" }] : []));
4681
+ tip = input(null, ...(ngDevMode ? [{ debugName: "tip" }] : []));
4682
+ activeTab = signal('live', ...(ngDevMode ? [{ debugName: "activeTab" }] : []));
4683
+ copyStatus = signal('', ...(ngDevMode ? [{ debugName: "copyStatus" }] : []));
4684
+ tabs = [
4685
+ { id: 'live', label: 'Live', hint: 'Renderizacao do exemplo' },
4686
+ { id: 'template', label: 'Template', hint: 'Markup HTML do exemplo' },
4687
+ { id: 'ts', label: 'TS', hint: 'Logica e estado do host' },
4688
+ { id: 'config', label: 'Config', hint: 'JSON de configuracao do componente' },
4689
+ ];
4690
+ selectedSnippet = computed(() => {
4691
+ switch (this.activeTab()) {
4692
+ case 'template':
4693
+ return this.templateCode();
4694
+ case 'ts':
4695
+ return this.tsCode();
4696
+ case 'config':
4697
+ return this.configCode();
4698
+ default:
4699
+ return '';
4700
+ }
4701
+ }, ...(ngDevMode ? [{ debugName: "selectedSnippet" }] : []));
4702
+ selectedSnippetLabel = computed(() => {
4703
+ switch (this.activeTab()) {
4704
+ case 'template':
4705
+ return 'Template';
4706
+ case 'ts':
4707
+ return 'TypeScript';
4708
+ case 'config':
4709
+ return 'Config';
4710
+ default:
4711
+ return 'Live';
4712
+ }
4713
+ }, ...(ngDevMode ? [{ debugName: "selectedSnippetLabel" }] : []));
4714
+ canCopySnippet = computed(() => this.selectedSnippet().trim().length > 0, ...(ngDevMode ? [{ debugName: "canCopySnippet" }] : []));
4715
+ tabChange = output();
4716
+ customizationEnabledChange = output();
4717
+ codeCopied = output();
4718
+ idPrefix = `pdx-manual-form-doc-example-${nextDocExampleId++}`;
4719
+ copyStatusTimer = null;
4720
+ ngOnDestroy() {
4721
+ this.clearCopyStatusTimer();
4722
+ }
4723
+ setTab(tab) {
4724
+ if (this.activeTab() === tab) {
4725
+ return;
4726
+ }
4727
+ this.activeTab.set(tab);
4728
+ this.setCopyStatus('');
4729
+ this.tabChange.emit(tab);
4730
+ }
4731
+ toggleCustomization() {
4732
+ this.customizationEnabledChange.emit(!this.customizationEnabled());
4733
+ }
4734
+ tabId(tab) {
4735
+ return `${this.idPrefix}-tab-${tab}`;
4736
+ }
4737
+ panelId(tab) {
4738
+ return `${this.idPrefix}-panel-${tab}`;
4739
+ }
4740
+ async copyActiveSnippet() {
4741
+ const tab = this.activeTab();
4742
+ const content = this.selectedSnippet();
4743
+ if (!content.trim()) {
4744
+ this.setCopyStatus('Sem conteudo para copiar');
4745
+ this.codeCopied.emit({ tab, success: false });
4746
+ return;
4747
+ }
4748
+ try {
4749
+ const clipboard = globalThis.navigator?.clipboard;
4750
+ if (!clipboard?.writeText) {
4751
+ this.setCopyStatus('Clipboard indisponivel');
4752
+ this.codeCopied.emit({ tab, success: false });
4753
+ return;
4754
+ }
4755
+ await clipboard.writeText(content);
4756
+ this.setCopyStatus('Copiado');
4757
+ this.codeCopied.emit({ tab, success: true });
4758
+ }
4759
+ catch {
4760
+ this.setCopyStatus('Falha ao copiar');
4761
+ this.codeCopied.emit({ tab, success: false });
4762
+ }
4763
+ }
4764
+ setCopyStatus(status) {
4765
+ this.copyStatus.set(status);
4766
+ this.clearCopyStatusTimer();
4767
+ if (status) {
4768
+ this.copyStatusTimer = setTimeout(() => this.copyStatus.set(''), 1800);
4769
+ }
4770
+ }
4771
+ clearCopyStatusTimer() {
4772
+ if (this.copyStatusTimer) {
4773
+ clearTimeout(this.copyStatusTimer);
4774
+ this.copyStatusTimer = null;
4775
+ }
4776
+ }
4777
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormDocExampleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4778
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ManualFormDocExampleComponent, isStandalone: true, selector: "praxis-manual-form-doc-example", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, level: { classPropertyName: "level", publicName: "level", isSignal: true, isRequired: false, transformFunction: null }, bannerTitle: { classPropertyName: "bannerTitle", publicName: "bannerTitle", isSignal: true, isRequired: false, transformFunction: null }, bannerDescription: { classPropertyName: "bannerDescription", publicName: "bannerDescription", isSignal: true, isRequired: false, transformFunction: null }, templateCode: { classPropertyName: "templateCode", publicName: "templateCode", isSignal: true, isRequired: false, transformFunction: null }, tsCode: { classPropertyName: "tsCode", publicName: "tsCode", isSignal: true, isRequired: false, transformFunction: null }, configCode: { classPropertyName: "configCode", publicName: "configCode", isSignal: true, isRequired: false, transformFunction: null }, customizationEnabled: { classPropertyName: "customizationEnabled", publicName: "customizationEnabled", isSignal: true, isRequired: false, transformFunction: null }, showCustomizationToggle: { classPropertyName: "showCustomizationToggle", publicName: "showCustomizationToggle", isSignal: true, isRequired: false, transformFunction: null }, important: { classPropertyName: "important", publicName: "important", isSignal: true, isRequired: false, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null }, tip: { classPropertyName: "tip", publicName: "tip", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { tabChange: "tabChange", customizationEnabledChange: "customizationEnabledChange", codeCopied: "codeCopied" }, ngImport: i0, template: "<section class=\"doc-example\" [attr.data-level]=\"level()\">\n @if (bannerTitle() || bannerDescription()) {\n <header class=\"doc-example__banner\">\n <div class=\"doc-example__banner-copy\">\n @if (bannerTitle()) {\n <h2>{{ bannerTitle() }}</h2>\n }\n @if (bannerDescription()) {\n <p>{{ bannerDescription() }}</p>\n }\n </div>\n <div class=\"doc-example__banner-art\">\n <ng-content select=\"[exampleBannerArtwork]\"></ng-content>\n </div>\n </header>\n }\n\n <div class=\"doc-example__summary\">\n <div class=\"doc-example__summary-main\">\n <span class=\"badge badge--level\">{{ level() }}</span>\n <h3>{{ title() }}</h3>\n @if (subtitle()) {\n <p>{{ subtitle() }}</p>\n }\n </div>\n\n @if (showCustomizationToggle()) {\n <button\n type=\"button\"\n class=\"mode-toggle\"\n [attr.aria-pressed]=\"customizationEnabled()\"\n [attr.aria-label]=\"customizationEnabled() ? 'Desligar modo de customizacao' : 'Ligar modo de customizacao'\"\n (click)=\"toggleCustomization()\"\n >\n {{ customizationEnabled() ? 'Customizacao: ligada' : 'Customizacao: desligada' }}\n </button>\n }\n </div>\n\n <p class=\"sr-only\" [attr.id]=\"idPrefix + '-tabs-help'\">\n Escolha entre visualizacao live, template, TypeScript ou config do exemplo.\n </p>\n\n <div class=\"doc-example__tabs\" role=\"tablist\" [attr.aria-describedby]=\"idPrefix + '-tabs-help'\">\n @for (tab of tabs; track tab.id) {\n <button\n type=\"button\"\n role=\"tab\"\n class=\"doc-example__tab\"\n [class.is-active]=\"activeTab() === tab.id\"\n [attr.id]=\"tabId(tab.id)\"\n [attr.aria-controls]=\"panelId(tab.id)\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n [attr.aria-label]=\"tab.label + ': ' + tab.hint\"\n (click)=\"setTab(tab.id)\"\n >\n {{ tab.label }}\n </button>\n }\n </div>\n\n <section\n class=\"doc-example__panel\"\n role=\"tabpanel\"\n [attr.id]=\"panelId(activeTab())\"\n [attr.aria-labelledby]=\"tabId(activeTab())\"\n >\n @if (activeTab() === 'live') {\n <div class=\"doc-example__live\">\n <ng-content select=\"[exampleLive]\"></ng-content>\n </div>\n } @else {\n <div class=\"doc-example__code-toolbar\">\n <span class=\"doc-example__code-label\">{{ selectedSnippetLabel() }}</span>\n <button type=\"button\" class=\"copy-btn\" [disabled]=\"!canCopySnippet()\" (click)=\"copyActiveSnippet()\">\n Copiar\n </button>\n @if (copyStatus()) {\n <span class=\"copy-status\" aria-live=\"polite\">{{ copyStatus() }}</span>\n }\n </div>\n <pre class=\"doc-example__code\"><code>{{ selectedSnippet() }}</code></pre>\n }\n </section>\n\n <ng-content select=\"[exampleDiagram]\"></ng-content>\n\n @if (important() || note() || tip()) {\n <div class=\"doc-example__callouts\">\n @if (important()) {\n <p class=\"callout callout--important\"><strong>IMPORTANTE:</strong> {{ important() }}</p>\n }\n @if (note()) {\n <p class=\"callout callout--note\"><strong>NOTA:</strong> {{ note() }}</p>\n }\n @if (tip()) {\n <p class=\"callout callout--tip\"><strong>DICA:</strong> {{ tip() }}</p>\n }\n </div>\n }\n</section>\n", styles: [":host{display:block}.doc-example{--doc-accent: #007a6f;--doc-surface: var(--md-sys-color-surface, #fff);--doc-outline: color-mix(in srgb, var(--doc-accent) 30%, #d6dde1);display:grid;gap:16px;padding:18px;border-radius:18px;border:1px solid var(--doc-outline);background:radial-gradient(140% 120% at 0% 0%,color-mix(in srgb,var(--doc-accent) 10%,transparent),transparent 55%),var(--doc-surface);box-shadow:0 10px 22px color-mix(in srgb,var(--doc-accent) 14%,transparent)}.doc-example__banner{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:14px;padding:16px;border-radius:14px;background:linear-gradient(130deg,color-mix(in srgb,var(--doc-accent) 22%,#10141a),color-mix(in srgb,var(--doc-accent) 10%,#1e2630));color:#f7fbff}.doc-example__banner-copy h2{margin:0 0 6px;font-size:1.35rem;line-height:1.2}.doc-example__banner-copy p{margin:0;color:color-mix(in srgb,#f7fbff 90%,transparent)}.doc-example__banner-art{align-self:center;justify-self:end}.doc-example__summary{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;flex-wrap:wrap}.doc-example__summary-main{display:grid;gap:4px}.doc-example__summary-main h3{margin:0;font-size:1.1rem}.doc-example__summary-main p{margin:0;color:var(--md-sys-color-on-surface-variant, #454e59)}.badge{width:fit-content;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.badge--level{background:color-mix(in srgb,var(--doc-accent) 14%,#ffffff);color:color-mix(in srgb,var(--doc-accent) 86%,#0f151b);border:1px solid color-mix(in srgb,var(--doc-accent) 40%,#cfd6dc)}.mode-toggle{min-height:36px;border-radius:10px;border:1px solid color-mix(in srgb,var(--doc-accent) 44%,#cad2d8);background:color-mix(in srgb,var(--doc-accent) 12%,#ffffff);color:color-mix(in srgb,var(--doc-accent) 84%,#182128);font-weight:600;padding:0 12px;cursor:pointer}.mode-toggle:focus-visible,.doc-example__tab:focus-visible,.copy-btn:focus-visible{outline:2px solid color-mix(in srgb,var(--doc-accent) 72%,#4d5b66);outline-offset:2px}.doc-example__tabs{display:flex;flex-wrap:wrap;gap:8px}.doc-example__tab{min-height:34px;padding:0 12px;border-radius:999px;border:1px solid color-mix(in srgb,var(--doc-accent) 30%,#cfd6dc);background:color-mix(in srgb,var(--doc-accent) 7%,#ffffff);color:var(--md-sys-color-on-surface, #171b21);font-weight:600;cursor:pointer}.doc-example__tab.is-active{border-color:color-mix(in srgb,var(--doc-accent) 62%,#8fa3b0);background:color-mix(in srgb,var(--doc-accent) 78%,#1a2732);color:#f4fbff}.doc-example__panel{border-radius:14px;border:1px solid color-mix(in srgb,var(--doc-accent) 24%,#d2d9de);background:color-mix(in srgb,var(--doc-accent) 5%,#ffffff);padding:14px}.doc-example__live{min-height:80px}.doc-example__code-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:10px}.doc-example__code-label{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--md-sys-color-on-surface-variant, #4f5861)}.copy-btn{min-height:30px;padding:0 10px;border-radius:8px;border:1px solid color-mix(in srgb,var(--doc-accent) 28%,#ccd3d8);background:#fff;cursor:pointer}.copy-btn:disabled{opacity:.6;cursor:not-allowed}.copy-status{color:color-mix(in srgb,var(--doc-accent) 84%,#1f2931);font-size:12px}.doc-example__code{margin:0;max-height:360px;overflow:auto;border-radius:10px;border:1px solid color-mix(in srgb,var(--doc-accent) 22%,#d3dbe0);background:#0f1218;color:#e9eef8;padding:12px;font-size:13px;line-height:1.55}.doc-example__callouts{display:grid;gap:10px}.callout{margin:0;padding:12px 14px;border-radius:10px;border-left:4px solid}.callout--important{border-left-color:#ff8f8f;background:#fff4f4}.callout--note{border-left-color:#91b3ff;background:#f1f6ff}.callout--tip{border-left-color:#84d8bf;background:#ecfff8}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}@media(max-width:768px){.doc-example{padding:14px}.doc-example__banner{grid-template-columns:1fr}.doc-example__banner-art{justify-self:start}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4779
+ }
4780
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormDocExampleComponent, decorators: [{
4781
+ type: Component,
4782
+ args: [{ selector: 'praxis-manual-form-doc-example', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"doc-example\" [attr.data-level]=\"level()\">\n @if (bannerTitle() || bannerDescription()) {\n <header class=\"doc-example__banner\">\n <div class=\"doc-example__banner-copy\">\n @if (bannerTitle()) {\n <h2>{{ bannerTitle() }}</h2>\n }\n @if (bannerDescription()) {\n <p>{{ bannerDescription() }}</p>\n }\n </div>\n <div class=\"doc-example__banner-art\">\n <ng-content select=\"[exampleBannerArtwork]\"></ng-content>\n </div>\n </header>\n }\n\n <div class=\"doc-example__summary\">\n <div class=\"doc-example__summary-main\">\n <span class=\"badge badge--level\">{{ level() }}</span>\n <h3>{{ title() }}</h3>\n @if (subtitle()) {\n <p>{{ subtitle() }}</p>\n }\n </div>\n\n @if (showCustomizationToggle()) {\n <button\n type=\"button\"\n class=\"mode-toggle\"\n [attr.aria-pressed]=\"customizationEnabled()\"\n [attr.aria-label]=\"customizationEnabled() ? 'Desligar modo de customizacao' : 'Ligar modo de customizacao'\"\n (click)=\"toggleCustomization()\"\n >\n {{ customizationEnabled() ? 'Customizacao: ligada' : 'Customizacao: desligada' }}\n </button>\n }\n </div>\n\n <p class=\"sr-only\" [attr.id]=\"idPrefix + '-tabs-help'\">\n Escolha entre visualizacao live, template, TypeScript ou config do exemplo.\n </p>\n\n <div class=\"doc-example__tabs\" role=\"tablist\" [attr.aria-describedby]=\"idPrefix + '-tabs-help'\">\n @for (tab of tabs; track tab.id) {\n <button\n type=\"button\"\n role=\"tab\"\n class=\"doc-example__tab\"\n [class.is-active]=\"activeTab() === tab.id\"\n [attr.id]=\"tabId(tab.id)\"\n [attr.aria-controls]=\"panelId(tab.id)\"\n [attr.aria-selected]=\"activeTab() === tab.id\"\n [attr.aria-label]=\"tab.label + ': ' + tab.hint\"\n (click)=\"setTab(tab.id)\"\n >\n {{ tab.label }}\n </button>\n }\n </div>\n\n <section\n class=\"doc-example__panel\"\n role=\"tabpanel\"\n [attr.id]=\"panelId(activeTab())\"\n [attr.aria-labelledby]=\"tabId(activeTab())\"\n >\n @if (activeTab() === 'live') {\n <div class=\"doc-example__live\">\n <ng-content select=\"[exampleLive]\"></ng-content>\n </div>\n } @else {\n <div class=\"doc-example__code-toolbar\">\n <span class=\"doc-example__code-label\">{{ selectedSnippetLabel() }}</span>\n <button type=\"button\" class=\"copy-btn\" [disabled]=\"!canCopySnippet()\" (click)=\"copyActiveSnippet()\">\n Copiar\n </button>\n @if (copyStatus()) {\n <span class=\"copy-status\" aria-live=\"polite\">{{ copyStatus() }}</span>\n }\n </div>\n <pre class=\"doc-example__code\"><code>{{ selectedSnippet() }}</code></pre>\n }\n </section>\n\n <ng-content select=\"[exampleDiagram]\"></ng-content>\n\n @if (important() || note() || tip()) {\n <div class=\"doc-example__callouts\">\n @if (important()) {\n <p class=\"callout callout--important\"><strong>IMPORTANTE:</strong> {{ important() }}</p>\n }\n @if (note()) {\n <p class=\"callout callout--note\"><strong>NOTA:</strong> {{ note() }}</p>\n }\n @if (tip()) {\n <p class=\"callout callout--tip\"><strong>DICA:</strong> {{ tip() }}</p>\n }\n </div>\n }\n</section>\n", styles: [":host{display:block}.doc-example{--doc-accent: #007a6f;--doc-surface: var(--md-sys-color-surface, #fff);--doc-outline: color-mix(in srgb, var(--doc-accent) 30%, #d6dde1);display:grid;gap:16px;padding:18px;border-radius:18px;border:1px solid var(--doc-outline);background:radial-gradient(140% 120% at 0% 0%,color-mix(in srgb,var(--doc-accent) 10%,transparent),transparent 55%),var(--doc-surface);box-shadow:0 10px 22px color-mix(in srgb,var(--doc-accent) 14%,transparent)}.doc-example__banner{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:14px;padding:16px;border-radius:14px;background:linear-gradient(130deg,color-mix(in srgb,var(--doc-accent) 22%,#10141a),color-mix(in srgb,var(--doc-accent) 10%,#1e2630));color:#f7fbff}.doc-example__banner-copy h2{margin:0 0 6px;font-size:1.35rem;line-height:1.2}.doc-example__banner-copy p{margin:0;color:color-mix(in srgb,#f7fbff 90%,transparent)}.doc-example__banner-art{align-self:center;justify-self:end}.doc-example__summary{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;flex-wrap:wrap}.doc-example__summary-main{display:grid;gap:4px}.doc-example__summary-main h3{margin:0;font-size:1.1rem}.doc-example__summary-main p{margin:0;color:var(--md-sys-color-on-surface-variant, #454e59)}.badge{width:fit-content;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase}.badge--level{background:color-mix(in srgb,var(--doc-accent) 14%,#ffffff);color:color-mix(in srgb,var(--doc-accent) 86%,#0f151b);border:1px solid color-mix(in srgb,var(--doc-accent) 40%,#cfd6dc)}.mode-toggle{min-height:36px;border-radius:10px;border:1px solid color-mix(in srgb,var(--doc-accent) 44%,#cad2d8);background:color-mix(in srgb,var(--doc-accent) 12%,#ffffff);color:color-mix(in srgb,var(--doc-accent) 84%,#182128);font-weight:600;padding:0 12px;cursor:pointer}.mode-toggle:focus-visible,.doc-example__tab:focus-visible,.copy-btn:focus-visible{outline:2px solid color-mix(in srgb,var(--doc-accent) 72%,#4d5b66);outline-offset:2px}.doc-example__tabs{display:flex;flex-wrap:wrap;gap:8px}.doc-example__tab{min-height:34px;padding:0 12px;border-radius:999px;border:1px solid color-mix(in srgb,var(--doc-accent) 30%,#cfd6dc);background:color-mix(in srgb,var(--doc-accent) 7%,#ffffff);color:var(--md-sys-color-on-surface, #171b21);font-weight:600;cursor:pointer}.doc-example__tab.is-active{border-color:color-mix(in srgb,var(--doc-accent) 62%,#8fa3b0);background:color-mix(in srgb,var(--doc-accent) 78%,#1a2732);color:#f4fbff}.doc-example__panel{border-radius:14px;border:1px solid color-mix(in srgb,var(--doc-accent) 24%,#d2d9de);background:color-mix(in srgb,var(--doc-accent) 5%,#ffffff);padding:14px}.doc-example__live{min-height:80px}.doc-example__code-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:10px}.doc-example__code-label{font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--md-sys-color-on-surface-variant, #4f5861)}.copy-btn{min-height:30px;padding:0 10px;border-radius:8px;border:1px solid color-mix(in srgb,var(--doc-accent) 28%,#ccd3d8);background:#fff;cursor:pointer}.copy-btn:disabled{opacity:.6;cursor:not-allowed}.copy-status{color:color-mix(in srgb,var(--doc-accent) 84%,#1f2931);font-size:12px}.doc-example__code{margin:0;max-height:360px;overflow:auto;border-radius:10px;border:1px solid color-mix(in srgb,var(--doc-accent) 22%,#d3dbe0);background:#0f1218;color:#e9eef8;padding:12px;font-size:13px;line-height:1.55}.doc-example__callouts{display:grid;gap:10px}.callout{margin:0;padding:12px 14px;border-radius:10px;border-left:4px solid}.callout--important{border-left-color:#ff8f8f;background:#fff4f4}.callout--note{border-left-color:#91b3ff;background:#f1f6ff}.callout--tip{border-left-color:#84d8bf;background:#ecfff8}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}@media(max-width:768px){.doc-example{padding:14px}.doc-example__banner{grid-template-columns:1fr}.doc-example__banner-art{justify-self:start}}\n"] }]
4783
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], level: [{ type: i0.Input, args: [{ isSignal: true, alias: "level", required: false }] }], bannerTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "bannerTitle", required: false }] }], bannerDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "bannerDescription", required: false }] }], templateCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "templateCode", required: false }] }], tsCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "tsCode", required: false }] }], configCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "configCode", required: false }] }], customizationEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "customizationEnabled", required: false }] }], showCustomizationToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCustomizationToggle", required: false }] }], important: [{ type: i0.Input, args: [{ isSignal: true, alias: "important", required: false }] }], note: [{ type: i0.Input, args: [{ isSignal: true, alias: "note", required: false }] }], tip: [{ type: i0.Input, args: [{ isSignal: true, alias: "tip", required: false }] }], tabChange: [{ type: i0.Output, args: ["tabChange"] }], customizationEnabledChange: [{ type: i0.Output, args: ["customizationEnabledChange"] }], codeCopied: [{ type: i0.Output, args: ["codeCopied"] }] } });
4784
+
4785
+ class DocExampleMemoryStorage {
4786
+ map = new Map();
4787
+ loadConfig(key) {
4788
+ return of(this.map.get(key) ?? null);
4789
+ }
4790
+ saveConfig(key, config) {
4791
+ this.map.set(key, config);
4792
+ return of(void 0);
4793
+ }
4794
+ clearConfig(key) {
4795
+ this.map.delete(key);
4796
+ return of(void 0);
4797
+ }
4798
+ }
4799
+ class ManualFormDocExampleShowcaseComponent {
4800
+ customizationEnabled = signal(false, ...(ngDevMode ? [{ debugName: "customizationEnabled" }] : []));
4801
+ form = new FormGroup({
4802
+ nome: new FormControl('', { nonNullable: true, validators: [Validators.required] }),
4803
+ descricao: new FormControl('', { nonNullable: true }),
4804
+ });
4805
+ templateSnippet = `<praxis-manual-form [formGroup]="form" formId="doc-example-cargo">
4806
+ <label>
4807
+ <span>Nome</span>
4808
+ <input type="text" formControlName="nome" />
4809
+ </label>
4810
+
4811
+ <label>
4812
+ <span>Descricao</span>
4813
+ <textarea formControlName="descricao" rows="3"></textarea>
4814
+ </label>
4815
+ </praxis-manual-form>`;
4816
+ tsSnippet = `readonly customizationEnabled = signal(false);
4817
+
4818
+ readonly form = new FormGroup({
4819
+ nome: new FormControl('', { nonNullable: true, validators: [Validators.required] }),
4820
+ descricao: new FormControl('', { nonNullable: true }),
4821
+ });
4822
+
4823
+ setCustomizationEnabled(enabled: boolean): void {
4824
+ this.customizationEnabled.set(enabled);
4825
+ }`;
4826
+ configSnippet = `{
4827
+ "component": "praxis-manual-form-doc-example",
4828
+ "tabs": ["live", "template", "ts", "config"],
4829
+ "defaultTab": "live",
4830
+ "level": "Intermediario"
4831
+ }`;
4832
+ setCustomizationEnabled(enabled) {
4833
+ this.customizationEnabled.set(enabled);
4834
+ }
4835
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormDocExampleShowcaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4836
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: ManualFormDocExampleShowcaseComponent, isStandalone: true, selector: "praxis-manual-form-doc-example-showcase", providers: [
4837
+ { provide: API_URL, useValue: { default: { baseUrl: '/api' } } },
4838
+ { provide: ASYNC_CONFIG_STORAGE, useFactory: () => new DocExampleMemoryStorage() },
4839
+ ], ngImport: i0, template: `
4840
+ <praxis-manual-form-doc-example
4841
+ title="Cadastro de cargo com preview live"
4842
+ subtitle="Exemplo live com abas para implementação."
4843
+ level="Intermediario"
4844
+ bannerTitle="Explore o formulário antes da teoria"
4845
+ bannerDescription="Interaja no live e abra código apenas quando precisar."
4846
+ [templateCode]="templateSnippet"
4847
+ [tsCode]="tsSnippet"
4848
+ [configCode]="configSnippet"
4849
+ [customizationEnabled]="customizationEnabled()"
4850
+ important="Comece pelo live para reduzir friccao de onboarding."
4851
+ note="As abas preservam o estado atual do formulario."
4852
+ tip="Use o toggle para validar variacoes de host sem recarregar."
4853
+ (customizationEnabledChange)="setCustomizationEnabled($event)"
4854
+ >
4855
+ <div exampleBannerArtwork class="showcase-art" aria-hidden="true">
4856
+ <div class="showcase-art__chip">Live</div>
4857
+ <div class="showcase-art__chip">Template</div>
4858
+ <div class="showcase-art__chip">TS</div>
4859
+ <div class="showcase-art__chip">Config</div>
4860
+ </div>
4861
+
4862
+ <div exampleLive class="showcase-live">
4863
+ <p class="showcase-mode" [class.showcase-mode--enabled]="customizationEnabled()">
4864
+ Customizacao no host: {{ customizationEnabled() ? 'ligada' : 'desligada' }}
4865
+ </p>
4866
+
4867
+ <praxis-manual-form
4868
+ [formGroup]="form"
4869
+ formId="doc-example-cargo"
4870
+ formTitle="Cadastro de cargo"
4871
+ formDescription="Exemplo simples com campos nativos para documentacao."
4872
+ [showActions]="false"
4873
+ [enableCustomization]="false"
4874
+ >
4875
+ <label class="showcase-field">
4876
+ <span>Nome</span>
4877
+ <input type="text" formControlName="nome" placeholder="Ex.: Analista de Dados" />
4878
+ </label>
4879
+
4880
+ <label class="showcase-field">
4881
+ <span>Descricao</span>
4882
+ <textarea formControlName="descricao" rows="3" placeholder="Resumo das responsabilidades"></textarea>
4883
+ </label>
4884
+ </praxis-manual-form>
4885
+ </div>
4886
+
4887
+ <div exampleDiagram class="showcase-diagram">
4888
+ <ol>
4889
+ <li>Preencha o formulario e valide comportamento dos campos.</li>
4890
+ <li>Abra a aba necessaria: Template, TS ou Config.</li>
4891
+ <li>Alterne customizacao para comparar o comportamento do host.</li>
4892
+ </ol>
4893
+ </div>
4894
+ </praxis-manual-form-doc-example>
4895
+ `, isInline: true, styles: [":host{display:block}.showcase-art{display:flex;gap:6px;flex-wrap:wrap}.showcase-art__chip{border-radius:999px;padding:4px 8px;font-size:11px;font-weight:700;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #98a3af) 50%,#d7dde3);background:color-mix(in srgb,var(--md-sys-color-primary, #00796b) 14%,#ffffff);color:var(--md-sys-color-on-surface, #151b22)}.showcase-live{display:grid;gap:12px}.showcase-mode{margin:0;border-radius:10px;border:1px solid #d6dde4;background:#f6f8fb;padding:8px 10px;color:#3b4651;font-size:13px}.showcase-mode--enabled{border-color:#5ebdb0;background:#e9f9f5;color:#075146}.showcase-field{display:grid;gap:6px}.showcase-field span{font-size:13px;font-weight:600}.showcase-field input,.showcase-field textarea{width:100%;border-radius:8px;border:1px solid #c8d2dc;padding:9px 10px;font:inherit;color:#1a222b;background:#fff}.showcase-field input:focus-visible,.showcase-field textarea:focus-visible{outline:2px solid #2d7f73;outline-offset:1px}.showcase-diagram{border-radius:12px;border:1px dashed #bdd0db;background:#f8fbff;padding:12px 14px}.showcase-diagram ol{margin:0;padding-left:20px;display:grid;gap:6px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],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: "component", type: ManualFormComponent, selector: "praxis-manual-form", inputs: ["formId", "formTitle", "formDescription", "actions", "showHeader", "showActions", "enableAutoSave", "componentInstanceId", "enableCustomization", "persistenceOptions", "usePathNames", "autoSaveDebounceMs"], outputs: ["submitted", "saved", "reset", "metadataChange"] }, { kind: "component", type: ManualFormDocExampleComponent, selector: "praxis-manual-form-doc-example", inputs: ["title", "subtitle", "level", "bannerTitle", "bannerDescription", "templateCode", "tsCode", "configCode", "customizationEnabled", "showCustomizationToggle", "important", "note", "tip"], outputs: ["tabChange", "customizationEnabledChange", "codeCopied"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4896
+ }
4897
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFormDocExampleShowcaseComponent, decorators: [{
4898
+ type: Component,
4899
+ args: [{ selector: 'praxis-manual-form-doc-example-showcase', standalone: true, imports: [CommonModule, ReactiveFormsModule, ManualFormComponent, ManualFormDocExampleComponent], template: `
4900
+ <praxis-manual-form-doc-example
4901
+ title="Cadastro de cargo com preview live"
4902
+ subtitle="Exemplo live com abas para implementação."
4903
+ level="Intermediario"
4904
+ bannerTitle="Explore o formulário antes da teoria"
4905
+ bannerDescription="Interaja no live e abra código apenas quando precisar."
4906
+ [templateCode]="templateSnippet"
4907
+ [tsCode]="tsSnippet"
4908
+ [configCode]="configSnippet"
4909
+ [customizationEnabled]="customizationEnabled()"
4910
+ important="Comece pelo live para reduzir friccao de onboarding."
4911
+ note="As abas preservam o estado atual do formulario."
4912
+ tip="Use o toggle para validar variacoes de host sem recarregar."
4913
+ (customizationEnabledChange)="setCustomizationEnabled($event)"
4914
+ >
4915
+ <div exampleBannerArtwork class="showcase-art" aria-hidden="true">
4916
+ <div class="showcase-art__chip">Live</div>
4917
+ <div class="showcase-art__chip">Template</div>
4918
+ <div class="showcase-art__chip">TS</div>
4919
+ <div class="showcase-art__chip">Config</div>
4920
+ </div>
4921
+
4922
+ <div exampleLive class="showcase-live">
4923
+ <p class="showcase-mode" [class.showcase-mode--enabled]="customizationEnabled()">
4924
+ Customizacao no host: {{ customizationEnabled() ? 'ligada' : 'desligada' }}
4925
+ </p>
4926
+
4927
+ <praxis-manual-form
4928
+ [formGroup]="form"
4929
+ formId="doc-example-cargo"
4930
+ formTitle="Cadastro de cargo"
4931
+ formDescription="Exemplo simples com campos nativos para documentacao."
4932
+ [showActions]="false"
4933
+ [enableCustomization]="false"
4934
+ >
4935
+ <label class="showcase-field">
4936
+ <span>Nome</span>
4937
+ <input type="text" formControlName="nome" placeholder="Ex.: Analista de Dados" />
4938
+ </label>
4939
+
4940
+ <label class="showcase-field">
4941
+ <span>Descricao</span>
4942
+ <textarea formControlName="descricao" rows="3" placeholder="Resumo das responsabilidades"></textarea>
4943
+ </label>
4944
+ </praxis-manual-form>
4945
+ </div>
4946
+
4947
+ <div exampleDiagram class="showcase-diagram">
4948
+ <ol>
4949
+ <li>Preencha o formulario e valide comportamento dos campos.</li>
4950
+ <li>Abra a aba necessaria: Template, TS ou Config.</li>
4951
+ <li>Alterne customizacao para comparar o comportamento do host.</li>
4952
+ </ol>
4953
+ </div>
4954
+ </praxis-manual-form-doc-example>
4955
+ `, providers: [
4956
+ { provide: API_URL, useValue: { default: { baseUrl: '/api' } } },
4957
+ { provide: ASYNC_CONFIG_STORAGE, useFactory: () => new DocExampleMemoryStorage() },
4958
+ ], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}.showcase-art{display:flex;gap:6px;flex-wrap:wrap}.showcase-art__chip{border-radius:999px;padding:4px 8px;font-size:11px;font-weight:700;border:1px solid color-mix(in srgb,var(--md-sys-color-outline, #98a3af) 50%,#d7dde3);background:color-mix(in srgb,var(--md-sys-color-primary, #00796b) 14%,#ffffff);color:var(--md-sys-color-on-surface, #151b22)}.showcase-live{display:grid;gap:12px}.showcase-mode{margin:0;border-radius:10px;border:1px solid #d6dde4;background:#f6f8fb;padding:8px 10px;color:#3b4651;font-size:13px}.showcase-mode--enabled{border-color:#5ebdb0;background:#e9f9f5;color:#075146}.showcase-field{display:grid;gap:6px}.showcase-field span{font-size:13px;font-weight:600}.showcase-field input,.showcase-field textarea{width:100%;border-radius:8px;border:1px solid #c8d2dc;padding:9px 10px;font:inherit;color:#1a222b;background:#fff}.showcase-field input:focus-visible,.showcase-field textarea:focus-visible{outline:2px solid #2d7f73;outline-offset:1px}.showcase-diagram{border-radius:12px;border:1px dashed #bdd0db;background:#f8fbff;padding:12px 14px}.showcase-diagram ol{margin:0;padding-left:20px;display:grid;gap:6px}\n"] }]
4959
+ }] });
4960
+
1507
4961
  class ManualFieldDirective {
1508
4962
  templateRef;
1509
4963
  viewContainer;
@@ -1531,10 +4985,10 @@ class ManualFieldDirective {
1531
4985
  };
1532
4986
  this.viewContainer.createEmbeddedView(this.templateRef, context);
1533
4987
  }
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 });
4988
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFieldDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Directive });
4989
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.17", type: ManualFieldDirective, isStandalone: true, selector: "[praxisManualField]", inputs: { fieldName: ["praxisManualField", "fieldName"], instance: ["praxisManualFieldInstance", "instance"] }, usesOnChanges: true, ngImport: i0 });
1536
4990
  }
1537
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFieldDirective, decorators: [{
4991
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFieldDirective, decorators: [{
1538
4992
  type: Directive,
1539
4993
  args: [{
1540
4994
  selector: '[praxisManualField]',
@@ -1550,7 +5004,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1550
5004
 
1551
5005
  /**
1552
5006
  * Attach to any field inside <praxis-manual-form> to open the metadata editor
1553
- * on double click, respecting the form's editModeEnabled state.
5007
+ * on double click, respecting the form's enableCustomization state.
1554
5008
  *
1555
5009
  * Usage:
1556
5010
  * <pdx-text-input pdxManualEdit="nome" ...></pdx-text-input>
@@ -1563,10 +5017,10 @@ class ManualFieldEditorOnDblclickDirective {
1563
5017
  return;
1564
5018
  this.manualForm?.tryOpenFieldEditor(this.fieldName);
1565
5019
  }
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 });
5020
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFieldEditorOnDblclickDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
5021
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.17", type: ManualFieldEditorOnDblclickDirective, isStandalone: true, selector: "[pdxManualEdit]", inputs: { fieldName: ["pdxManualEdit", "fieldName"] }, host: { listeners: { "dblclick": "onDblClick()" } }, ngImport: i0 });
1568
5022
  }
1569
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ManualFieldEditorOnDblclickDirective, decorators: [{
5023
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ManualFieldEditorOnDblclickDirective, decorators: [{
1570
5024
  type: Directive,
1571
5025
  args: [{
1572
5026
  selector: '[pdxManualEdit]',
@@ -1588,5 +5042,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1588
5042
  * Generated bundle index. Do not edit.
1589
5043
  */
1590
5044
 
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 };
5045
+ export { MANUAL_FORM_AI_CAPABILITIES, MANUAL_FORM_CONSTRUCTOR_TO_CONTROL_TYPE, MANUAL_FORM_SELECTOR_TO_CONTROL_TYPE, ManualFieldDirective, ManualFieldEditorOnDblclickDirective, ManualFieldKeyService, ManualFieldMetadataBridgeService, ManualFormActionsComponent, ManualFormComponent, ManualFormConfigEditorComponent, ManualFormDocExampleComponent, ManualFormDocExampleShowcaseComponent, ManualFormHeaderComponent, ManualFormInstance, ManualFormInstanceFactory, createManualFormSeed, toFieldMetadataMap };
1592
5046
  //# sourceMappingURL=praxisui-manual-form.mjs.map