@praxisui/tabs 1.0.0-beta.4 → 1.0.0-beta.41

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,17 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { Inject, Component, inject, EventEmitter, signal, Output, Input, ChangeDetectionStrategy, ENVIRONMENT_INITIALIZER } from '@angular/core';
3
+ import { ActivatedRoute } from '@angular/router';
3
4
  import * as i1$1 from '@angular/common';
4
5
  import { CommonModule } from '@angular/common';
5
6
  import * as i3$1 from '@angular/material/tabs';
6
7
  import { MatTabsModule } from '@angular/material/tabs';
7
8
  import * as i7 from '@angular/material/icon';
8
9
  import { MatIconModule } from '@angular/material/icon';
10
+ import * as i11 from '@angular/material/tooltip';
11
+ import { MatTooltipModule } from '@angular/material/tooltip';
9
12
  import * as i1 from '@praxisui/core';
10
- import { PraxisIconDirective, CONFIG_STORAGE, EmptyStateCardComponent, DynamicWidgetLoaderDirective, ComponentMetadataRegistry } from '@praxisui/core';
11
- import * as i5$1 from '@angular/material/button';
13
+ import { PraxisIconDirective, deepMerge, ASYNC_CONFIG_STORAGE, ComponentKeyService, LoggerService, EmptyStateCardComponent, DynamicWidgetLoaderDirective, ComponentMetadataRegistry } from '@praxisui/core';
14
+ import * as i6$1 from '@angular/material/button';
12
15
  import { MatButtonModule } from '@angular/material/button';
13
16
  import * as i3 from '@angular/forms';
14
17
  import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@@ -22,11 +25,11 @@ import * as i9 from '@angular/material/slide-toggle';
22
25
  import { MatSlideToggleModule } from '@angular/material/slide-toggle';
23
26
  import * as i10 from '@angular/cdk/drag-drop';
24
27
  import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
25
- import * as i11 from '@angular/material/tooltip';
26
- import { MatTooltipModule } from '@angular/material/tooltip';
27
- import { BehaviorSubject } from 'rxjs';
28
+ import { BehaviorSubject, Subject } from 'rxjs';
28
29
  import { produce } from 'immer';
29
30
  import { MatSnackBar } from '@angular/material/snack-bar';
31
+ import { take, takeUntil } from 'rxjs/operators';
32
+ import { BaseAiAdapter, PraxisAiAssistantComponent } from '@praxisui/ai';
30
33
 
31
34
  class PraxisTabsConfigEditor {
32
35
  registry;
@@ -57,31 +60,31 @@ class PraxisTabsConfigEditor {
57
60
  ];
58
61
  presets = {
59
62
  primary: {
60
- 'active-indicator-color': 'var(--mat-sys-primary)',
61
- 'active-label-text-color': 'var(--mat-sys-primary)',
62
- 'inactive-label-text-color': 'rgba(var(--mat-sys-on-surface-rgb), 0.72)',
63
- 'inactive-hover-label-text-color': 'var(--mat-sys-on-surface)',
64
- 'divider-color': 'rgba(255, 255, 255, 0.12)',
65
- 'background-color': 'var(--mat-sys-surface-container)',
66
- 'pagination-icon-color': 'var(--mat-sys-on-surface)'
63
+ 'active-indicator-color': 'var(--md-sys-color-primary)',
64
+ 'active-label-text-color': 'var(--md-sys-color-primary)',
65
+ 'inactive-label-text-color': 'var(--md-sys-color-on-surface-variant)',
66
+ 'inactive-hover-label-text-color': 'var(--md-sys-color-on-surface)',
67
+ 'divider-color': 'var(--md-sys-color-outline-variant)',
68
+ 'background-color': 'var(--md-sys-color-surface-container)',
69
+ 'pagination-icon-color': 'var(--md-sys-color-on-surface)'
67
70
  },
68
71
  neutral: {
69
- 'active-indicator-color': 'var(--mat-sys-on-surface)',
70
- 'active-label-text-color': 'var(--mat-sys-on-surface)',
71
- 'inactive-label-text-color': 'rgba(var(--mat-sys-on-surface-rgb), 0.56)',
72
- 'inactive-hover-label-text-color': 'var(--mat-sys-on-surface)',
73
- 'divider-color': 'rgba(255, 255, 255, 0.10)',
74
- 'background-color': 'var(--mat-sys-surface-container)',
75
- 'pagination-icon-color': 'var(--mat-sys-on-surface)'
72
+ 'active-indicator-color': 'var(--md-sys-color-on-surface)',
73
+ 'active-label-text-color': 'var(--md-sys-color-on-surface)',
74
+ 'inactive-label-text-color': 'var(--md-sys-color-on-surface-variant)',
75
+ 'inactive-hover-label-text-color': 'var(--md-sys-color-on-surface)',
76
+ 'divider-color': 'var(--md-sys-color-outline-variant)',
77
+ 'background-color': 'var(--md-sys-color-surface-container)',
78
+ 'pagination-icon-color': 'var(--md-sys-color-on-surface)'
76
79
  },
77
80
  'high-contrast': {
78
- 'active-indicator-color': '#FFD54F',
79
- 'active-label-text-color': '#FFFFFF',
80
- 'inactive-label-text-color': '#BDBDBD',
81
- 'inactive-hover-label-text-color': '#EEEEEE',
82
- 'divider-color': '#FFFFFF',
83
- 'background-color': 'var(--mat-sys-surface)',
84
- 'pagination-icon-color': '#FFFFFF'
81
+ 'active-indicator-color': 'var(--md-sys-color-primary)',
82
+ 'active-label-text-color': 'var(--md-sys-color-on-primary)',
83
+ 'inactive-label-text-color': 'var(--md-sys-color-on-surface-variant)',
84
+ 'inactive-hover-label-text-color': 'var(--md-sys-color-on-surface)',
85
+ 'divider-color': 'var(--md-sys-color-outline)',
86
+ 'background-color': 'var(--md-sys-color-surface)',
87
+ 'pagination-icon-color': 'var(--md-sys-color-on-surface)'
85
88
  }
86
89
  };
87
90
  isDirty$ = new BehaviorSubject(false);
@@ -271,10 +274,10 @@ class PraxisTabsConfigEditor {
271
274
  t.widgets.push({ id: 'praxis-dynamic-form', inputs: { resourcePath: rp } });
272
275
  }
273
276
  else if (type === 'table') {
274
- t.widgets.push({ id: 'praxis-table', inputs: { resourcePath: rp } });
277
+ t.widgets.push({ id: 'praxis-table', inputs: { resourcePath: rp, tableId: rp } });
275
278
  }
276
279
  else {
277
- t.widgets.push({ id: 'praxis-crud', inputs: { metadata: { resource: { path: rp } } } });
280
+ t.widgets.push({ id: 'praxis-crud', inputs: { crudId: rp, metadata: { resource: { path: rp } } } });
278
281
  }
279
282
  this.onAppearanceChange();
280
283
  }
@@ -345,10 +348,10 @@ class PraxisTabsConfigEditor {
345
348
  l.widgets.push({ id: 'praxis-dynamic-form', inputs: { resourcePath: rp } });
346
349
  }
347
350
  else if (type === 'table') {
348
- l.widgets.push({ id: 'praxis-table', inputs: { resourcePath: rp } });
351
+ l.widgets.push({ id: 'praxis-table', inputs: { resourcePath: rp, tableId: rp } });
349
352
  }
350
353
  else {
351
- l.widgets.push({ id: 'praxis-crud', inputs: { metadata: { resource: { path: rp } } } });
354
+ l.widgets.push({ id: 'praxis-crud', inputs: { crudId: rp, metadata: { resource: { path: rp } } } });
352
355
  }
353
356
  this.onAppearanceChange();
354
357
  }
@@ -376,58 +379,108 @@ class PraxisTabsConfigEditor {
376
379
  }
377
380
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisTabsConfigEditor, deps: [{ token: SETTINGS_PANEL_DATA }, { token: i1.ComponentMetadataRegistry }], target: i0.ɵɵFactoryTarget.Component });
378
381
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: PraxisTabsConfigEditor, isStandalone: true, selector: "praxis-tabs-config-editor", ngImport: i0, template: `
379
- <mat-tab-group>
382
+ <mat-tab-group class="editor-tabs">
380
383
  <mat-tab label="Comportamento">
381
- <div style="padding: 12px; display:grid; gap: 12px;">
382
- <div style="display:flex; gap: 10px; flex-wrap: wrap;">
383
- <mat-slide-toggle [(ngModel)]="behavior.closeable" (ngModelChange)="onAppearanceChange()">closeable</mat-slide-toggle>
384
- <mat-slide-toggle [(ngModel)]="behavior.lazyLoad" (ngModelChange)="onAppearanceChange()">lazyLoad (planejado)</mat-slide-toggle>
385
- <mat-slide-toggle [(ngModel)]="behavior.reorderable" (ngModelChange)="onAppearanceChange()">reorderable (planejado)</mat-slide-toggle>
384
+ <div class="editor-section">
385
+ <div class="editor-row">
386
+ <mat-slide-toggle [(ngModel)]="behavior.closeable" (ngModelChange)="onAppearanceChange()">Fechavel</mat-slide-toggle>
387
+ <mat-slide-toggle [(ngModel)]="behavior.lazyLoad" (ngModelChange)="onAppearanceChange()">Carregar sob demanda (planejado)</mat-slide-toggle>
388
+ <mat-slide-toggle [(ngModel)]="behavior.reorderable" (ngModelChange)="onAppearanceChange()">Reordenavel (planejado)</mat-slide-toggle>
386
389
  </div>
387
390
  </div>
388
391
  </mat-tab>
389
392
  <mat-tab label="Grupo">
390
- <div style="padding: 12px; display:grid; gap: 12px;">
391
- <div style="display:grid; grid-template-columns: repeat(2, minmax(220px,1fr)); gap: 12px;">
392
- <mat-form-field appearance="outline"><mat-label>Alinhamento</mat-label>
393
+ <div class="editor-section">
394
+ <div class="editor-grid two">
395
+ <mat-form-field appearance="outline"><mat-label>Alinhamento das abas</mat-label>
393
396
  <select matNativeControl [(ngModel)]="group.alignTabs" (ngModelChange)="onAppearanceChange()">
394
- <option [ngValue]="undefined">padrão</option>
395
- <option value="start">start</option>
396
- <option value="center">center</option>
397
- <option value="end">end</option>
397
+ <option [ngValue]="undefined">Padrao</option>
398
+ <option value="start">Inicio</option>
399
+ <option value="center">Centro</option>
400
+ <option value="end">Fim</option>
398
401
  </select>
399
402
  </mat-form-field>
400
- <mat-form-field appearance="outline"><mat-label>Header</mat-label>
403
+ <mat-form-field appearance="outline"><mat-label>Posicao do header</mat-label>
401
404
  <select matNativeControl [(ngModel)]="group.headerPosition" (ngModelChange)="onAppearanceChange()">
402
- <option [ngValue]="undefined">above</option>
403
- <option value="above">above</option>
404
- <option value="below">below</option>
405
+ <option [ngValue]="undefined">Acima (padrao)</option>
406
+ <option value="above">Acima</option>
407
+ <option value="below">Abaixo</option>
405
408
  </select>
406
409
  </mat-form-field>
407
- <mat-form-field appearance="outline"><mat-label>selectedIndex</mat-label>
410
+ <mat-form-field appearance="outline"><mat-label>Indice selecionado</mat-label>
408
411
  <input matInput type="number" [(ngModel)]="group.selectedIndex" (ngModelChange)="onAppearanceChange()" />
412
+ <button
413
+ mat-icon-button
414
+ matSuffix
415
+ class="help-icon-button"
416
+ type="button"
417
+ [matTooltip]="'selectedIndex'"
418
+ matTooltipPosition="above"
419
+ >
420
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
421
+ </button>
409
422
  </mat-form-field>
410
- <mat-form-field appearance="outline"><mat-label>animationDuration</mat-label>
423
+ <mat-form-field appearance="outline"><mat-label>Duracao da animacao</mat-label>
411
424
  <input matInput [(ngModel)]="group.animationDuration" (ngModelChange)="onAppearanceChange()" placeholder="500ms" />
425
+ <button
426
+ mat-icon-button
427
+ matSuffix
428
+ class="help-icon-button"
429
+ type="button"
430
+ [matTooltip]="'animationDuration'"
431
+ matTooltipPosition="above"
432
+ >
433
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
434
+ </button>
412
435
  </mat-form-field>
413
- <mat-form-field appearance="outline"><mat-label>contentTabIndex</mat-label>
436
+ <mat-form-field appearance="outline"><mat-label>TabIndex do conteudo</mat-label>
414
437
  <input matInput type="number" [(ngModel)]="group.contentTabIndex" (ngModelChange)="onAppearanceChange()" />
438
+ <button
439
+ mat-icon-button
440
+ matSuffix
441
+ class="help-icon-button"
442
+ type="button"
443
+ [matTooltip]="'contentTabIndex'"
444
+ matTooltipPosition="above"
445
+ >
446
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
447
+ </button>
415
448
  </mat-form-field>
416
- <mat-form-field appearance="outline"><mat-label>color (M2)</mat-label>
449
+ <mat-form-field appearance="outline"><mat-label>Cor (M2)</mat-label>
417
450
  <select matNativeControl [(ngModel)]="group.color" (ngModelChange)="onAppearanceChange()">
418
- <option [ngValue]="undefined">(nenhum)</option>
419
- <option value="primary">primary</option>
420
- <option value="accent">accent</option>
421
- <option value="warn">warn</option>
451
+ <option [ngValue]="undefined">(nenhuma)</option>
452
+ <option value="primary">Primary</option>
453
+ <option value="accent">Accent</option>
454
+ <option value="warn">Warn</option>
422
455
  </select>
456
+ <button
457
+ mat-icon-button
458
+ matSuffix
459
+ class="help-icon-button"
460
+ type="button"
461
+ [matTooltip]="'Preferir tokens em Estilo.'"
462
+ matTooltipPosition="above"
463
+ >
464
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
465
+ </button>
423
466
  </mat-form-field>
424
- <mat-form-field appearance="outline"><mat-label>backgroundColor (M2)</mat-label>
467
+ <mat-form-field appearance="outline"><mat-label>Cor de fundo (M2)</mat-label>
425
468
  <select matNativeControl [(ngModel)]="group.backgroundColor" (ngModelChange)="onAppearanceChange()">
426
- <option [ngValue]="undefined">(nenhum)</option>
427
- <option value="primary">primary</option>
428
- <option value="accent">accent</option>
429
- <option value="warn">warn</option>
469
+ <option [ngValue]="undefined">(nenhuma)</option>
470
+ <option value="primary">Primary</option>
471
+ <option value="accent">Accent</option>
472
+ <option value="warn">Warn</option>
430
473
  </select>
474
+ <button
475
+ mat-icon-button
476
+ matSuffix
477
+ class="help-icon-button"
478
+ type="button"
479
+ [matTooltip]="'Preferir tokens em Estilo.'"
480
+ matTooltipPosition="above"
481
+ >
482
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
483
+ </button>
431
484
  </mat-form-field>
432
485
  <mat-form-field appearance="outline"><mat-label>aria-label</mat-label>
433
486
  <input matInput [(ngModel)]="group.ariaLabel" (ngModelChange)="onAppearanceChange()" />
@@ -436,54 +489,94 @@ class PraxisTabsConfigEditor {
436
489
  <input matInput [(ngModel)]="group.ariaLabelledby" (ngModelChange)="onAppearanceChange()" />
437
490
  </mat-form-field>
438
491
  </div>
439
- <div style="display:flex; gap: 10px; flex-wrap: wrap;">
440
- <mat-slide-toggle [(ngModel)]="group.dynamicHeight" (ngModelChange)="onAppearanceChange()">dynamicHeight</mat-slide-toggle>
441
- <mat-slide-toggle [(ngModel)]="group.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">fitInkBarToContent</mat-slide-toggle>
442
- <mat-slide-toggle [(ngModel)]="group.disablePagination" (ngModelChange)="onAppearanceChange()">disablePagination</mat-slide-toggle>
443
- <mat-slide-toggle [(ngModel)]="group.disableRipple" (ngModelChange)="onAppearanceChange()">disableRipple</mat-slide-toggle>
444
- <mat-slide-toggle [(ngModel)]="group.preserveContent" (ngModelChange)="onAppearanceChange()">preserveContent</mat-slide-toggle>
445
- <mat-slide-toggle [(ngModel)]="group.stretchTabs" (ngModelChange)="onAppearanceChange()">stretchTabs</mat-slide-toggle>
492
+ <div class="editor-row">
493
+ <mat-slide-toggle [(ngModel)]="group.dynamicHeight" (ngModelChange)="onAppearanceChange()">Altura dinamica</mat-slide-toggle>
494
+ <mat-slide-toggle [(ngModel)]="group.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">Indicador ajustado ao conteudo</mat-slide-toggle>
495
+ <mat-slide-toggle [(ngModel)]="group.disablePagination" (ngModelChange)="onAppearanceChange()">Sem paginacao</mat-slide-toggle>
496
+ <mat-slide-toggle [(ngModel)]="group.disableRipple" (ngModelChange)="onAppearanceChange()">Sem ripple</mat-slide-toggle>
497
+ <mat-slide-toggle [(ngModel)]="group.preserveContent" (ngModelChange)="onAppearanceChange()">Preservar conteudo</mat-slide-toggle>
498
+ <mat-slide-toggle [(ngModel)]="group.stretchTabs" (ngModelChange)="onAppearanceChange()">Esticar abas</mat-slide-toggle>
446
499
  </div>
447
500
  </div>
448
501
  </mat-tab>
449
502
 
450
503
  <mat-tab label="Navegação">
451
- <div style="padding: 12px; display:grid; gap: 12px;">
452
- <div style="display:grid; grid-template-columns: repeat(2, minmax(220px,1fr)); gap: 12px;">
453
- <mat-form-field appearance="outline"><mat-label>selectedIndex</mat-label>
504
+ <div class="editor-section">
505
+ <div class="editor-grid two">
506
+ <mat-form-field appearance="outline"><mat-label>Indice selecionado</mat-label>
454
507
  <input matInput type="number" [(ngModel)]="nav.selectedIndex" (ngModelChange)="onAppearanceChange()" />
508
+ <button
509
+ mat-icon-button
510
+ matSuffix
511
+ class="help-icon-button"
512
+ type="button"
513
+ [matTooltip]="'selectedIndex'"
514
+ matTooltipPosition="above"
515
+ >
516
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
517
+ </button>
455
518
  </mat-form-field>
456
- <mat-form-field appearance="outline"><mat-label>animationDuration</mat-label>
519
+ <mat-form-field appearance="outline"><mat-label>Duracao da animacao</mat-label>
457
520
  <input matInput [(ngModel)]="nav.animationDuration" (ngModelChange)="onAppearanceChange()" placeholder="500ms" />
521
+ <button
522
+ mat-icon-button
523
+ matSuffix
524
+ class="help-icon-button"
525
+ type="button"
526
+ [matTooltip]="'animationDuration'"
527
+ matTooltipPosition="above"
528
+ >
529
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
530
+ </button>
458
531
  </mat-form-field>
459
- <mat-form-field appearance="outline"><mat-label>color (M2)</mat-label>
532
+ <mat-form-field appearance="outline"><mat-label>Cor (M2)</mat-label>
460
533
  <select matNativeControl [(ngModel)]="nav.color" (ngModelChange)="onAppearanceChange()">
461
- <option [ngValue]="undefined">(nenhum)</option>
462
- <option value="primary">primary</option>
463
- <option value="accent">accent</option>
464
- <option value="warn">warn</option>
534
+ <option [ngValue]="undefined">(nenhuma)</option>
535
+ <option value="primary">Primary</option>
536
+ <option value="accent">Accent</option>
537
+ <option value="warn">Warn</option>
465
538
  </select>
539
+ <button
540
+ mat-icon-button
541
+ matSuffix
542
+ class="help-icon-button"
543
+ type="button"
544
+ [matTooltip]="'Preferir tokens em Estilo.'"
545
+ matTooltipPosition="above"
546
+ >
547
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
548
+ </button>
466
549
  </mat-form-field>
467
- <mat-form-field appearance="outline"><mat-label>backgroundColor (M2)</mat-label>
550
+ <mat-form-field appearance="outline"><mat-label>Cor de fundo (M2)</mat-label>
468
551
  <select matNativeControl [(ngModel)]="nav.backgroundColor" (ngModelChange)="onAppearanceChange()">
469
- <option [ngValue]="undefined">(nenhum)</option>
470
- <option value="primary">primary</option>
471
- <option value="accent">accent</option>
472
- <option value="warn">warn</option>
552
+ <option [ngValue]="undefined">(nenhuma)</option>
553
+ <option value="primary">Primary</option>
554
+ <option value="accent">Accent</option>
555
+ <option value="warn">Warn</option>
473
556
  </select>
557
+ <button
558
+ mat-icon-button
559
+ matSuffix
560
+ class="help-icon-button"
561
+ type="button"
562
+ [matTooltip]="'Preferir tokens em Estilo.'"
563
+ matTooltipPosition="above"
564
+ >
565
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
566
+ </button>
474
567
  </mat-form-field>
475
568
  </div>
476
- <div style="display:flex; gap: 10px; flex-wrap: wrap;">
477
- <mat-slide-toggle [(ngModel)]="nav.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">fitInkBarToContent</mat-slide-toggle>
478
- <mat-slide-toggle [(ngModel)]="nav.disablePagination" (ngModelChange)="onAppearanceChange()">disablePagination</mat-slide-toggle>
479
- <mat-slide-toggle [(ngModel)]="nav.disableRipple" (ngModelChange)="onAppearanceChange()">disableRipple</mat-slide-toggle>
480
- <mat-slide-toggle [(ngModel)]="nav.stretchTabs" (ngModelChange)="onAppearanceChange()">stretchTabs</mat-slide-toggle>
569
+ <div class="editor-row">
570
+ <mat-slide-toggle [(ngModel)]="nav.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">Indicador ajustado ao conteudo</mat-slide-toggle>
571
+ <mat-slide-toggle [(ngModel)]="nav.disablePagination" (ngModelChange)="onAppearanceChange()">Sem paginacao</mat-slide-toggle>
572
+ <mat-slide-toggle [(ngModel)]="nav.disableRipple" (ngModelChange)="onAppearanceChange()">Sem ripple</mat-slide-toggle>
573
+ <mat-slide-toggle [(ngModel)]="nav.stretchTabs" (ngModelChange)="onAppearanceChange()">Esticar abas</mat-slide-toggle>
481
574
  </div>
482
575
  </div>
483
576
  </mat-tab>
484
577
  <mat-tab label="JSON">
485
- <div style="padding: 12px; display: grid; gap: 12px;">
486
- <div class="json-editor-toolbar" style="display:flex; gap:8px;">
578
+ <div class="editor-section">
579
+ <div class="editor-toolbar">
487
580
  <button mat-button (click)="formatJson()" [disabled]="!isValid">
488
581
  <mat-icon [praxisIcon]="'format_align_left'"></mat-icon>Formatar
489
582
  </button>
@@ -492,26 +585,26 @@ class PraxisTabsConfigEditor {
492
585
  </button>
493
586
  </div>
494
587
 
495
- <mat-form-field appearance="outline" class="json-textarea-field" style="width:100%">
496
- <mat-label>Configuração JSON</mat-label>
588
+ <mat-form-field appearance="outline" class="json-textarea-field full">
589
+ <mat-label>Configuracao JSON</mat-label>
497
590
  <textarea
498
591
  matInput
499
592
  [(ngModel)]="jsonText"
500
593
  (ngModelChange)="onJsonTextChange($event)"
501
594
  rows="22"
502
595
  spellcheck="false"
503
- style="font-family: monospace"
596
+ class="editor-json"
504
597
  ></textarea>
505
- <mat-hint *ngIf="isValid">JSON válido</mat-hint>
506
- <mat-error *ngIf="!isValid && jsonText">JSON inválido: {{ errorMsg }}</mat-error>
598
+ <mat-hint *ngIf="isValid">JSON valido</mat-hint>
599
+ <mat-error *ngIf="!isValid && jsonText">JSON invalido: {{ errorMsg }}</mat-error>
507
600
  </mat-form-field>
508
601
  </div>
509
602
  </mat-tab>
510
603
 
511
604
  <mat-tab label="Estilo">
512
- <div style="padding: 12px; display: grid; gap: 16px;">
513
- <div style="display:flex; gap:8px; align-items:center; flex-wrap: wrap;">
514
- <span style="opacity:.75">Presets:</span>
605
+ <div class="editor-section editor-section-lg">
606
+ <div class="editor-row">
607
+ <span class="editor-muted">Presets:</span>
515
608
  <button mat-button color="primary" (click)="applyPreset('primary')">
516
609
  <mat-icon [praxisIcon]="'palette'"></mat-icon>
517
610
  Primário
@@ -530,7 +623,7 @@ class PraxisTabsConfigEditor {
530
623
  </button>
531
624
  </div>
532
625
 
533
- <div style="display:grid; gap:8px; grid-template-columns: repeat(2, minmax(220px, 1fr));">
626
+ <div class="editor-grid two tight">
534
627
  <mat-form-field appearance="outline">
535
628
  <mat-label>Classe de tema (opcional)</mat-label>
536
629
  <input matInput [(ngModel)]="appearance.themeClass" (ngModelChange)="onAppearanceChange()" placeholder="ex.: tabs-accented" />
@@ -539,38 +632,48 @@ class PraxisTabsConfigEditor {
539
632
  <mat-form-field appearance="outline">
540
633
  <mat-label>Densidade</mat-label>
541
634
  <select matNativeControl [(ngModel)]="appearance.density" (ngModelChange)="onAppearanceChange()">
542
- <option [ngValue]="undefined">padrão</option>
543
- <option value="compact">compact</option>
544
- <option value="comfortable">comfortable</option>
545
- <option value="spacious">spacious</option>
635
+ <option [ngValue]="undefined">Padrao</option>
636
+ <option value="compact">Compacta</option>
637
+ <option value="comfortable">Confortavel</option>
638
+ <option value="spacious">Espacosa</option>
546
639
  </select>
547
640
  </mat-form-field>
548
641
  </div>
549
642
 
550
643
  <div>
551
- <h3 style="margin: 6px 0 8px;">Tokens (M3 aproximados)</h3>
552
- <div style="display:grid; grid-template-columns: repeat(2, minmax(220px, 1fr)); gap: 10px;">
644
+ <div class="editor-title-row">
645
+ <h3 class="editor-title">Tokens (Material 3)</h3>
646
+ <button
647
+ mat-icon-button
648
+ class="help-icon-button"
649
+ type="button"
650
+ [matTooltip]="'Valores aceitam CSS vars (ex.: var(--md-sys-color-primary)) ou cores hex/rgb.'"
651
+ matTooltipPosition="above"
652
+ >
653
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
654
+ </button>
655
+ </div>
656
+ <div class="editor-grid two">
553
657
  <ng-container *ngFor="let t of tokenList">
554
658
  <mat-form-field appearance="outline">
555
659
  <mat-label>{{ t.label }}</mat-label>
556
- <input matInput placeholder="var(--mat-sys-primary) / #RRGGBB" [ngModel]="appearance.tokens?.[t.key]" (ngModelChange)="onTokenChange(t.key, $event)" />
660
+ <input matInput placeholder="var(--md-sys-color-primary) / #RRGGBB" [ngModel]="appearance.tokens?.[t.key]" (ngModelChange)="onTokenChange(t.key, $event)" />
557
661
  </mat-form-field>
558
662
  </ng-container>
559
663
  </div>
560
- <p style="margin:8px 0 0; color: var(--mat-sys-on-surface-variant); font-size: 12px;">Dica: valores aceitam CSS vars (ex.: <code>var(--mat-sys-primary)</code>) ou cores hex/rgb.</p>
561
664
  </div>
562
665
 
563
666
  <div>
564
- <h3 style="margin: 6px 0 8px;">CSS Personalizado</h3>
565
- <mat-form-field appearance="outline" class="json-textarea-field" style="width:100%">
667
+ <h3 class="editor-title">CSS personalizado</h3>
668
+ <mat-form-field appearance="outline" class="json-textarea-field full">
566
669
  <mat-label>CSS a ser injetado no componente</mat-label>
567
670
  <textarea matInput rows="10" [(ngModel)]="appearance.customCss" (ngModelChange)="onAppearanceChange()" placeholder=".praxis-tabs-root .mdc-tab__text-label { font-weight: 600; }"></textarea>
568
671
  </mat-form-field>
569
672
  </div>
570
673
 
571
674
  <div>
572
- <h3 style="margin: 6px 0 8px;">Snippet SCSS (para uso em styles.scss)</h3>
573
- <pre style="white-space: pre-wrap; background: rgba(0,0,0,0.2); padding: 8px; border-radius: 6px;">
675
+ <h3 class="editor-title">Snippet SCSS (para uso em styles.scss)</h3>
676
+ <pre class="editor-code">
574
677
  @use '@angular/material' as mat;
575
678
  {{ scssSnippet() }}
576
679
  </pre>
@@ -579,27 +682,27 @@ class PraxisTabsConfigEditor {
579
682
  </mat-tab>
580
683
 
581
684
  <mat-tab label="Abas">
582
- <div style="padding:12px; display:grid; gap:12px;">
685
+ <div class="editor-section">
583
686
  <button mat-stroked-button color="primary" (click)="addTab()"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar aba</button>
584
- <div *ngIf="editedConfig.tabs?.length; else noTabs" style="display:grid; gap:10px;">
585
- <div *ngFor="let t of editedConfig.tabs; let i = index" style="border:1px solid var(--mat-sys-outline-variant); padding:10px; border-radius:8px; display:grid; gap:8px;">
586
- <div style="display:flex; align-items:center; gap:8px;">
587
- <strong style="flex:1">#{{ i+1 }}</strong>
687
+ <div *ngIf="editedConfig.tabs?.length; else noTabs" class="editor-grid">
688
+ <div *ngFor="let t of editedConfig.tabs; let i = index" class="editor-card">
689
+ <div class="editor-card-header">
690
+ <strong class="editor-card-title">Aba #{{ i+1 }}</strong>
588
691
  <button mat-icon-button (click)="moveTab(i, -1)" [disabled]="i===0"><mat-icon [praxisIcon]="'arrow_upward'"></mat-icon></button>
589
692
  <button mat-icon-button (click)="moveTab(i, 1)" [disabled]="i===editedConfig.tabs!.length-1"><mat-icon [praxisIcon]="'arrow_downward'"></mat-icon></button>
590
693
  <button mat-icon-button color="warn" (click)="removeTab(i)"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
591
694
  </div>
592
- <div style="display:grid; grid-template-columns: repeat(2, minmax(220px,1fr)); gap: 10px;">
695
+ <div class="editor-grid two tight">
593
696
  <mat-form-field appearance="outline"><mat-label>ID</mat-label>
594
697
  <input matInput [(ngModel)]="t.id" (ngModelChange)="onAppearanceChange()" />
595
698
  </mat-form-field>
596
- <mat-form-field appearance="outline"><mat-label>Texto</mat-label>
699
+ <mat-form-field appearance="outline"><mat-label>Rotulo</mat-label>
597
700
  <input matInput [(ngModel)]="t.textLabel" (ngModelChange)="onAppearanceChange()" />
598
701
  </mat-form-field>
599
- <mat-form-field appearance="outline"><mat-label>Label class</mat-label>
702
+ <mat-form-field appearance="outline"><mat-label>Classe do rotulo</mat-label>
600
703
  <input matInput [(ngModel)]="t.labelClass" (ngModelChange)="onAppearanceChange()" />
601
704
  </mat-form-field>
602
- <mat-form-field appearance="outline"><mat-label>Body class</mat-label>
705
+ <mat-form-field appearance="outline"><mat-label>Classe do conteudo</mat-label>
603
706
  <input matInput [(ngModel)]="t.bodyClass" (ngModelChange)="onAppearanceChange()" />
604
707
  </mat-form-field>
605
708
  <mat-form-field appearance="outline"><mat-label>aria-label</mat-label>
@@ -609,22 +712,22 @@ class PraxisTabsConfigEditor {
609
712
  <input matInput [(ngModel)]="t.ariaLabelledby" (ngModelChange)="onAppearanceChange()" />
610
713
  </mat-form-field>
611
714
  </div>
612
- <mat-slide-toggle [(ngModel)]="t.disabled" (ngModelChange)="onAppearanceChange()">disabled</mat-slide-toggle>
715
+ <mat-slide-toggle [(ngModel)]="t.disabled" (ngModelChange)="onAppearanceChange()">Desativada</mat-slide-toggle>
613
716
 
614
717
  <!-- Widgets (componentes dinâmicos) -->
615
- <div style="border-top:1px solid var(--mat-sys-outline-variant); padding-top:8px; display:grid; gap:8px;">
616
- <div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
617
- <mat-form-field appearance="outline" style="min-width:260px;">
718
+ <div class="editor-divider editor-grid">
719
+ <div class="editor-row">
720
+ <mat-form-field appearance="outline" class="editor-field-min">
618
721
  <mat-label>Adicionar componente</mat-label>
619
722
  <select matNativeControl [(ngModel)]="selectedTabWidgetId[i]">
620
723
  <option [ngValue]="''">(selecione)</option>
621
724
  <option *ngFor="let c of componentOptions" [ngValue]="c.id">{{ c.friendlyName || c.id }}</option>
622
725
  </select>
623
726
  </mat-form-field>
624
- <button mat-stroked-button (click)="addWidgetToTab(i)"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar</button>
625
- <span style="flex:1"></span>
626
- <mat-form-field appearance="outline" style="width:240px;">
627
- <mat-label>resourcePath</mat-label>
727
+ <button mat-stroked-button (click)="addWidgetToTab(i)"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar</button>
728
+ <span class="editor-spacer"></span>
729
+ <mat-form-field appearance="outline" class="editor-field-240">
730
+ <mat-label>Recurso (resourcePath)</mat-label>
628
731
  <input matInput [(ngModel)]="quickResourcePathTab[i]" placeholder="ex.: usuarios" />
629
732
  </mat-form-field>
630
733
  <button mat-button (click)="addPresetToTab(i, 'form')"><mat-icon [praxisIcon]="'description'"></mat-icon>Form</button>
@@ -632,22 +735,22 @@ class PraxisTabsConfigEditor {
632
735
  <button mat-button (click)="addPresetToTab(i, 'crud')"><mat-icon [praxisIcon]="'dynamic_form'"></mat-icon>CRUD</button>
633
736
  </div>
634
737
 
635
- <div *ngIf="t.widgets?.length" style="display:grid; gap:8px;" cdkDropList [cdkDropListData]="t.widgets || []" (cdkDropListDropped)="onTabWidgetDrop(i, $event)">
636
- <div *ngFor="let w of t.widgets; let wi = index" cdkDrag style="border:1px dashed var(--mat-sys-outline-variant); padding:8px; border-radius:6px;">
637
- <div style="display:flex; align-items:center; gap:8px;">
738
+ <div *ngIf="t.widgets?.length" class="editor-grid" cdkDropList [cdkDropListData]="t.widgets || []" (cdkDropListDropped)="onTabWidgetDrop(i, $event)">
739
+ <div *ngFor="let w of t.widgets; let wi = index" cdkDrag class="editor-card dashed">
740
+ <div class="editor-card-header">
638
741
  <button mat-icon-button cdkDragHandle matTooltip="Arrastar para reordenar" aria-label="Arrastar para reordenar">
639
742
  <mat-icon [praxisIcon]="'drag_indicator'"></mat-icon>
640
743
  </button>
641
- <strong style="flex:1">{{ getCompName(w.id) }}</strong>
744
+ <strong class="editor-card-title">{{ getCompName(w.id) }}</strong>
642
745
  <button mat-icon-button color="warn" (click)="removeWidgetFromTab(i, wi)" matTooltip="Remover componente" aria-label="Remover componente"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
643
746
  </div>
644
- <div style="display:grid; grid-template-columns: 1fr 1fr; gap:8px;">
747
+ <div class="editor-grid two tight">
645
748
  <mat-form-field appearance="outline">
646
- <mat-label>inputs (JSON)</mat-label>
749
+ <mat-label>Inputs (JSON)</mat-label>
647
750
  <textarea matInput rows="4" [ngModel]="stringify(w.inputs)" (ngModelChange)="updateWidgetInputsTab(i, wi, $event)"></textarea>
648
751
  </mat-form-field>
649
752
  <mat-form-field appearance="outline">
650
- <mat-label>outputs (JSON)</mat-label>
753
+ <mat-label>Outputs (JSON)</mat-label>
651
754
  <textarea matInput rows="4" [ngModel]="stringify(w.outputs)" (ngModelChange)="updateWidgetOutputsTab(i, wi, $event)"></textarea>
652
755
  </mat-form-field>
653
756
  </div>
@@ -656,49 +759,49 @@ class PraxisTabsConfigEditor {
656
759
  </div>
657
760
  </div>
658
761
  </div>
659
- <ng-template #noTabs><em>Nenhuma aba definida.</em></ng-template>
762
+ <ng-template #noTabs><em class="editor-muted">Nenhuma aba definida.</em></ng-template>
660
763
  </div>
661
764
  </mat-tab>
662
765
 
663
766
  <mat-tab label="Acessibilidade">
664
- <div style="padding: 12px; display:grid; gap: 12px;">
665
- <div style="display:flex; gap: 10px; flex-wrap: wrap;">
666
- <mat-slide-toggle [(ngModel)]="accessibility.highContrast" (ngModelChange)="onAppearanceChange()">highContrast</mat-slide-toggle>
667
- <mat-slide-toggle [(ngModel)]="accessibility.reduceMotion" (ngModelChange)="onAppearanceChange()">reduceMotion</mat-slide-toggle>
767
+ <div class="editor-section">
768
+ <div class="editor-row">
769
+ <mat-slide-toggle [(ngModel)]="accessibility.highContrast" (ngModelChange)="onAppearanceChange()">Alto contraste</mat-slide-toggle>
770
+ <mat-slide-toggle [(ngModel)]="accessibility.reduceMotion" (ngModelChange)="onAppearanceChange()">Reduzir movimento</mat-slide-toggle>
668
771
  </div>
669
772
  </div>
670
773
  </mat-tab>
671
774
 
672
775
  <mat-tab label="Links">
673
- <div style="padding:12px; display:grid; gap:12px;">
776
+ <div class="editor-section">
674
777
  <button mat-stroked-button color="primary" (click)="addLink()"><mat-icon [praxisIcon]="'add_link'"></mat-icon>Adicionar link</button>
675
- <div *ngIf="nav.links?.length; else noLinks" style="display:grid; gap:10px;">
676
- <div *ngFor="let l of nav.links; let i = index" style="border:1px solid var(--mat-sys-outline-variant); padding:10px; border-radius:8px; display:grid; gap:8px;">
677
- <div style="display:flex; align-items:center; gap:8px;">
678
- <strong style="flex:1">#{{ i+1 }}</strong>
778
+ <div *ngIf="nav.links?.length; else noLinks" class="editor-grid">
779
+ <div *ngFor="let l of nav.links; let i = index" class="editor-card">
780
+ <div class="editor-card-header">
781
+ <strong class="editor-card-title">Link #{{ i+1 }}</strong>
679
782
  <button mat-icon-button (click)="moveLink(i, -1)" [disabled]="i===0"><mat-icon [praxisIcon]="'arrow_upward'"></mat-icon></button>
680
783
  <button mat-icon-button (click)="moveLink(i, 1)" [disabled]="i===nav.links!.length-1"><mat-icon [praxisIcon]="'arrow_downward'"></mat-icon></button>
681
784
  <button mat-icon-button color="warn" (click)="removeLink(i)"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
682
785
  </div>
683
- <div style="display:grid; grid-template-columns: repeat(2, minmax(220px,1fr)); gap: 10px;">
786
+ <div class="editor-grid two tight">
684
787
  <mat-form-field appearance="outline"><mat-label>ID</mat-label>
685
788
  <input matInput [(ngModel)]="l.id" (ngModelChange)="onAppearanceChange()" />
686
789
  </mat-form-field>
687
- <mat-form-field appearance="outline"><mat-label>Label</mat-label>
790
+ <mat-form-field appearance="outline"><mat-label>Rotulo</mat-label>
688
791
  <input matInput [(ngModel)]="l.label" (ngModelChange)="onAppearanceChange()" />
689
792
  </mat-form-field>
690
793
  </div>
691
- <div style="display:flex; gap:10px; flex-wrap:wrap;">
692
- <mat-slide-toggle [(ngModel)]="l.active" (ngModelChange)="onAppearanceChange()">active</mat-slide-toggle>
693
- <mat-slide-toggle [(ngModel)]="l.disabled" (ngModelChange)="onAppearanceChange()">disabled</mat-slide-toggle>
694
- <mat-slide-toggle [(ngModel)]="l.disableRipple" (ngModelChange)="onAppearanceChange()">disableRipple</mat-slide-toggle>
695
- <mat-slide-toggle [(ngModel)]="l.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">fitInkBarToContent</mat-slide-toggle>
794
+ <div class="editor-row">
795
+ <mat-slide-toggle [(ngModel)]="l.active" (ngModelChange)="onAppearanceChange()">Ativo</mat-slide-toggle>
796
+ <mat-slide-toggle [(ngModel)]="l.disabled" (ngModelChange)="onAppearanceChange()">Desativado</mat-slide-toggle>
797
+ <mat-slide-toggle [(ngModel)]="l.disableRipple" (ngModelChange)="onAppearanceChange()">Sem ripple</mat-slide-toggle>
798
+ <mat-slide-toggle [(ngModel)]="l.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">Indicador ajustado ao conteudo</mat-slide-toggle>
696
799
  </div>
697
800
 
698
801
  <!-- Widgets para Links -->
699
- <div style="border-top:1px solid var(--mat-sys-outline-variant); padding-top:8px; display:grid; gap:8px;">
700
- <div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
701
- <mat-form-field appearance="outline" style="min-width:260px;">
802
+ <div class="editor-divider editor-grid">
803
+ <div class="editor-row">
804
+ <mat-form-field appearance="outline" class="editor-field-min">
702
805
  <mat-label>Adicionar componente</mat-label>
703
806
  <select matNativeControl [(ngModel)]="selectedLinkWidgetId[i]">
704
807
  <option [ngValue]="''">(selecione)</option>
@@ -706,9 +809,9 @@ class PraxisTabsConfigEditor {
706
809
  </select>
707
810
  </mat-form-field>
708
811
  <button mat-stroked-button (click)="addWidgetToLink(i)"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar</button>
709
- <span style="flex:1"></span>
710
- <mat-form-field appearance="outline" style="width:240px;">
711
- <mat-label>resourcePath</mat-label>
812
+ <span class="editor-spacer"></span>
813
+ <mat-form-field appearance="outline" class="editor-field-240">
814
+ <mat-label>Recurso (resourcePath)</mat-label>
712
815
  <input matInput [(ngModel)]="quickResourcePathLink[i]" placeholder="ex.: usuarios" />
713
816
  </mat-form-field>
714
817
  <button mat-button (click)="addPresetToLink(i, 'form')"><mat-icon [praxisIcon]="'description'"></mat-icon>Form</button>
@@ -716,22 +819,22 @@ class PraxisTabsConfigEditor {
716
819
  <button mat-button (click)="addPresetToLink(i, 'crud')"><mat-icon [praxisIcon]="'dynamic_form'"></mat-icon>CRUD</button>
717
820
  </div>
718
821
 
719
- <div *ngIf="l.widgets?.length" style="display:grid; gap:8px;" cdkDropList [cdkDropListData]="l.widgets || []" (cdkDropListDropped)="onLinkWidgetDrop(i, $event)">
720
- <div *ngFor="let w of l.widgets; let wi = index" cdkDrag style="border:1px dashed var(--mat-sys-outline-variant); padding:8px; border-radius:6px;">
721
- <div style="display:flex; align-items:center; gap:8px;">
822
+ <div *ngIf="l.widgets?.length" class="editor-grid" cdkDropList [cdkDropListData]="l.widgets || []" (cdkDropListDropped)="onLinkWidgetDrop(i, $event)">
823
+ <div *ngFor="let w of l.widgets; let wi = index" cdkDrag class="editor-card dashed">
824
+ <div class="editor-card-header">
722
825
  <button mat-icon-button cdkDragHandle matTooltip="Arrastar para reordenar" aria-label="Arrastar para reordenar">
723
826
  <mat-icon [praxisIcon]="'drag_indicator'"></mat-icon>
724
827
  </button>
725
- <strong style="flex:1">{{ getCompName(w.id) }}</strong>
828
+ <strong class="editor-card-title">{{ getCompName(w.id) }}</strong>
726
829
  <button mat-icon-button color="warn" (click)="removeWidgetFromLink(i, wi)" matTooltip="Remover componente" aria-label="Remover componente"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
727
830
  </div>
728
- <div style="display:grid; grid-template-columns: 1fr 1fr; gap:8px;">
831
+ <div class="editor-grid two tight">
729
832
  <mat-form-field appearance="outline">
730
- <mat-label>inputs (JSON)</mat-label>
833
+ <mat-label>Inputs (JSON)</mat-label>
731
834
  <textarea matInput rows="4" [ngModel]="stringify(w.inputs)" (ngModelChange)="updateWidgetInputsLink(i, wi, $event)"></textarea>
732
835
  </mat-form-field>
733
836
  <mat-form-field appearance="outline">
734
- <mat-label>outputs (JSON)</mat-label>
837
+ <mat-label>Outputs (JSON)</mat-label>
735
838
  <textarea matInput rows="4" [ngModel]="stringify(w.outputs)" (ngModelChange)="updateWidgetOutputsLink(i, wi, $event)"></textarea>
736
839
  </mat-form-field>
737
840
  </div>
@@ -740,18 +843,15 @@ class PraxisTabsConfigEditor {
740
843
  </div>
741
844
  </div>
742
845
  </div>
743
- <ng-template #noLinks><em>Nenhum link definido.</em></ng-template>
846
+ <ng-template #noLinks><em class="editor-muted">Nenhum link definido.</em></ng-template>
744
847
  </div>
745
848
  </mat-tab>
746
849
  </mat-tab-group>
747
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.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: i3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i3.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.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: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "directive", type: i5.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i5.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.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: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5$1.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: i5$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.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: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i11.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
850
+ `, isInline: true, styles: [":host{display:block;color:var(--md-sys-color-on-surface)}.editor-tabs{--editor-surface: var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));--editor-border: 1px solid var(--md-sys-color-outline-variant);--editor-radius: 12px;--editor-muted: var(--md-sys-color-on-surface-variant)}.editor-section{padding:12px;display:grid;gap:12px;background:var(--editor-surface);border:var(--editor-border);border-radius:var(--editor-radius)}.editor-section-lg{gap:16px}.editor-row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}.editor-grid{display:grid;gap:12px}.editor-grid.two{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-grid.tight{gap:8px}.editor-toolbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center}.editor-muted{color:var(--editor-muted)}.editor-title{margin:6px 0 8px;font-size:1rem}.editor-title-row{display:flex;align-items:center;gap:8px}.editor-code{white-space:pre-wrap;background:var(--md-sys-color-surface-container);padding:8px;border-radius:6px;border:1px solid var(--md-sys-color-outline-variant)}.editor-card{border:1px solid var(--md-sys-color-outline-variant);padding:10px;border-radius:10px;display:grid;gap:8px;background:var(--md-sys-color-surface)}.editor-card.dashed{border-style:dashed}.editor-card-header{display:flex;align-items:center;gap:8px}.editor-card-title{flex:1;font-weight:600}.editor-divider{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:8px}.editor-spacer{flex:1}.editor-field-min{min-width:260px}.editor-section .editor-field-min{width:260px;max-width:260px}.editor-field-240{width:240px}.editor-section .editor-field-240{width:240px;max-width:240px}.editor-json{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.editor-section .mat-mdc-form-field{width:100%;max-width:520px;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.editor-section .mat-mdc-form-field.full{max-width:none}.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}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.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: i3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i3.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.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: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "directive", type: i5.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i5.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i5.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.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: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6$1.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: i6$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.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: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i11.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
748
851
  }
749
852
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisTabsConfigEditor, decorators: [{
750
853
  type: Component,
751
- args: [{
752
- selector: 'praxis-tabs-config-editor',
753
- standalone: true,
754
- imports: [
854
+ args: [{ selector: 'praxis-tabs-config-editor', standalone: true, imports: [
755
855
  CommonModule,
756
856
  FormsModule,
757
857
  MatTabsModule,
@@ -763,60 +863,109 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
763
863
  MatSlideToggleModule,
764
864
  DragDropModule,
765
865
  MatTooltipModule,
766
- ],
767
- template: `
768
- <mat-tab-group>
866
+ ], template: `
867
+ <mat-tab-group class="editor-tabs">
769
868
  <mat-tab label="Comportamento">
770
- <div style="padding: 12px; display:grid; gap: 12px;">
771
- <div style="display:flex; gap: 10px; flex-wrap: wrap;">
772
- <mat-slide-toggle [(ngModel)]="behavior.closeable" (ngModelChange)="onAppearanceChange()">closeable</mat-slide-toggle>
773
- <mat-slide-toggle [(ngModel)]="behavior.lazyLoad" (ngModelChange)="onAppearanceChange()">lazyLoad (planejado)</mat-slide-toggle>
774
- <mat-slide-toggle [(ngModel)]="behavior.reorderable" (ngModelChange)="onAppearanceChange()">reorderable (planejado)</mat-slide-toggle>
869
+ <div class="editor-section">
870
+ <div class="editor-row">
871
+ <mat-slide-toggle [(ngModel)]="behavior.closeable" (ngModelChange)="onAppearanceChange()">Fechavel</mat-slide-toggle>
872
+ <mat-slide-toggle [(ngModel)]="behavior.lazyLoad" (ngModelChange)="onAppearanceChange()">Carregar sob demanda (planejado)</mat-slide-toggle>
873
+ <mat-slide-toggle [(ngModel)]="behavior.reorderable" (ngModelChange)="onAppearanceChange()">Reordenavel (planejado)</mat-slide-toggle>
775
874
  </div>
776
875
  </div>
777
876
  </mat-tab>
778
877
  <mat-tab label="Grupo">
779
- <div style="padding: 12px; display:grid; gap: 12px;">
780
- <div style="display:grid; grid-template-columns: repeat(2, minmax(220px,1fr)); gap: 12px;">
781
- <mat-form-field appearance="outline"><mat-label>Alinhamento</mat-label>
878
+ <div class="editor-section">
879
+ <div class="editor-grid two">
880
+ <mat-form-field appearance="outline"><mat-label>Alinhamento das abas</mat-label>
782
881
  <select matNativeControl [(ngModel)]="group.alignTabs" (ngModelChange)="onAppearanceChange()">
783
- <option [ngValue]="undefined">padrão</option>
784
- <option value="start">start</option>
785
- <option value="center">center</option>
786
- <option value="end">end</option>
882
+ <option [ngValue]="undefined">Padrao</option>
883
+ <option value="start">Inicio</option>
884
+ <option value="center">Centro</option>
885
+ <option value="end">Fim</option>
787
886
  </select>
788
887
  </mat-form-field>
789
- <mat-form-field appearance="outline"><mat-label>Header</mat-label>
888
+ <mat-form-field appearance="outline"><mat-label>Posicao do header</mat-label>
790
889
  <select matNativeControl [(ngModel)]="group.headerPosition" (ngModelChange)="onAppearanceChange()">
791
- <option [ngValue]="undefined">above</option>
792
- <option value="above">above</option>
793
- <option value="below">below</option>
890
+ <option [ngValue]="undefined">Acima (padrao)</option>
891
+ <option value="above">Acima</option>
892
+ <option value="below">Abaixo</option>
794
893
  </select>
795
894
  </mat-form-field>
796
- <mat-form-field appearance="outline"><mat-label>selectedIndex</mat-label>
895
+ <mat-form-field appearance="outline"><mat-label>Indice selecionado</mat-label>
797
896
  <input matInput type="number" [(ngModel)]="group.selectedIndex" (ngModelChange)="onAppearanceChange()" />
897
+ <button
898
+ mat-icon-button
899
+ matSuffix
900
+ class="help-icon-button"
901
+ type="button"
902
+ [matTooltip]="'selectedIndex'"
903
+ matTooltipPosition="above"
904
+ >
905
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
906
+ </button>
798
907
  </mat-form-field>
799
- <mat-form-field appearance="outline"><mat-label>animationDuration</mat-label>
908
+ <mat-form-field appearance="outline"><mat-label>Duracao da animacao</mat-label>
800
909
  <input matInput [(ngModel)]="group.animationDuration" (ngModelChange)="onAppearanceChange()" placeholder="500ms" />
910
+ <button
911
+ mat-icon-button
912
+ matSuffix
913
+ class="help-icon-button"
914
+ type="button"
915
+ [matTooltip]="'animationDuration'"
916
+ matTooltipPosition="above"
917
+ >
918
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
919
+ </button>
801
920
  </mat-form-field>
802
- <mat-form-field appearance="outline"><mat-label>contentTabIndex</mat-label>
921
+ <mat-form-field appearance="outline"><mat-label>TabIndex do conteudo</mat-label>
803
922
  <input matInput type="number" [(ngModel)]="group.contentTabIndex" (ngModelChange)="onAppearanceChange()" />
923
+ <button
924
+ mat-icon-button
925
+ matSuffix
926
+ class="help-icon-button"
927
+ type="button"
928
+ [matTooltip]="'contentTabIndex'"
929
+ matTooltipPosition="above"
930
+ >
931
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
932
+ </button>
804
933
  </mat-form-field>
805
- <mat-form-field appearance="outline"><mat-label>color (M2)</mat-label>
934
+ <mat-form-field appearance="outline"><mat-label>Cor (M2)</mat-label>
806
935
  <select matNativeControl [(ngModel)]="group.color" (ngModelChange)="onAppearanceChange()">
807
- <option [ngValue]="undefined">(nenhum)</option>
808
- <option value="primary">primary</option>
809
- <option value="accent">accent</option>
810
- <option value="warn">warn</option>
936
+ <option [ngValue]="undefined">(nenhuma)</option>
937
+ <option value="primary">Primary</option>
938
+ <option value="accent">Accent</option>
939
+ <option value="warn">Warn</option>
811
940
  </select>
941
+ <button
942
+ mat-icon-button
943
+ matSuffix
944
+ class="help-icon-button"
945
+ type="button"
946
+ [matTooltip]="'Preferir tokens em Estilo.'"
947
+ matTooltipPosition="above"
948
+ >
949
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
950
+ </button>
812
951
  </mat-form-field>
813
- <mat-form-field appearance="outline"><mat-label>backgroundColor (M2)</mat-label>
952
+ <mat-form-field appearance="outline"><mat-label>Cor de fundo (M2)</mat-label>
814
953
  <select matNativeControl [(ngModel)]="group.backgroundColor" (ngModelChange)="onAppearanceChange()">
815
- <option [ngValue]="undefined">(nenhum)</option>
816
- <option value="primary">primary</option>
817
- <option value="accent">accent</option>
818
- <option value="warn">warn</option>
954
+ <option [ngValue]="undefined">(nenhuma)</option>
955
+ <option value="primary">Primary</option>
956
+ <option value="accent">Accent</option>
957
+ <option value="warn">Warn</option>
819
958
  </select>
959
+ <button
960
+ mat-icon-button
961
+ matSuffix
962
+ class="help-icon-button"
963
+ type="button"
964
+ [matTooltip]="'Preferir tokens em Estilo.'"
965
+ matTooltipPosition="above"
966
+ >
967
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
968
+ </button>
820
969
  </mat-form-field>
821
970
  <mat-form-field appearance="outline"><mat-label>aria-label</mat-label>
822
971
  <input matInput [(ngModel)]="group.ariaLabel" (ngModelChange)="onAppearanceChange()" />
@@ -825,54 +974,94 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
825
974
  <input matInput [(ngModel)]="group.ariaLabelledby" (ngModelChange)="onAppearanceChange()" />
826
975
  </mat-form-field>
827
976
  </div>
828
- <div style="display:flex; gap: 10px; flex-wrap: wrap;">
829
- <mat-slide-toggle [(ngModel)]="group.dynamicHeight" (ngModelChange)="onAppearanceChange()">dynamicHeight</mat-slide-toggle>
830
- <mat-slide-toggle [(ngModel)]="group.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">fitInkBarToContent</mat-slide-toggle>
831
- <mat-slide-toggle [(ngModel)]="group.disablePagination" (ngModelChange)="onAppearanceChange()">disablePagination</mat-slide-toggle>
832
- <mat-slide-toggle [(ngModel)]="group.disableRipple" (ngModelChange)="onAppearanceChange()">disableRipple</mat-slide-toggle>
833
- <mat-slide-toggle [(ngModel)]="group.preserveContent" (ngModelChange)="onAppearanceChange()">preserveContent</mat-slide-toggle>
834
- <mat-slide-toggle [(ngModel)]="group.stretchTabs" (ngModelChange)="onAppearanceChange()">stretchTabs</mat-slide-toggle>
977
+ <div class="editor-row">
978
+ <mat-slide-toggle [(ngModel)]="group.dynamicHeight" (ngModelChange)="onAppearanceChange()">Altura dinamica</mat-slide-toggle>
979
+ <mat-slide-toggle [(ngModel)]="group.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">Indicador ajustado ao conteudo</mat-slide-toggle>
980
+ <mat-slide-toggle [(ngModel)]="group.disablePagination" (ngModelChange)="onAppearanceChange()">Sem paginacao</mat-slide-toggle>
981
+ <mat-slide-toggle [(ngModel)]="group.disableRipple" (ngModelChange)="onAppearanceChange()">Sem ripple</mat-slide-toggle>
982
+ <mat-slide-toggle [(ngModel)]="group.preserveContent" (ngModelChange)="onAppearanceChange()">Preservar conteudo</mat-slide-toggle>
983
+ <mat-slide-toggle [(ngModel)]="group.stretchTabs" (ngModelChange)="onAppearanceChange()">Esticar abas</mat-slide-toggle>
835
984
  </div>
836
985
  </div>
837
986
  </mat-tab>
838
987
 
839
988
  <mat-tab label="Navegação">
840
- <div style="padding: 12px; display:grid; gap: 12px;">
841
- <div style="display:grid; grid-template-columns: repeat(2, minmax(220px,1fr)); gap: 12px;">
842
- <mat-form-field appearance="outline"><mat-label>selectedIndex</mat-label>
989
+ <div class="editor-section">
990
+ <div class="editor-grid two">
991
+ <mat-form-field appearance="outline"><mat-label>Indice selecionado</mat-label>
843
992
  <input matInput type="number" [(ngModel)]="nav.selectedIndex" (ngModelChange)="onAppearanceChange()" />
993
+ <button
994
+ mat-icon-button
995
+ matSuffix
996
+ class="help-icon-button"
997
+ type="button"
998
+ [matTooltip]="'selectedIndex'"
999
+ matTooltipPosition="above"
1000
+ >
1001
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
1002
+ </button>
844
1003
  </mat-form-field>
845
- <mat-form-field appearance="outline"><mat-label>animationDuration</mat-label>
1004
+ <mat-form-field appearance="outline"><mat-label>Duracao da animacao</mat-label>
846
1005
  <input matInput [(ngModel)]="nav.animationDuration" (ngModelChange)="onAppearanceChange()" placeholder="500ms" />
1006
+ <button
1007
+ mat-icon-button
1008
+ matSuffix
1009
+ class="help-icon-button"
1010
+ type="button"
1011
+ [matTooltip]="'animationDuration'"
1012
+ matTooltipPosition="above"
1013
+ >
1014
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
1015
+ </button>
847
1016
  </mat-form-field>
848
- <mat-form-field appearance="outline"><mat-label>color (M2)</mat-label>
1017
+ <mat-form-field appearance="outline"><mat-label>Cor (M2)</mat-label>
849
1018
  <select matNativeControl [(ngModel)]="nav.color" (ngModelChange)="onAppearanceChange()">
850
- <option [ngValue]="undefined">(nenhum)</option>
851
- <option value="primary">primary</option>
852
- <option value="accent">accent</option>
853
- <option value="warn">warn</option>
1019
+ <option [ngValue]="undefined">(nenhuma)</option>
1020
+ <option value="primary">Primary</option>
1021
+ <option value="accent">Accent</option>
1022
+ <option value="warn">Warn</option>
854
1023
  </select>
1024
+ <button
1025
+ mat-icon-button
1026
+ matSuffix
1027
+ class="help-icon-button"
1028
+ type="button"
1029
+ [matTooltip]="'Preferir tokens em Estilo.'"
1030
+ matTooltipPosition="above"
1031
+ >
1032
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
1033
+ </button>
855
1034
  </mat-form-field>
856
- <mat-form-field appearance="outline"><mat-label>backgroundColor (M2)</mat-label>
1035
+ <mat-form-field appearance="outline"><mat-label>Cor de fundo (M2)</mat-label>
857
1036
  <select matNativeControl [(ngModel)]="nav.backgroundColor" (ngModelChange)="onAppearanceChange()">
858
- <option [ngValue]="undefined">(nenhum)</option>
859
- <option value="primary">primary</option>
860
- <option value="accent">accent</option>
861
- <option value="warn">warn</option>
1037
+ <option [ngValue]="undefined">(nenhuma)</option>
1038
+ <option value="primary">Primary</option>
1039
+ <option value="accent">Accent</option>
1040
+ <option value="warn">Warn</option>
862
1041
  </select>
1042
+ <button
1043
+ mat-icon-button
1044
+ matSuffix
1045
+ class="help-icon-button"
1046
+ type="button"
1047
+ [matTooltip]="'Preferir tokens em Estilo.'"
1048
+ matTooltipPosition="above"
1049
+ >
1050
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
1051
+ </button>
863
1052
  </mat-form-field>
864
1053
  </div>
865
- <div style="display:flex; gap: 10px; flex-wrap: wrap;">
866
- <mat-slide-toggle [(ngModel)]="nav.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">fitInkBarToContent</mat-slide-toggle>
867
- <mat-slide-toggle [(ngModel)]="nav.disablePagination" (ngModelChange)="onAppearanceChange()">disablePagination</mat-slide-toggle>
868
- <mat-slide-toggle [(ngModel)]="nav.disableRipple" (ngModelChange)="onAppearanceChange()">disableRipple</mat-slide-toggle>
869
- <mat-slide-toggle [(ngModel)]="nav.stretchTabs" (ngModelChange)="onAppearanceChange()">stretchTabs</mat-slide-toggle>
1054
+ <div class="editor-row">
1055
+ <mat-slide-toggle [(ngModel)]="nav.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">Indicador ajustado ao conteudo</mat-slide-toggle>
1056
+ <mat-slide-toggle [(ngModel)]="nav.disablePagination" (ngModelChange)="onAppearanceChange()">Sem paginacao</mat-slide-toggle>
1057
+ <mat-slide-toggle [(ngModel)]="nav.disableRipple" (ngModelChange)="onAppearanceChange()">Sem ripple</mat-slide-toggle>
1058
+ <mat-slide-toggle [(ngModel)]="nav.stretchTabs" (ngModelChange)="onAppearanceChange()">Esticar abas</mat-slide-toggle>
870
1059
  </div>
871
1060
  </div>
872
1061
  </mat-tab>
873
1062
  <mat-tab label="JSON">
874
- <div style="padding: 12px; display: grid; gap: 12px;">
875
- <div class="json-editor-toolbar" style="display:flex; gap:8px;">
1063
+ <div class="editor-section">
1064
+ <div class="editor-toolbar">
876
1065
  <button mat-button (click)="formatJson()" [disabled]="!isValid">
877
1066
  <mat-icon [praxisIcon]="'format_align_left'"></mat-icon>Formatar
878
1067
  </button>
@@ -881,26 +1070,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
881
1070
  </button>
882
1071
  </div>
883
1072
 
884
- <mat-form-field appearance="outline" class="json-textarea-field" style="width:100%">
885
- <mat-label>Configuração JSON</mat-label>
1073
+ <mat-form-field appearance="outline" class="json-textarea-field full">
1074
+ <mat-label>Configuracao JSON</mat-label>
886
1075
  <textarea
887
1076
  matInput
888
1077
  [(ngModel)]="jsonText"
889
1078
  (ngModelChange)="onJsonTextChange($event)"
890
1079
  rows="22"
891
1080
  spellcheck="false"
892
- style="font-family: monospace"
1081
+ class="editor-json"
893
1082
  ></textarea>
894
- <mat-hint *ngIf="isValid">JSON válido</mat-hint>
895
- <mat-error *ngIf="!isValid && jsonText">JSON inválido: {{ errorMsg }}</mat-error>
1083
+ <mat-hint *ngIf="isValid">JSON valido</mat-hint>
1084
+ <mat-error *ngIf="!isValid && jsonText">JSON invalido: {{ errorMsg }}</mat-error>
896
1085
  </mat-form-field>
897
1086
  </div>
898
1087
  </mat-tab>
899
1088
 
900
1089
  <mat-tab label="Estilo">
901
- <div style="padding: 12px; display: grid; gap: 16px;">
902
- <div style="display:flex; gap:8px; align-items:center; flex-wrap: wrap;">
903
- <span style="opacity:.75">Presets:</span>
1090
+ <div class="editor-section editor-section-lg">
1091
+ <div class="editor-row">
1092
+ <span class="editor-muted">Presets:</span>
904
1093
  <button mat-button color="primary" (click)="applyPreset('primary')">
905
1094
  <mat-icon [praxisIcon]="'palette'"></mat-icon>
906
1095
  Primário
@@ -919,7 +1108,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
919
1108
  </button>
920
1109
  </div>
921
1110
 
922
- <div style="display:grid; gap:8px; grid-template-columns: repeat(2, minmax(220px, 1fr));">
1111
+ <div class="editor-grid two tight">
923
1112
  <mat-form-field appearance="outline">
924
1113
  <mat-label>Classe de tema (opcional)</mat-label>
925
1114
  <input matInput [(ngModel)]="appearance.themeClass" (ngModelChange)="onAppearanceChange()" placeholder="ex.: tabs-accented" />
@@ -928,38 +1117,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
928
1117
  <mat-form-field appearance="outline">
929
1118
  <mat-label>Densidade</mat-label>
930
1119
  <select matNativeControl [(ngModel)]="appearance.density" (ngModelChange)="onAppearanceChange()">
931
- <option [ngValue]="undefined">padrão</option>
932
- <option value="compact">compact</option>
933
- <option value="comfortable">comfortable</option>
934
- <option value="spacious">spacious</option>
1120
+ <option [ngValue]="undefined">Padrao</option>
1121
+ <option value="compact">Compacta</option>
1122
+ <option value="comfortable">Confortavel</option>
1123
+ <option value="spacious">Espacosa</option>
935
1124
  </select>
936
1125
  </mat-form-field>
937
1126
  </div>
938
1127
 
939
1128
  <div>
940
- <h3 style="margin: 6px 0 8px;">Tokens (M3 aproximados)</h3>
941
- <div style="display:grid; grid-template-columns: repeat(2, minmax(220px, 1fr)); gap: 10px;">
1129
+ <div class="editor-title-row">
1130
+ <h3 class="editor-title">Tokens (Material 3)</h3>
1131
+ <button
1132
+ mat-icon-button
1133
+ class="help-icon-button"
1134
+ type="button"
1135
+ [matTooltip]="'Valores aceitam CSS vars (ex.: var(--md-sys-color-primary)) ou cores hex/rgb.'"
1136
+ matTooltipPosition="above"
1137
+ >
1138
+ <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
1139
+ </button>
1140
+ </div>
1141
+ <div class="editor-grid two">
942
1142
  <ng-container *ngFor="let t of tokenList">
943
1143
  <mat-form-field appearance="outline">
944
1144
  <mat-label>{{ t.label }}</mat-label>
945
- <input matInput placeholder="var(--mat-sys-primary) / #RRGGBB" [ngModel]="appearance.tokens?.[t.key]" (ngModelChange)="onTokenChange(t.key, $event)" />
1145
+ <input matInput placeholder="var(--md-sys-color-primary) / #RRGGBB" [ngModel]="appearance.tokens?.[t.key]" (ngModelChange)="onTokenChange(t.key, $event)" />
946
1146
  </mat-form-field>
947
1147
  </ng-container>
948
1148
  </div>
949
- <p style="margin:8px 0 0; color: var(--mat-sys-on-surface-variant); font-size: 12px;">Dica: valores aceitam CSS vars (ex.: <code>var(--mat-sys-primary)</code>) ou cores hex/rgb.</p>
950
1149
  </div>
951
1150
 
952
1151
  <div>
953
- <h3 style="margin: 6px 0 8px;">CSS Personalizado</h3>
954
- <mat-form-field appearance="outline" class="json-textarea-field" style="width:100%">
1152
+ <h3 class="editor-title">CSS personalizado</h3>
1153
+ <mat-form-field appearance="outline" class="json-textarea-field full">
955
1154
  <mat-label>CSS a ser injetado no componente</mat-label>
956
1155
  <textarea matInput rows="10" [(ngModel)]="appearance.customCss" (ngModelChange)="onAppearanceChange()" placeholder=".praxis-tabs-root .mdc-tab__text-label { font-weight: 600; }"></textarea>
957
1156
  </mat-form-field>
958
1157
  </div>
959
1158
 
960
1159
  <div>
961
- <h3 style="margin: 6px 0 8px;">Snippet SCSS (para uso em styles.scss)</h3>
962
- <pre style="white-space: pre-wrap; background: rgba(0,0,0,0.2); padding: 8px; border-radius: 6px;">
1160
+ <h3 class="editor-title">Snippet SCSS (para uso em styles.scss)</h3>
1161
+ <pre class="editor-code">
963
1162
  @use '@angular/material' as mat;
964
1163
  {{ scssSnippet() }}
965
1164
  </pre>
@@ -968,27 +1167,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
968
1167
  </mat-tab>
969
1168
 
970
1169
  <mat-tab label="Abas">
971
- <div style="padding:12px; display:grid; gap:12px;">
1170
+ <div class="editor-section">
972
1171
  <button mat-stroked-button color="primary" (click)="addTab()"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar aba</button>
973
- <div *ngIf="editedConfig.tabs?.length; else noTabs" style="display:grid; gap:10px;">
974
- <div *ngFor="let t of editedConfig.tabs; let i = index" style="border:1px solid var(--mat-sys-outline-variant); padding:10px; border-radius:8px; display:grid; gap:8px;">
975
- <div style="display:flex; align-items:center; gap:8px;">
976
- <strong style="flex:1">#{{ i+1 }}</strong>
1172
+ <div *ngIf="editedConfig.tabs?.length; else noTabs" class="editor-grid">
1173
+ <div *ngFor="let t of editedConfig.tabs; let i = index" class="editor-card">
1174
+ <div class="editor-card-header">
1175
+ <strong class="editor-card-title">Aba #{{ i+1 }}</strong>
977
1176
  <button mat-icon-button (click)="moveTab(i, -1)" [disabled]="i===0"><mat-icon [praxisIcon]="'arrow_upward'"></mat-icon></button>
978
1177
  <button mat-icon-button (click)="moveTab(i, 1)" [disabled]="i===editedConfig.tabs!.length-1"><mat-icon [praxisIcon]="'arrow_downward'"></mat-icon></button>
979
1178
  <button mat-icon-button color="warn" (click)="removeTab(i)"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
980
1179
  </div>
981
- <div style="display:grid; grid-template-columns: repeat(2, minmax(220px,1fr)); gap: 10px;">
1180
+ <div class="editor-grid two tight">
982
1181
  <mat-form-field appearance="outline"><mat-label>ID</mat-label>
983
1182
  <input matInput [(ngModel)]="t.id" (ngModelChange)="onAppearanceChange()" />
984
1183
  </mat-form-field>
985
- <mat-form-field appearance="outline"><mat-label>Texto</mat-label>
1184
+ <mat-form-field appearance="outline"><mat-label>Rotulo</mat-label>
986
1185
  <input matInput [(ngModel)]="t.textLabel" (ngModelChange)="onAppearanceChange()" />
987
1186
  </mat-form-field>
988
- <mat-form-field appearance="outline"><mat-label>Label class</mat-label>
1187
+ <mat-form-field appearance="outline"><mat-label>Classe do rotulo</mat-label>
989
1188
  <input matInput [(ngModel)]="t.labelClass" (ngModelChange)="onAppearanceChange()" />
990
1189
  </mat-form-field>
991
- <mat-form-field appearance="outline"><mat-label>Body class</mat-label>
1190
+ <mat-form-field appearance="outline"><mat-label>Classe do conteudo</mat-label>
992
1191
  <input matInput [(ngModel)]="t.bodyClass" (ngModelChange)="onAppearanceChange()" />
993
1192
  </mat-form-field>
994
1193
  <mat-form-field appearance="outline"><mat-label>aria-label</mat-label>
@@ -998,22 +1197,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
998
1197
  <input matInput [(ngModel)]="t.ariaLabelledby" (ngModelChange)="onAppearanceChange()" />
999
1198
  </mat-form-field>
1000
1199
  </div>
1001
- <mat-slide-toggle [(ngModel)]="t.disabled" (ngModelChange)="onAppearanceChange()">disabled</mat-slide-toggle>
1200
+ <mat-slide-toggle [(ngModel)]="t.disabled" (ngModelChange)="onAppearanceChange()">Desativada</mat-slide-toggle>
1002
1201
 
1003
1202
  <!-- Widgets (componentes dinâmicos) -->
1004
- <div style="border-top:1px solid var(--mat-sys-outline-variant); padding-top:8px; display:grid; gap:8px;">
1005
- <div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
1006
- <mat-form-field appearance="outline" style="min-width:260px;">
1203
+ <div class="editor-divider editor-grid">
1204
+ <div class="editor-row">
1205
+ <mat-form-field appearance="outline" class="editor-field-min">
1007
1206
  <mat-label>Adicionar componente</mat-label>
1008
1207
  <select matNativeControl [(ngModel)]="selectedTabWidgetId[i]">
1009
1208
  <option [ngValue]="''">(selecione)</option>
1010
1209
  <option *ngFor="let c of componentOptions" [ngValue]="c.id">{{ c.friendlyName || c.id }}</option>
1011
1210
  </select>
1012
1211
  </mat-form-field>
1013
- <button mat-stroked-button (click)="addWidgetToTab(i)"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar</button>
1014
- <span style="flex:1"></span>
1015
- <mat-form-field appearance="outline" style="width:240px;">
1016
- <mat-label>resourcePath</mat-label>
1212
+ <button mat-stroked-button (click)="addWidgetToTab(i)"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar</button>
1213
+ <span class="editor-spacer"></span>
1214
+ <mat-form-field appearance="outline" class="editor-field-240">
1215
+ <mat-label>Recurso (resourcePath)</mat-label>
1017
1216
  <input matInput [(ngModel)]="quickResourcePathTab[i]" placeholder="ex.: usuarios" />
1018
1217
  </mat-form-field>
1019
1218
  <button mat-button (click)="addPresetToTab(i, 'form')"><mat-icon [praxisIcon]="'description'"></mat-icon>Form</button>
@@ -1021,22 +1220,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1021
1220
  <button mat-button (click)="addPresetToTab(i, 'crud')"><mat-icon [praxisIcon]="'dynamic_form'"></mat-icon>CRUD</button>
1022
1221
  </div>
1023
1222
 
1024
- <div *ngIf="t.widgets?.length" style="display:grid; gap:8px;" cdkDropList [cdkDropListData]="t.widgets || []" (cdkDropListDropped)="onTabWidgetDrop(i, $event)">
1025
- <div *ngFor="let w of t.widgets; let wi = index" cdkDrag style="border:1px dashed var(--mat-sys-outline-variant); padding:8px; border-radius:6px;">
1026
- <div style="display:flex; align-items:center; gap:8px;">
1223
+ <div *ngIf="t.widgets?.length" class="editor-grid" cdkDropList [cdkDropListData]="t.widgets || []" (cdkDropListDropped)="onTabWidgetDrop(i, $event)">
1224
+ <div *ngFor="let w of t.widgets; let wi = index" cdkDrag class="editor-card dashed">
1225
+ <div class="editor-card-header">
1027
1226
  <button mat-icon-button cdkDragHandle matTooltip="Arrastar para reordenar" aria-label="Arrastar para reordenar">
1028
1227
  <mat-icon [praxisIcon]="'drag_indicator'"></mat-icon>
1029
1228
  </button>
1030
- <strong style="flex:1">{{ getCompName(w.id) }}</strong>
1229
+ <strong class="editor-card-title">{{ getCompName(w.id) }}</strong>
1031
1230
  <button mat-icon-button color="warn" (click)="removeWidgetFromTab(i, wi)" matTooltip="Remover componente" aria-label="Remover componente"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
1032
1231
  </div>
1033
- <div style="display:grid; grid-template-columns: 1fr 1fr; gap:8px;">
1232
+ <div class="editor-grid two tight">
1034
1233
  <mat-form-field appearance="outline">
1035
- <mat-label>inputs (JSON)</mat-label>
1234
+ <mat-label>Inputs (JSON)</mat-label>
1036
1235
  <textarea matInput rows="4" [ngModel]="stringify(w.inputs)" (ngModelChange)="updateWidgetInputsTab(i, wi, $event)"></textarea>
1037
1236
  </mat-form-field>
1038
1237
  <mat-form-field appearance="outline">
1039
- <mat-label>outputs (JSON)</mat-label>
1238
+ <mat-label>Outputs (JSON)</mat-label>
1040
1239
  <textarea matInput rows="4" [ngModel]="stringify(w.outputs)" (ngModelChange)="updateWidgetOutputsTab(i, wi, $event)"></textarea>
1041
1240
  </mat-form-field>
1042
1241
  </div>
@@ -1045,49 +1244,49 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1045
1244
  </div>
1046
1245
  </div>
1047
1246
  </div>
1048
- <ng-template #noTabs><em>Nenhuma aba definida.</em></ng-template>
1247
+ <ng-template #noTabs><em class="editor-muted">Nenhuma aba definida.</em></ng-template>
1049
1248
  </div>
1050
1249
  </mat-tab>
1051
1250
 
1052
1251
  <mat-tab label="Acessibilidade">
1053
- <div style="padding: 12px; display:grid; gap: 12px;">
1054
- <div style="display:flex; gap: 10px; flex-wrap: wrap;">
1055
- <mat-slide-toggle [(ngModel)]="accessibility.highContrast" (ngModelChange)="onAppearanceChange()">highContrast</mat-slide-toggle>
1056
- <mat-slide-toggle [(ngModel)]="accessibility.reduceMotion" (ngModelChange)="onAppearanceChange()">reduceMotion</mat-slide-toggle>
1252
+ <div class="editor-section">
1253
+ <div class="editor-row">
1254
+ <mat-slide-toggle [(ngModel)]="accessibility.highContrast" (ngModelChange)="onAppearanceChange()">Alto contraste</mat-slide-toggle>
1255
+ <mat-slide-toggle [(ngModel)]="accessibility.reduceMotion" (ngModelChange)="onAppearanceChange()">Reduzir movimento</mat-slide-toggle>
1057
1256
  </div>
1058
1257
  </div>
1059
1258
  </mat-tab>
1060
1259
 
1061
1260
  <mat-tab label="Links">
1062
- <div style="padding:12px; display:grid; gap:12px;">
1261
+ <div class="editor-section">
1063
1262
  <button mat-stroked-button color="primary" (click)="addLink()"><mat-icon [praxisIcon]="'add_link'"></mat-icon>Adicionar link</button>
1064
- <div *ngIf="nav.links?.length; else noLinks" style="display:grid; gap:10px;">
1065
- <div *ngFor="let l of nav.links; let i = index" style="border:1px solid var(--mat-sys-outline-variant); padding:10px; border-radius:8px; display:grid; gap:8px;">
1066
- <div style="display:flex; align-items:center; gap:8px;">
1067
- <strong style="flex:1">#{{ i+1 }}</strong>
1263
+ <div *ngIf="nav.links?.length; else noLinks" class="editor-grid">
1264
+ <div *ngFor="let l of nav.links; let i = index" class="editor-card">
1265
+ <div class="editor-card-header">
1266
+ <strong class="editor-card-title">Link #{{ i+1 }}</strong>
1068
1267
  <button mat-icon-button (click)="moveLink(i, -1)" [disabled]="i===0"><mat-icon [praxisIcon]="'arrow_upward'"></mat-icon></button>
1069
1268
  <button mat-icon-button (click)="moveLink(i, 1)" [disabled]="i===nav.links!.length-1"><mat-icon [praxisIcon]="'arrow_downward'"></mat-icon></button>
1070
1269
  <button mat-icon-button color="warn" (click)="removeLink(i)"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
1071
1270
  </div>
1072
- <div style="display:grid; grid-template-columns: repeat(2, minmax(220px,1fr)); gap: 10px;">
1271
+ <div class="editor-grid two tight">
1073
1272
  <mat-form-field appearance="outline"><mat-label>ID</mat-label>
1074
1273
  <input matInput [(ngModel)]="l.id" (ngModelChange)="onAppearanceChange()" />
1075
1274
  </mat-form-field>
1076
- <mat-form-field appearance="outline"><mat-label>Label</mat-label>
1275
+ <mat-form-field appearance="outline"><mat-label>Rotulo</mat-label>
1077
1276
  <input matInput [(ngModel)]="l.label" (ngModelChange)="onAppearanceChange()" />
1078
1277
  </mat-form-field>
1079
1278
  </div>
1080
- <div style="display:flex; gap:10px; flex-wrap:wrap;">
1081
- <mat-slide-toggle [(ngModel)]="l.active" (ngModelChange)="onAppearanceChange()">active</mat-slide-toggle>
1082
- <mat-slide-toggle [(ngModel)]="l.disabled" (ngModelChange)="onAppearanceChange()">disabled</mat-slide-toggle>
1083
- <mat-slide-toggle [(ngModel)]="l.disableRipple" (ngModelChange)="onAppearanceChange()">disableRipple</mat-slide-toggle>
1084
- <mat-slide-toggle [(ngModel)]="l.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">fitInkBarToContent</mat-slide-toggle>
1279
+ <div class="editor-row">
1280
+ <mat-slide-toggle [(ngModel)]="l.active" (ngModelChange)="onAppearanceChange()">Ativo</mat-slide-toggle>
1281
+ <mat-slide-toggle [(ngModel)]="l.disabled" (ngModelChange)="onAppearanceChange()">Desativado</mat-slide-toggle>
1282
+ <mat-slide-toggle [(ngModel)]="l.disableRipple" (ngModelChange)="onAppearanceChange()">Sem ripple</mat-slide-toggle>
1283
+ <mat-slide-toggle [(ngModel)]="l.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">Indicador ajustado ao conteudo</mat-slide-toggle>
1085
1284
  </div>
1086
1285
 
1087
1286
  <!-- Widgets para Links -->
1088
- <div style="border-top:1px solid var(--mat-sys-outline-variant); padding-top:8px; display:grid; gap:8px;">
1089
- <div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
1090
- <mat-form-field appearance="outline" style="min-width:260px;">
1287
+ <div class="editor-divider editor-grid">
1288
+ <div class="editor-row">
1289
+ <mat-form-field appearance="outline" class="editor-field-min">
1091
1290
  <mat-label>Adicionar componente</mat-label>
1092
1291
  <select matNativeControl [(ngModel)]="selectedLinkWidgetId[i]">
1093
1292
  <option [ngValue]="''">(selecione)</option>
@@ -1095,9 +1294,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1095
1294
  </select>
1096
1295
  </mat-form-field>
1097
1296
  <button mat-stroked-button (click)="addWidgetToLink(i)"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar</button>
1098
- <span style="flex:1"></span>
1099
- <mat-form-field appearance="outline" style="width:240px;">
1100
- <mat-label>resourcePath</mat-label>
1297
+ <span class="editor-spacer"></span>
1298
+ <mat-form-field appearance="outline" class="editor-field-240">
1299
+ <mat-label>Recurso (resourcePath)</mat-label>
1101
1300
  <input matInput [(ngModel)]="quickResourcePathLink[i]" placeholder="ex.: usuarios" />
1102
1301
  </mat-form-field>
1103
1302
  <button mat-button (click)="addPresetToLink(i, 'form')"><mat-icon [praxisIcon]="'description'"></mat-icon>Form</button>
@@ -1105,22 +1304,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1105
1304
  <button mat-button (click)="addPresetToLink(i, 'crud')"><mat-icon [praxisIcon]="'dynamic_form'"></mat-icon>CRUD</button>
1106
1305
  </div>
1107
1306
 
1108
- <div *ngIf="l.widgets?.length" style="display:grid; gap:8px;" cdkDropList [cdkDropListData]="l.widgets || []" (cdkDropListDropped)="onLinkWidgetDrop(i, $event)">
1109
- <div *ngFor="let w of l.widgets; let wi = index" cdkDrag style="border:1px dashed var(--mat-sys-outline-variant); padding:8px; border-radius:6px;">
1110
- <div style="display:flex; align-items:center; gap:8px;">
1307
+ <div *ngIf="l.widgets?.length" class="editor-grid" cdkDropList [cdkDropListData]="l.widgets || []" (cdkDropListDropped)="onLinkWidgetDrop(i, $event)">
1308
+ <div *ngFor="let w of l.widgets; let wi = index" cdkDrag class="editor-card dashed">
1309
+ <div class="editor-card-header">
1111
1310
  <button mat-icon-button cdkDragHandle matTooltip="Arrastar para reordenar" aria-label="Arrastar para reordenar">
1112
1311
  <mat-icon [praxisIcon]="'drag_indicator'"></mat-icon>
1113
1312
  </button>
1114
- <strong style="flex:1">{{ getCompName(w.id) }}</strong>
1313
+ <strong class="editor-card-title">{{ getCompName(w.id) }}</strong>
1115
1314
  <button mat-icon-button color="warn" (click)="removeWidgetFromLink(i, wi)" matTooltip="Remover componente" aria-label="Remover componente"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
1116
1315
  </div>
1117
- <div style="display:grid; grid-template-columns: 1fr 1fr; gap:8px;">
1316
+ <div class="editor-grid two tight">
1118
1317
  <mat-form-field appearance="outline">
1119
- <mat-label>inputs (JSON)</mat-label>
1318
+ <mat-label>Inputs (JSON)</mat-label>
1120
1319
  <textarea matInput rows="4" [ngModel]="stringify(w.inputs)" (ngModelChange)="updateWidgetInputsLink(i, wi, $event)"></textarea>
1121
1320
  </mat-form-field>
1122
1321
  <mat-form-field appearance="outline">
1123
- <mat-label>outputs (JSON)</mat-label>
1322
+ <mat-label>Outputs (JSON)</mat-label>
1124
1323
  <textarea matInput rows="4" [ngModel]="stringify(w.outputs)" (ngModelChange)="updateWidgetOutputsLink(i, wi, $event)"></textarea>
1125
1324
  </mat-form-field>
1126
1325
  </div>
@@ -1129,12 +1328,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1129
1328
  </div>
1130
1329
  </div>
1131
1330
  </div>
1132
- <ng-template #noLinks><em>Nenhum link definido.</em></ng-template>
1331
+ <ng-template #noLinks><em class="editor-muted">Nenhum link definido.</em></ng-template>
1133
1332
  </div>
1134
1333
  </mat-tab>
1135
1334
  </mat-tab-group>
1136
- `,
1137
- }]
1335
+ `, styles: [":host{display:block;color:var(--md-sys-color-on-surface)}.editor-tabs{--editor-surface: var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));--editor-border: 1px solid var(--md-sys-color-outline-variant);--editor-radius: 12px;--editor-muted: var(--md-sys-color-on-surface-variant)}.editor-section{padding:12px;display:grid;gap:12px;background:var(--editor-surface);border:var(--editor-border);border-radius:var(--editor-radius)}.editor-section-lg{gap:16px}.editor-row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}.editor-grid{display:grid;gap:12px}.editor-grid.two{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-grid.tight{gap:8px}.editor-toolbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center}.editor-muted{color:var(--editor-muted)}.editor-title{margin:6px 0 8px;font-size:1rem}.editor-title-row{display:flex;align-items:center;gap:8px}.editor-code{white-space:pre-wrap;background:var(--md-sys-color-surface-container);padding:8px;border-radius:6px;border:1px solid var(--md-sys-color-outline-variant)}.editor-card{border:1px solid var(--md-sys-color-outline-variant);padding:10px;border-radius:10px;display:grid;gap:8px;background:var(--md-sys-color-surface)}.editor-card.dashed{border-style:dashed}.editor-card-header{display:flex;align-items:center;gap:8px}.editor-card-title{flex:1;font-weight:600}.editor-divider{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:8px}.editor-spacer{flex:1}.editor-field-min{min-width:260px}.editor-section .editor-field-min{width:260px;max-width:260px}.editor-field-240{width:240px}.editor-section .editor-field-240{width:240px;max-width:240px}.editor-json{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.editor-section .mat-mdc-form-field{width:100%;max-width:520px;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.editor-section .mat-mdc-form-field.full{max-width:none}.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}\n"] }]
1138
1336
  }], ctorParameters: () => [{ type: undefined, decorators: [{
1139
1337
  type: Inject,
1140
1338
  args: [SETTINGS_PANEL_DATA]
@@ -1153,23 +1351,32 @@ class TabsQuickSetupComponent {
1153
1351
  if (cfg) {
1154
1352
  if (cfg.tabs?.length) {
1155
1353
  this.model.mode = 'group';
1156
- this.model.labels = cfg.tabs.map((t) => t.textLabel || 'Aba');
1354
+ this.model.labels = cfg.tabs
1355
+ .map((t) => String(t.textLabel || '').trim())
1356
+ .filter((label) => label.length > 0);
1157
1357
  this.model.dynamicHeight = cfg.group?.dynamicHeight ?? true;
1158
1358
  this.model.stretchTabs = cfg.group?.stretchTabs ?? true;
1159
1359
  }
1160
1360
  else if (cfg.nav?.links?.length) {
1161
1361
  this.model.mode = 'nav';
1162
- this.model.labels = cfg.nav.links.map((l) => l.label || 'Link');
1362
+ this.model.labels = cfg.nav.links
1363
+ .map((l) => String(l.label || '').trim())
1364
+ .filter((label) => label.length > 0);
1163
1365
  this.model.stretchTabs = cfg.nav.stretchTabs ?? true;
1164
1366
  }
1165
1367
  this.initial = { ...this.model, labels: [...this.model.labels] };
1166
1368
  }
1167
1369
  this.updateState();
1168
1370
  }
1371
+ normalizedLabels() {
1372
+ return this.model.labels
1373
+ .map((label) => String(label || '').trim())
1374
+ .filter((label) => label.length > 0);
1375
+ }
1169
1376
  updateState() {
1170
1377
  const dirty = JSON.stringify(this.model) !== JSON.stringify(this.initial);
1171
1378
  this.isDirty$.next(dirty);
1172
- this.isValid$.next(this.model.labels.length > 0);
1379
+ this.isValid$.next(this.normalizedLabels().length > 0);
1173
1380
  }
1174
1381
  setMode(mode) {
1175
1382
  this.model.mode = mode;
@@ -1188,6 +1395,7 @@ class TabsQuickSetupComponent {
1188
1395
  this.updateState();
1189
1396
  }
1190
1397
  buildConfig() {
1398
+ const labels = this.normalizedLabels();
1191
1399
  if (this.model.mode === 'group') {
1192
1400
  return {
1193
1401
  group: {
@@ -1195,14 +1403,14 @@ class TabsQuickSetupComponent {
1195
1403
  stretchTabs: !!this.model.stretchTabs,
1196
1404
  selectedIndex: 0,
1197
1405
  },
1198
- tabs: this.model.labels.map((label, idx) => ({ id: `tab${idx + 1}`, textLabel: label })),
1406
+ tabs: labels.map((label, idx) => ({ id: `tab${idx + 1}`, textLabel: label })),
1199
1407
  };
1200
1408
  }
1201
1409
  return {
1202
1410
  nav: {
1203
1411
  selectedIndex: 0,
1204
1412
  stretchTabs: !!this.model.stretchTabs,
1205
- links: this.model.labels.map((label, idx) => ({ id: `link${idx + 1}`, label })),
1413
+ links: labels.map((label, idx) => ({ id: `link${idx + 1}`, label })),
1206
1414
  },
1207
1415
  };
1208
1416
  }
@@ -1214,21 +1422,21 @@ class TabsQuickSetupComponent {
1214
1422
  }
1215
1423
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: TabsQuickSetupComponent, deps: [{ token: SETTINGS_PANEL_DATA }], target: i0.ɵɵFactoryTarget.Component });
1216
1424
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: TabsQuickSetupComponent, isStandalone: true, selector: "praxis-tabs-quick-setup", ngImport: i0, template: `
1217
- <div style="display:grid; gap: 12px; padding: 8px;">
1218
- <div style="display:flex; gap: 12px; align-items:center; flex-wrap:wrap;">
1425
+ <div class="setup-root">
1426
+ <div class="setup-row">
1219
1427
  <button mat-stroked-button [color]="model.mode==='group' ? 'primary' : undefined" (click)="setMode('group')">
1220
1428
  <mat-icon [praxisIcon]="'tab'"></mat-icon>
1221
- Abas (TabGroup)
1429
+ Grupo de abas
1222
1430
  </button>
1223
1431
  <button mat-stroked-button [color]="model.mode==='nav' ? 'primary' : undefined" (click)="setMode('nav')">
1224
1432
  <mat-icon [praxisIcon]="'segment'"></mat-icon>
1225
- Navegação (TabNav)
1433
+ Navegacao por links
1226
1434
  </button>
1227
1435
  </div>
1228
1436
 
1229
- <div style="display:grid; grid-template-columns: 1fr auto; gap: 8px; align-items:start;">
1437
+ <div class="setup-grid">
1230
1438
  <mat-form-field appearance="outline">
1231
- <mat-label>Adicionar label</mat-label>
1439
+ <mat-label>Adicionar rotulo</mat-label>
1232
1440
  <input matInput [(ngModel)]="newLabel" (keyup.enter)="addLabel()" placeholder="Ex.: Dados Gerais" />
1233
1441
  </mat-form-field>
1234
1442
  <button mat-flat-button color="primary" (click)="addLabel()">
@@ -1237,22 +1445,22 @@ class TabsQuickSetupComponent {
1237
1445
  </button>
1238
1446
  </div>
1239
1447
 
1240
- <div *ngIf="model.labels.length; else emptyLabels" style="display:flex; flex-wrap:wrap; gap: 8px;">
1448
+ <div *ngIf="model.labels.length; else emptyLabels" class="setup-row wrap">
1241
1449
  <div *ngFor="let l of model.labels; let i = index" class="chip">
1242
1450
  <span>{{ l }}</span>
1243
- <button mat-icon-button (click)="removeLabel(i)"><mat-icon [praxisIcon]="'close'"></mat-icon></button>
1451
+ <button mat-icon-button (click)="removeLabel(i)" aria-label="Remover rotulo"><mat-icon [praxisIcon]="'close'"></mat-icon></button>
1244
1452
  </div>
1245
1453
  </div>
1246
1454
  <ng-template #emptyLabels>
1247
- <em>Adicione 1 ou mais labels para criar as abas.</em>
1455
+ <em class="hint">Adicione um ou mais rotulos para criar as abas.</em>
1248
1456
  </ng-template>
1249
1457
 
1250
- <div style="display:flex; gap: 12px; flex-wrap:wrap;">
1251
- <mat-slide-toggle [(ngModel)]="model.dynamicHeight">dynamicHeight</mat-slide-toggle>
1252
- <mat-slide-toggle [(ngModel)]="model.stretchTabs">stretchTabs</mat-slide-toggle>
1458
+ <div class="setup-row">
1459
+ <mat-slide-toggle [(ngModel)]="model.dynamicHeight">Altura dinamica</mat-slide-toggle>
1460
+ <mat-slide-toggle [(ngModel)]="model.stretchTabs">Esticar abas</mat-slide-toggle>
1253
1461
  </div>
1254
1462
  </div>
1255
- `, isInline: true, styles: [".chip{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border:1px solid var(--mat-sys-outline-variant);border-radius:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.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: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.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: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5$1.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: i5$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.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"] }] });
1463
+ `, isInline: true, styles: [":host{display:block;color:var(--md-sys-color-on-surface)}.setup-root{display:grid;gap:12px;padding:8px;background:var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));border:1px solid var(--md-sys-color-outline-variant);border-radius:12px}.setup-row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.setup-row.wrap{flex-wrap:wrap}.setup-grid{display:grid;grid-template-columns:1fr auto;gap:8px;align-items:start}.setup-root .mat-mdc-form-field{width:100%;max-width:520px;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.chip{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border:1px solid var(--md-sys-color-outline-variant);border-radius:16px;background:var(--md-sys-color-surface)}.hint{color:var(--md-sys-color-on-surface-variant)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.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: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.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: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6$1.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: i6$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.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"] }] });
1256
1464
  }
1257
1465
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: TabsQuickSetupComponent, decorators: [{
1258
1466
  type: Component,
@@ -1266,21 +1474,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1266
1474
  MatButtonModule,
1267
1475
  MatSlideToggleModule,
1268
1476
  ], template: `
1269
- <div style="display:grid; gap: 12px; padding: 8px;">
1270
- <div style="display:flex; gap: 12px; align-items:center; flex-wrap:wrap;">
1477
+ <div class="setup-root">
1478
+ <div class="setup-row">
1271
1479
  <button mat-stroked-button [color]="model.mode==='group' ? 'primary' : undefined" (click)="setMode('group')">
1272
1480
  <mat-icon [praxisIcon]="'tab'"></mat-icon>
1273
- Abas (TabGroup)
1481
+ Grupo de abas
1274
1482
  </button>
1275
1483
  <button mat-stroked-button [color]="model.mode==='nav' ? 'primary' : undefined" (click)="setMode('nav')">
1276
1484
  <mat-icon [praxisIcon]="'segment'"></mat-icon>
1277
- Navegação (TabNav)
1485
+ Navegacao por links
1278
1486
  </button>
1279
1487
  </div>
1280
1488
 
1281
- <div style="display:grid; grid-template-columns: 1fr auto; gap: 8px; align-items:start;">
1489
+ <div class="setup-grid">
1282
1490
  <mat-form-field appearance="outline">
1283
- <mat-label>Adicionar label</mat-label>
1491
+ <mat-label>Adicionar rotulo</mat-label>
1284
1492
  <input matInput [(ngModel)]="newLabel" (keyup.enter)="addLabel()" placeholder="Ex.: Dados Gerais" />
1285
1493
  </mat-form-field>
1286
1494
  <button mat-flat-button color="primary" (click)="addLabel()">
@@ -1289,33 +1497,256 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1289
1497
  </button>
1290
1498
  </div>
1291
1499
 
1292
- <div *ngIf="model.labels.length; else emptyLabels" style="display:flex; flex-wrap:wrap; gap: 8px;">
1500
+ <div *ngIf="model.labels.length; else emptyLabels" class="setup-row wrap">
1293
1501
  <div *ngFor="let l of model.labels; let i = index" class="chip">
1294
1502
  <span>{{ l }}</span>
1295
- <button mat-icon-button (click)="removeLabel(i)"><mat-icon [praxisIcon]="'close'"></mat-icon></button>
1503
+ <button mat-icon-button (click)="removeLabel(i)" aria-label="Remover rotulo"><mat-icon [praxisIcon]="'close'"></mat-icon></button>
1296
1504
  </div>
1297
1505
  </div>
1298
1506
  <ng-template #emptyLabels>
1299
- <em>Adicione 1 ou mais labels para criar as abas.</em>
1507
+ <em class="hint">Adicione um ou mais rotulos para criar as abas.</em>
1300
1508
  </ng-template>
1301
1509
 
1302
- <div style="display:flex; gap: 12px; flex-wrap:wrap;">
1303
- <mat-slide-toggle [(ngModel)]="model.dynamicHeight">dynamicHeight</mat-slide-toggle>
1304
- <mat-slide-toggle [(ngModel)]="model.stretchTabs">stretchTabs</mat-slide-toggle>
1510
+ <div class="setup-row">
1511
+ <mat-slide-toggle [(ngModel)]="model.dynamicHeight">Altura dinamica</mat-slide-toggle>
1512
+ <mat-slide-toggle [(ngModel)]="model.stretchTabs">Esticar abas</mat-slide-toggle>
1305
1513
  </div>
1306
1514
  </div>
1307
- `, styles: [".chip{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border:1px solid var(--mat-sys-outline-variant);border-radius:16px}\n"] }]
1515
+ `, styles: [":host{display:block;color:var(--md-sys-color-on-surface)}.setup-root{display:grid;gap:12px;padding:8px;background:var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));border:1px solid var(--md-sys-color-outline-variant);border-radius:12px}.setup-row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.setup-row.wrap{flex-wrap:wrap}.setup-grid{display:grid;grid-template-columns:1fr auto;gap:8px;align-items:start}.setup-root .mat-mdc-form-field{width:100%;max-width:520px;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.chip{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border:1px solid var(--md-sys-color-outline-variant);border-radius:16px;background:var(--md-sys-color-surface)}.hint{color:var(--md-sys-color-on-surface-variant)}\n"] }]
1308
1516
  }], ctorParameters: () => [{ type: undefined, decorators: [{
1309
1517
  type: Inject,
1310
1518
  args: [SETTINGS_PANEL_DATA]
1311
1519
  }] }] });
1312
1520
 
1521
+ /**
1522
+ * Capabilities catalog for Praxis Tabs (TabsMetadata).
1523
+ * Paths follow TabsMetadata shape (patches are merged at config root).
1524
+ */
1525
+ const ENUMS = {
1526
+ appearanceDensity: ['compact', 'comfortable', 'spacious'],
1527
+ alignTabs: ['start', 'center', 'end'],
1528
+ headerPosition: ['above', 'below'],
1529
+ matColor: ['primary', 'accent', 'warn'],
1530
+ };
1531
+ const TOKENS = [
1532
+ 'active-indicator-color',
1533
+ 'active-label-text-color',
1534
+ 'active-hover-indicator-color',
1535
+ 'active-hover-label-text-color',
1536
+ 'active-focus-indicator-color',
1537
+ 'active-focus-label-text-color',
1538
+ 'inactive-label-text-color',
1539
+ 'inactive-hover-label-text-color',
1540
+ 'inactive-focus-label-text-color',
1541
+ 'inactive-ripple-color',
1542
+ 'pagination-icon-color',
1543
+ 'divider-color',
1544
+ 'background-color',
1545
+ ];
1546
+ const TOKEN_CAPS = TOKENS.map((t) => ({
1547
+ path: `appearance.tokens.${t}`,
1548
+ category: 'appearance',
1549
+ valueKind: 'string',
1550
+ description: `Token ${t} (CSS color or var).`,
1551
+ }));
1552
+ const CAPS = [
1553
+ { path: 'meta', category: 'meta', valueKind: 'object', description: 'Metadata bag for tabs.' },
1554
+ { path: 'appearance', category: 'appearance', valueKind: 'object', description: 'Appearance settings.' },
1555
+ { path: 'appearance.density', category: 'appearance', valueKind: 'enum', allowedValues: ENUMS.appearanceDensity, description: 'Density preset.' },
1556
+ { path: 'appearance.themeClass', category: 'appearance', valueKind: 'string', description: 'Theme CSS class applied to root.' },
1557
+ { path: 'appearance.customCss', category: 'appearance', valueKind: 'string', description: 'Custom CSS injected into component.', safetyNotes: 'CSS is injected as raw string.' },
1558
+ { path: 'appearance.tokens', category: 'appearance', valueKind: 'object', description: 'Style tokens mapped to runtime CSS.' },
1559
+ ...TOKEN_CAPS,
1560
+ { path: 'behavior', category: 'behavior', valueKind: 'object', description: 'Behavior settings.' },
1561
+ { path: 'behavior.lazyLoad', category: 'behavior', valueKind: 'boolean', description: 'Lazy load tab content.' },
1562
+ { path: 'behavior.closeable', category: 'behavior', valueKind: 'boolean', description: 'Show close button on tabs.' },
1563
+ { path: 'behavior.reorderable', category: 'behavior', valueKind: 'boolean', description: 'Allow reorder tabs/links.' },
1564
+ { path: 'accessibility', category: 'accessibility', valueKind: 'object', description: 'Accessibility settings.' },
1565
+ { path: 'accessibility.ariaLabels', category: 'accessibility', valueKind: 'object', description: 'aria-labels map.' },
1566
+ { path: 'accessibility.keyboardNavigation', category: 'accessibility', valueKind: 'boolean', description: 'Enable keyboard navigation.' },
1567
+ { path: 'accessibility.highContrast', category: 'accessibility', valueKind: 'boolean', description: 'High contrast mode.' },
1568
+ { path: 'accessibility.reduceMotion', category: 'accessibility', valueKind: 'boolean', description: 'Reduce motion.' },
1569
+ { path: 'group', category: 'group', valueKind: 'object', description: 'MatTabGroup settings.' },
1570
+ { path: 'group.alignTabs', category: 'group', valueKind: 'enum', allowedValues: ENUMS.alignTabs, description: 'Align tabs in header.' },
1571
+ { path: 'group.animationDuration', category: 'group', valueKind: 'string', description: 'Animation duration (e.g., 500ms).' },
1572
+ { path: 'group.ariaLabel', category: 'group', valueKind: 'string', description: 'aria-label for tab group.' },
1573
+ { path: 'group.ariaLabelledby', category: 'group', valueKind: 'string', description: 'aria-labelledby for tab group.' },
1574
+ { path: 'group.color', category: 'group', valueKind: 'enum', allowedValues: ENUMS.matColor, description: 'M2 color.' },
1575
+ { path: 'group.backgroundColor', category: 'group', valueKind: 'enum', allowedValues: ENUMS.matColor, description: 'M2 background color.' },
1576
+ { path: 'group.contentTabIndex', category: 'group', valueKind: 'number', description: 'TabIndex for content.' },
1577
+ { path: 'group.disablePagination', category: 'group', valueKind: 'boolean', description: 'Disable pagination of header.' },
1578
+ { path: 'group.disableRipple', category: 'group', valueKind: 'boolean', description: 'Disable ripple.' },
1579
+ { path: 'group.dynamicHeight', category: 'group', valueKind: 'boolean', description: 'Enable dynamic height.' },
1580
+ { path: 'group.fitInkBarToContent', category: 'group', valueKind: 'boolean', description: 'Fit ink bar to content.' },
1581
+ { path: 'group.headerPosition', category: 'group', valueKind: 'enum', allowedValues: ENUMS.headerPosition, description: 'Header position.' },
1582
+ { path: 'group.preserveContent', category: 'group', valueKind: 'boolean', description: 'Preserve content of inactive tabs.' },
1583
+ { path: 'group.selectedIndex', category: 'group', valueKind: 'number', description: 'Selected tab index.' },
1584
+ { path: 'group.stretchTabs', category: 'group', valueKind: 'boolean', description: 'Stretch tabs.' },
1585
+ { path: 'nav', category: 'nav', valueKind: 'object', description: 'Tab nav settings.' },
1586
+ { path: 'nav.animationDuration', category: 'nav', valueKind: 'string', description: 'Animation duration (e.g., 500ms).' },
1587
+ { path: 'nav.color', category: 'nav', valueKind: 'enum', allowedValues: ENUMS.matColor, description: 'M2 color.' },
1588
+ { path: 'nav.backgroundColor', category: 'nav', valueKind: 'enum', allowedValues: ENUMS.matColor, description: 'M2 background color.' },
1589
+ { path: 'nav.disablePagination', category: 'nav', valueKind: 'boolean', description: 'Disable pagination of nav.' },
1590
+ { path: 'nav.disableRipple', category: 'nav', valueKind: 'boolean', description: 'Disable ripple.' },
1591
+ { path: 'nav.fitInkBarToContent', category: 'nav', valueKind: 'boolean', description: 'Fit ink bar to content.' },
1592
+ { path: 'nav.selectedIndex', category: 'nav', valueKind: 'number', description: 'Selected nav link index.' },
1593
+ { path: 'nav.stretchTabs', category: 'nav', valueKind: 'boolean', description: 'Stretch nav tabs.' },
1594
+ { path: 'nav.links', category: 'nav', valueKind: 'array', description: 'Nav links array.' },
1595
+ { path: 'nav.links[].id', category: 'nav', valueKind: 'string', description: 'Link id.' },
1596
+ { path: 'nav.links[].label', category: 'nav', valueKind: 'string', description: 'Link label.' },
1597
+ { path: 'nav.links[].active', category: 'nav', valueKind: 'boolean', description: 'Link active flag.' },
1598
+ { path: 'nav.links[].disabled', category: 'nav', valueKind: 'boolean', description: 'Disable link.' },
1599
+ { path: 'nav.links[].disableRipple', category: 'nav', valueKind: 'boolean', description: 'Disable ripple on link.' },
1600
+ { path: 'nav.links[].fitInkBarToContent', category: 'nav', valueKind: 'boolean', description: 'Fit ink bar to link content.' },
1601
+ { path: 'nav.links[].content', category: 'nav', valueKind: 'array', description: 'Dynamic field metadata list.' },
1602
+ { path: 'nav.links[].widgets', category: 'nav', valueKind: 'array', description: 'Widget definitions for link content.' },
1603
+ { path: 'tabs', category: 'tabs', valueKind: 'array', description: 'Tabs array.' },
1604
+ { path: 'tabs[].id', category: 'tabs', valueKind: 'string', description: 'Tab id.' },
1605
+ { path: 'tabs[].textLabel', category: 'tabs', valueKind: 'string', description: 'Tab label.' },
1606
+ { path: 'tabs[].labelClass', category: 'tabs', valueKind: 'string', description: 'Label class (string or string[]).' },
1607
+ { path: 'tabs[].bodyClass', category: 'tabs', valueKind: 'string', description: 'Body class (string or string[]).' },
1608
+ { path: 'tabs[].ariaLabel', category: 'tabs', valueKind: 'string', description: 'aria-label for tab.' },
1609
+ { path: 'tabs[].ariaLabelledby', category: 'tabs', valueKind: 'string', description: 'aria-labelledby for tab.' },
1610
+ { path: 'tabs[].disabled', category: 'tabs', valueKind: 'boolean', description: 'Disable tab.' },
1611
+ { path: 'tabs[].content', category: 'tabs', valueKind: 'array', description: 'Dynamic field metadata list.' },
1612
+ { path: 'tabs[].widgets', category: 'tabs', valueKind: 'array', description: 'Widget definitions for tab content.' },
1613
+ { path: 'tabs[].isActive', category: 'tabs', valueKind: 'boolean', description: 'Active flag (metadata only).' },
1614
+ { path: 'tabs[].origin', category: 'tabs', valueKind: 'number', description: 'Origin index (metadata only).' },
1615
+ { path: 'tabs[].position', category: 'tabs', valueKind: 'number', description: 'Position index (metadata only).' },
1616
+ { path: 'events', category: 'events', valueKind: 'object', description: 'Event config (placeholder).' },
1617
+ ];
1618
+ const TABS_AI_CAPABILITIES = {
1619
+ version: 'v1.0',
1620
+ enums: ENUMS,
1621
+ targets: [
1622
+ 'praxis-tabs',
1623
+ 'tab-group',
1624
+ 'tab-item',
1625
+ 'praxis-tabs-config-editor',
1626
+ 'praxis-tabs-quick-setup',
1627
+ ],
1628
+ notes: [
1629
+ 'TabsMetadata drives both group and nav modes. Use nav.* when links are present.',
1630
+ 'appearance.customCss is injected into a <style> tag; avoid unsafe CSS from untrusted sources.',
1631
+ 'tabs[].content expects DynamicFieldMetadata[] and widgets[] expects WidgetDefinition[].',
1632
+ ],
1633
+ capabilities: CAPS,
1634
+ };
1635
+
1636
+ class TabsAiAdapter extends BaseAiAdapter {
1637
+ tabs;
1638
+ componentName = 'Praxis Tabs';
1639
+ constructor(tabs) {
1640
+ super();
1641
+ this.tabs = tabs;
1642
+ }
1643
+ getCurrentConfig() {
1644
+ return this.cloneConfig(this.tabs.config || {});
1645
+ }
1646
+ getCapabilities() {
1647
+ return TABS_AI_CAPABILITIES.capabilities;
1648
+ }
1649
+ getRuntimeState() {
1650
+ const cfg = this.tabs.config;
1651
+ return {
1652
+ mode: cfg?.nav?.links?.length ? 'nav' : 'group',
1653
+ tabsCount: cfg?.tabs?.length ?? 0,
1654
+ linksCount: cfg?.nav?.links?.length ?? 0,
1655
+ lazyLoad: !!cfg?.behavior?.lazyLoad,
1656
+ selectedIndex: cfg?.group?.selectedIndex ?? 0,
1657
+ navIndex: cfg?.nav?.selectedIndex ?? 0,
1658
+ };
1659
+ }
1660
+ createSnapshot() {
1661
+ return this.cloneConfig(this.tabs.config || {});
1662
+ }
1663
+ async restoreSnapshot(snapshot) {
1664
+ if (!snapshot)
1665
+ return;
1666
+ this.applyConfig(snapshot);
1667
+ }
1668
+ async applyPatch(patch) {
1669
+ try {
1670
+ const current = this.getCurrentConfig();
1671
+ const next = this.smartMergeTabsConfig(current, patch);
1672
+ this.applyConfig(next);
1673
+ return { success: true };
1674
+ }
1675
+ catch (error) {
1676
+ const message = error instanceof Error ? error.message : 'Unknown error applying patch';
1677
+ return { success: false, error: message };
1678
+ }
1679
+ }
1680
+ applyConfig(config) {
1681
+ const apply = this.tabs.applyConfigFromAdapter;
1682
+ if (typeof apply === 'function') {
1683
+ apply.call(this.tabs, config);
1684
+ return;
1685
+ }
1686
+ const prev = this.tabs.config;
1687
+ this.tabs.config = config;
1688
+ this.tabs.ngOnChanges({
1689
+ config: {
1690
+ previousValue: prev,
1691
+ currentValue: config,
1692
+ firstChange: false,
1693
+ isFirstChange: () => false,
1694
+ },
1695
+ });
1696
+ }
1697
+ smartMergeTabsConfig(base, patch) {
1698
+ const result = deepMerge(base, patch);
1699
+ if (patch.tabs && Array.isArray(patch.tabs)) {
1700
+ const merged = this.mergeByKey(base.tabs || [], patch.tabs, (t) => t.id || t.textLabel || '');
1701
+ result.tabs = merged;
1702
+ }
1703
+ const patchLinks = patch.nav?.links;
1704
+ if (patchLinks && Array.isArray(patchLinks)) {
1705
+ const merged = this.mergeByKey(base.nav?.links || [], patchLinks, (l) => l.id || l.label || '');
1706
+ result.nav = { ...(result.nav || {}), links: merged };
1707
+ }
1708
+ return result;
1709
+ }
1710
+ mergeByKey(baseArr, patchArr, keyFn) {
1711
+ const merged = baseArr.map((orig) => {
1712
+ const key = keyFn(orig);
1713
+ const match = key ? patchArr.find((p) => keyFn(p) === key) : undefined;
1714
+ return match ? deepMerge(orig, match) : orig;
1715
+ });
1716
+ patchArr.forEach((item) => {
1717
+ const key = keyFn(item);
1718
+ if (!key || !baseArr.find((o) => keyFn(o) === key)) {
1719
+ merged.push(item);
1720
+ }
1721
+ });
1722
+ return merged;
1723
+ }
1724
+ cloneConfig(config) {
1725
+ try {
1726
+ return structuredClone(config);
1727
+ }
1728
+ catch {
1729
+ return JSON.parse(JSON.stringify(config));
1730
+ }
1731
+ }
1732
+ }
1733
+
1313
1734
  class PraxisTabs {
1314
1735
  settings = inject(SettingsPanelService);
1315
- storage = inject(CONFIG_STORAGE);
1736
+ storage = inject(ASYNC_CONFIG_STORAGE);
1316
1737
  snack = inject(MatSnackBar);
1738
+ componentKeys = inject(ComponentKeyService);
1739
+ logger = inject(LoggerService);
1740
+ route = (() => { try {
1741
+ return inject(ActivatedRoute);
1742
+ }
1743
+ catch {
1744
+ return undefined;
1745
+ } })();
1746
+ warnedMissingId = false;
1317
1747
  config = null;
1318
1748
  tabsId;
1749
+ componentInstanceId;
1319
1750
  editModeEnabled = false;
1320
1751
  form = null;
1321
1752
  context = null;
@@ -1326,42 +1757,38 @@ class PraxisTabs {
1326
1757
  indexFocused = new EventEmitter();
1327
1758
  selectFocusedIndex = new EventEmitter();
1328
1759
  widgetEvent = new EventEmitter();
1760
+ aiAdapter = new TabsAiAdapter(this);
1329
1761
  // Signals to manage local state for selection in Nav mode and Group mode
1330
1762
  currentNavIndex = signal(0, ...(ngDevMode ? [{ debugName: "currentNavIndex" }] : []));
1331
1763
  selectedIndexSignal = signal(0, ...(ngDevMode ? [{ debugName: "selectedIndexSignal" }] : []));
1332
1764
  groupLoaded = new Set();
1333
1765
  navLoaded = new Set();
1766
+ destroy$ = new Subject();
1334
1767
  ngOnInit() {
1768
+ this.syncSelectionFromConfig();
1335
1769
  // Load stored config if tabsId provided
1336
- if (this.tabsId) {
1337
- const stored = this.storage.loadConfig(this.storageKey());
1338
- if (stored) {
1339
- this.config = stored;
1340
- }
1770
+ const key = this.storageKey();
1771
+ if (key) {
1772
+ this.storage.loadConfig(key).pipe(take(1)).subscribe((stored) => {
1773
+ if (stored) {
1774
+ this.config = stored;
1775
+ }
1776
+ this.syncSelectionFromConfig();
1777
+ });
1341
1778
  }
1342
- // Initialize selected indices from config
1343
- this.selectedIndexSignal.set(this.config?.group?.selectedIndex ?? 0);
1344
- this.currentNavIndex.set(this.config?.nav?.selectedIndex ?? 0);
1345
- // Mark initial selections as loaded for lazy mode
1346
- this.groupLoaded.add(this.selectedIndexSignal() ?? 0);
1347
- this.navLoaded.add(this.currentNavIndex() ?? 0);
1348
1779
  }
1349
1780
  ngOnChanges(changes) {
1350
1781
  if (changes['config'] && this.config) {
1351
1782
  // Reset loaded caches on config change and seed with current selections
1352
- this.groupLoaded.clear();
1353
- this.navLoaded.clear();
1354
- // Keep indices in sync when config input changes
1355
- this.selectedIndexSignal.set(this.config?.group?.selectedIndex ?? 0);
1356
- this.currentNavIndex.set(this.config?.nav?.selectedIndex ?? 0);
1357
- this.groupLoaded.add(this.selectedIndexSignal() ?? 0);
1358
- this.navLoaded.add(this.currentNavIndex() ?? 0);
1783
+ this.syncSelectionFromConfig();
1359
1784
  // Persist when tabsId provided
1360
- if (this.tabsId) {
1361
- this.storage.saveConfig(this.storageKey(), this.config);
1362
- }
1785
+ this.persistConfig(this.config);
1363
1786
  }
1364
1787
  }
1788
+ ngOnDestroy() {
1789
+ this.destroy$.next();
1790
+ this.destroy$.complete();
1791
+ }
1365
1792
  isNavMode() {
1366
1793
  return !!this.config?.nav && !!this.config?.nav?.links?.length;
1367
1794
  }
@@ -1375,9 +1802,18 @@ class PraxisTabs {
1375
1802
  return this.currentNavIndex() === i;
1376
1803
  }
1377
1804
  onNavClick(i) {
1378
- if (!this.config?.nav)
1805
+ if (!this.config?.nav?.links?.length)
1806
+ return;
1807
+ const linksCount = this.config.nav.links.length;
1808
+ if (i < 0 || i >= linksCount)
1379
1809
  return;
1380
1810
  this.currentNavIndex.set(i);
1811
+ this.config = produce(this.config, (draft) => {
1812
+ if (!draft.nav)
1813
+ return;
1814
+ draft.nav.selectedIndex = i;
1815
+ });
1816
+ this.persistConfig(this.config);
1381
1817
  // Lazy: mark as loaded
1382
1818
  this.navLoaded.add(i);
1383
1819
  // Emit as index change for consumers to track
@@ -1388,6 +1824,9 @@ class PraxisTabs {
1388
1824
  return;
1389
1825
  const prev = event.previousIndex;
1390
1826
  const curr = event.currentIndex;
1827
+ const linksLength = this.config.nav.links.length;
1828
+ if (prev < 0 || curr < 0 || prev >= linksLength || curr >= linksLength)
1829
+ return;
1391
1830
  if (prev === curr)
1392
1831
  return;
1393
1832
  const selectedId = this.config.nav.links[this.currentNavIndex()]?.id;
@@ -1403,43 +1842,74 @@ class PraxisTabs {
1403
1842
  this.currentNavIndex.set(idx);
1404
1843
  }
1405
1844
  else {
1406
- // If no id, try to preserve selection around movement
1407
- if (this.currentNavIndex() === prev)
1845
+ // If no id, preserve selected index around move.
1846
+ const selected = this.currentNavIndex();
1847
+ if (selected === prev) {
1408
1848
  this.currentNavIndex.set(curr);
1849
+ }
1850
+ else if (prev < selected && curr >= selected) {
1851
+ this.currentNavIndex.set(selected - 1);
1852
+ }
1853
+ else if (prev > selected && curr <= selected) {
1854
+ this.currentNavIndex.set(selected + 1);
1855
+ }
1409
1856
  }
1410
1857
  // Reset lazy cache for nav except current
1411
1858
  const currIdx = this.currentNavIndex();
1412
1859
  this.navLoaded.clear();
1413
1860
  this.navLoaded.add(currIdx);
1861
+ this.config = produce(this.config, (draft) => {
1862
+ if (!draft.nav)
1863
+ return;
1864
+ draft.nav.selectedIndex = currIdx;
1865
+ });
1866
+ this.persistConfig(this.config);
1414
1867
  }
1415
1868
  onSelectedIndexChange(index) {
1416
- this.selectedIndexSignal.set(index);
1869
+ const selected = this.clampIndex(index, this.config?.tabs?.length ?? 0);
1870
+ this.selectedIndexSignal.set(selected);
1417
1871
  // Update config immutably
1418
1872
  if (this.config) {
1419
1873
  this.config = produce(this.config, (draft) => {
1420
1874
  if (!draft.group) {
1421
- draft.group = { selectedIndex: index };
1875
+ draft.group = { selectedIndex: selected };
1422
1876
  }
1423
1877
  else {
1424
- draft.group.selectedIndex = index;
1878
+ draft.group.selectedIndex = selected;
1425
1879
  }
1426
1880
  });
1427
- if (this.tabsId) {
1428
- this.storage.saveConfig(this.storageKey(), this.config);
1429
- }
1881
+ this.persistConfig(this.config);
1430
1882
  }
1431
1883
  // Lazy: mark as loaded
1432
- this.groupLoaded.add(index);
1433
- this.selectedIndexChange.emit(index);
1884
+ this.groupLoaded.add(selected);
1885
+ this.selectedIndexChange.emit(selected);
1434
1886
  }
1435
1887
  closeTab(index) {
1436
1888
  if (!this.config?.tabs)
1437
1889
  return;
1890
+ if (index < 0 || index >= this.config.tabs.length)
1891
+ return;
1892
+ const currentSelected = this.selectedIndexSignal();
1438
1893
  this.config = produce(this.config, (draft) => {
1439
1894
  draft.tabs.splice(index, 1);
1895
+ const tabsLength = draft.tabs.length;
1896
+ if (!draft.group) {
1897
+ draft.group = {};
1898
+ }
1899
+ if (tabsLength <= 0) {
1900
+ draft.group.selectedIndex = 0;
1901
+ return;
1902
+ }
1903
+ const adjusted = currentSelected > index ? currentSelected - 1 : currentSelected;
1904
+ draft.group.selectedIndex = this.clampIndex(adjusted, tabsLength);
1440
1905
  });
1441
- if (this.tabsId)
1442
- this.storage.saveConfig(this.storageKey(), this.config);
1906
+ const nextSelected = this.clampIndex(this.config?.group?.selectedIndex, this.config?.tabs?.length ?? 0);
1907
+ this.selectedIndexSignal.set(nextSelected);
1908
+ this.groupLoaded.clear();
1909
+ if ((this.config?.tabs?.length ?? 0) > 0) {
1910
+ this.groupLoaded.add(nextSelected);
1911
+ }
1912
+ this.persistConfig(this.config);
1443
1913
  }
1444
1914
  moveTab(index, delta) {
1445
1915
  if (!this.config?.tabs)
@@ -1470,10 +1940,12 @@ class PraxisTabs {
1470
1940
  const sel = this.selectedIndexSignal() ?? 0;
1471
1941
  this.groupLoaded.clear();
1472
1942
  this.groupLoaded.add(sel);
1943
+ this.persistConfig(this.config);
1473
1944
  }
1474
1945
  openEditor() {
1946
+ const key = this.storageKey() || this.tabsId || 'default';
1475
1947
  const ref = this.settings.open({
1476
- id: `praxis-tabs-editor:${this.tabsId || 'default'}`,
1948
+ id: `praxis-tabs-editor:${key}`,
1477
1949
  title: 'Configurar Tabs',
1478
1950
  content: {
1479
1951
  component: PraxisTabsConfigEditor,
@@ -1481,25 +1953,20 @@ class PraxisTabs {
1481
1953
  },
1482
1954
  });
1483
1955
  // Essential preview: apply updates without closing
1484
- ref.applied$.subscribe((value) => {
1956
+ ref.applied$.pipe(takeUntil(this.destroy$)).subscribe((value) => {
1485
1957
  const nextCfg = value?.config || value;
1486
1958
  if (nextCfg) {
1487
1959
  this.config = produce(this.config || {}, () => nextCfg);
1488
- if (this.tabsId)
1489
- this.storage.saveConfig(this.storageKey(), this.config);
1490
- this.selectedIndexSignal.set(this.config?.group?.selectedIndex ?? 0);
1491
- this.currentNavIndex.set(this.config?.nav?.selectedIndex ?? 0);
1960
+ this.persistConfig(this.config);
1961
+ this.syncSelectionFromConfig();
1492
1962
  }
1493
1963
  });
1494
- ref.saved$.subscribe((value) => {
1964
+ ref.saved$.pipe(takeUntil(this.destroy$)).subscribe((value) => {
1495
1965
  const nextCfg = value?.config || value;
1496
1966
  if (nextCfg) {
1497
1967
  this.config = produce(this.config || {}, () => nextCfg);
1498
- if (this.tabsId)
1499
- this.storage.saveConfig(this.storageKey(), this.config);
1500
- // Sync indices
1501
- this.selectedIndexSignal.set(this.config?.group?.selectedIndex ?? 0);
1502
- this.currentNavIndex.set(this.config?.nav?.selectedIndex ?? 0);
1968
+ this.persistConfig(this.config);
1969
+ this.syncSelectionFromConfig();
1503
1970
  }
1504
1971
  });
1505
1972
  }
@@ -1513,16 +1980,16 @@ class PraxisTabs {
1513
1980
  draft.group.selectedIndex = (draft.tabs.length || 1) - 1;
1514
1981
  });
1515
1982
  this.config = next;
1516
- if (this.tabsId)
1517
- this.storage.saveConfig(this.storageKey(), this.config);
1983
+ this.persistConfig(this.config);
1518
1984
  this.selectedIndexSignal.set(this.config?.group?.selectedIndex ?? 0);
1519
1985
  }
1520
1986
  /** Clears persisted tabs configuration to fallback to defaults */
1521
1987
  resetPreferences() {
1522
- if (!this.tabsId)
1988
+ const key = this.storageKey();
1989
+ if (!key)
1523
1990
  return;
1524
1991
  try {
1525
- this.storage.clearConfig(this.storageKey());
1992
+ this.storage.clearConfig(key).pipe(take(1)).subscribe({ error: () => { } });
1526
1993
  }
1527
1994
  catch { }
1528
1995
  try {
@@ -1532,8 +1999,9 @@ class PraxisTabs {
1532
1999
  // keep current in-memory config; caller may choose to reload
1533
2000
  }
1534
2001
  openQuickSetup() {
2002
+ const key = this.storageKey() || this.tabsId || 'default';
1535
2003
  const ref = this.settings.open({
1536
- id: `praxis-tabs-quick-setup:${this.tabsId || 'default'}`,
2004
+ id: `praxis-tabs-quick-setup:${key}`,
1537
2005
  title: 'Criar abas rapidamente',
1538
2006
  content: {
1539
2007
  component: TabsQuickSetupComponent,
@@ -1544,17 +2012,77 @@ class PraxisTabs {
1544
2012
  const nextCfg = value?.config || value;
1545
2013
  if (nextCfg) {
1546
2014
  this.config = produce(this.config || {}, () => nextCfg);
1547
- if (this.tabsId)
1548
- this.storage.saveConfig(this.storageKey(), this.config);
1549
- this.selectedIndexSignal.set(this.config?.group?.selectedIndex ?? 0);
1550
- this.currentNavIndex.set(this.config?.nav?.selectedIndex ?? 0);
2015
+ this.persistConfig(this.config);
2016
+ this.syncSelectionFromConfig();
1551
2017
  }
1552
2018
  };
1553
- ref.applied$.subscribe(apply);
1554
- ref.saved$.subscribe(apply);
2019
+ ref.applied$.pipe(takeUntil(this.destroy$)).subscribe(apply);
2020
+ ref.saved$.pipe(takeUntil(this.destroy$)).subscribe(apply);
1555
2021
  }
1556
2022
  storageKey() {
1557
- return `tabs:${this.tabsId}`;
2023
+ const id = this.componentKeyId();
2024
+ return id ? `tabs:${id}` : null;
2025
+ }
2026
+ syncSelectionFromConfig() {
2027
+ this.groupLoaded.clear();
2028
+ this.navLoaded.clear();
2029
+ const tabsLength = this.config?.tabs?.length ?? 0;
2030
+ const linksLength = this.config?.nav?.links?.length ?? 0;
2031
+ const groupIndex = this.clampIndex(this.config?.group?.selectedIndex, tabsLength);
2032
+ const navIndex = this.clampIndex(this.config?.nav?.selectedIndex, linksLength);
2033
+ this.selectedIndexSignal.set(groupIndex);
2034
+ this.currentNavIndex.set(navIndex);
2035
+ if (tabsLength > 0)
2036
+ this.groupLoaded.add(groupIndex);
2037
+ if (linksLength > 0)
2038
+ this.navLoaded.add(navIndex);
2039
+ }
2040
+ persistConfig(config) {
2041
+ const key = this.storageKey();
2042
+ if (!key || !config)
2043
+ return;
2044
+ this.storage.saveConfig(key, config).pipe(take(1)).subscribe({ error: () => { } });
2045
+ }
2046
+ componentKeyId() {
2047
+ const key = this.componentKeys.buildComponentId({
2048
+ componentType: 'praxis-tabs',
2049
+ componentId: this.tabsId,
2050
+ instanceKey: this.componentInstanceId,
2051
+ componentRef: this,
2052
+ route: this.route,
2053
+ requireComponentId: true,
2054
+ });
2055
+ if (!key)
2056
+ this.warnMissingId();
2057
+ return key;
2058
+ }
2059
+ warnMissingId() {
2060
+ if (this.warnedMissingId)
2061
+ return;
2062
+ this.warnedMissingId = true;
2063
+ this.logger.warnOnce('[PraxisTabs] tabsId is required for config persistence.', {
2064
+ context: {
2065
+ lib: 'praxis-tabs',
2066
+ component: 'PraxisTabs',
2067
+ },
2068
+ data: {
2069
+ tabsId: this.tabsId,
2070
+ componentInstanceId: this.componentInstanceId,
2071
+ },
2072
+ dedupeKey: 'praxis-tabs:missing-tabs-id',
2073
+ });
2074
+ }
2075
+ clampIndex(index, size) {
2076
+ if (size <= 0)
2077
+ return 0;
2078
+ const numeric = Number(index);
2079
+ const safe = Number.isFinite(numeric) ? Math.trunc(numeric) : 0;
2080
+ return Math.min(Math.max(safe, 0), size - 1);
2081
+ }
2082
+ applyConfigFromAdapter(next) {
2083
+ this.config = next;
2084
+ this.syncSelectionFromConfig();
2085
+ this.persistConfig(this.config);
1558
2086
  }
1559
2087
  // =====================
1560
2088
  // Lazy load helpers
@@ -1576,82 +2104,145 @@ class PraxisTabs {
1576
2104
  emitWidgetEvent(loc, ev) {
1577
2105
  this.widgetEvent.emit({ ...loc, ...ev });
1578
2106
  }
2107
+ styleScopeId() {
2108
+ const scopeSeed = [this.tabsId, this.componentInstanceId]
2109
+ .filter((v) => typeof v === 'string' && !!v.trim())
2110
+ .join('--');
2111
+ const raw = String(scopeSeed || 'default').trim();
2112
+ const sanitized = raw
2113
+ .replace(/[^a-zA-Z0-9_-]+/g, '-')
2114
+ .replace(/-+/g, '-')
2115
+ .replace(/^-|-$/g, '');
2116
+ return sanitized || 'default';
2117
+ }
2118
+ safeCustomCss() {
2119
+ const css = this.config?.appearance?.customCss;
2120
+ if (!css)
2121
+ return null;
2122
+ const value = String(css).trim();
2123
+ if (!value)
2124
+ return null;
2125
+ const forbidden = [
2126
+ /@import/i,
2127
+ /expression\s*\(/i,
2128
+ /behavior\s*:/i,
2129
+ /<\/style/i,
2130
+ /<script/i,
2131
+ /url\s*\(\s*(['"]?)\s*(javascript:|data:text\/html)/i,
2132
+ ];
2133
+ if (forbidden.some((pattern) => pattern.test(value))) {
2134
+ return null;
2135
+ }
2136
+ return value;
2137
+ }
2138
+ sanitizeCssValue(value) {
2139
+ if (typeof value !== 'string')
2140
+ return null;
2141
+ const trimmed = value.trim();
2142
+ if (!trimmed)
2143
+ return null;
2144
+ const forbidden = [
2145
+ /@import/i,
2146
+ /expression\s*\(/i,
2147
+ /behavior\s*:/i,
2148
+ /url\s*\(\s*(['"]?)\s*(javascript:|data:text\/html)/i,
2149
+ ];
2150
+ if (forbidden.some((pattern) => pattern.test(trimmed))) {
2151
+ return null;
2152
+ }
2153
+ const sanitized = trimmed.replace(/[{};<>]/g, '').trim();
2154
+ return sanitized || null;
2155
+ }
1579
2156
  styleCss() {
1580
2157
  const t = this.config?.appearance?.tokens;
1581
2158
  if (!t)
1582
2159
  return null;
1583
- const scope = `.praxis-tabs-root[data-tabs-id="${(this.tabsId || 'default').replace(/"/g, '')}"]`;
2160
+ const scope = `.praxis-tabs-root[data-tabs-id="${this.styleScopeId()}"]`;
1584
2161
  const rules = [];
1585
2162
  const push = (selector, decls) => {
1586
2163
  rules.push(`${scope} ${selector}{${decls.join('')}}`);
1587
2164
  };
1588
2165
  // Active indicator underline
1589
- if (t['active-indicator-color']) {
1590
- push(' .mat-mdc-tab .mdc-tab-indicator__content--underline', [`background-color:${t['active-indicator-color']}!important;`]);
2166
+ const activeIndicatorColor = this.sanitizeCssValue(t['active-indicator-color']);
2167
+ if (activeIndicatorColor) {
2168
+ push(' .mat-mdc-tab .mdc-tab-indicator__content--underline', [`background-color:${activeIndicatorColor}!important;`]);
1591
2169
  }
1592
2170
  // Active label
1593
- if (t['active-label-text-color']) {
2171
+ const activeLabelColor = this.sanitizeCssValue(t['active-label-text-color']);
2172
+ if (activeLabelColor) {
1594
2173
  push(' .mdc-tab--active .mdc-tab__text-label', [
1595
- `color:${t['active-label-text-color']}!important;`,
2174
+ `color:${activeLabelColor}!important;`,
1596
2175
  ]);
1597
2176
  }
1598
- if (t['active-hover-label-text-color']) {
2177
+ const activeHoverLabelColor = this.sanitizeCssValue(t['active-hover-label-text-color']);
2178
+ if (activeHoverLabelColor) {
1599
2179
  push(' .mdc-tab--active:hover .mdc-tab__text-label', [
1600
- `color:${t['active-hover-label-text-color']}!important;`,
2180
+ `color:${activeHoverLabelColor}!important;`,
1601
2181
  ]);
1602
2182
  }
1603
- if (t['active-focus-label-text-color']) {
2183
+ const activeFocusLabelColor = this.sanitizeCssValue(t['active-focus-label-text-color']);
2184
+ if (activeFocusLabelColor) {
1604
2185
  push(' .mdc-tab--active:focus .mdc-tab__text-label', [
1605
- `color:${t['active-focus-label-text-color']}!important;`,
2186
+ `color:${activeFocusLabelColor}!important;`,
1606
2187
  ]);
1607
2188
  }
1608
- if (t['active-focus-indicator-color']) {
2189
+ const activeFocusIndicatorColor = this.sanitizeCssValue(t['active-focus-indicator-color']);
2190
+ if (activeFocusIndicatorColor) {
1609
2191
  push(' .mdc-tab--active:focus .mdc-tab-indicator__content--underline', [
1610
- `background-color:${t['active-focus-indicator-color']}!important;`,
2192
+ `background-color:${activeFocusIndicatorColor}!important;`,
1611
2193
  ]);
1612
2194
  }
1613
- if (t['active-hover-indicator-color']) {
2195
+ const activeHoverIndicatorColor = this.sanitizeCssValue(t['active-hover-indicator-color']);
2196
+ if (activeHoverIndicatorColor) {
1614
2197
  push(' .mdc-tab--active:hover .mdc-tab-indicator__content--underline', [
1615
- `background-color:${t['active-hover-indicator-color']}!important;`,
2198
+ `background-color:${activeHoverIndicatorColor}!important;`,
1616
2199
  ]);
1617
2200
  }
1618
2201
  // Inactive label
1619
- if (t['inactive-label-text-color']) {
2202
+ const inactiveLabelColor = this.sanitizeCssValue(t['inactive-label-text-color']);
2203
+ if (inactiveLabelColor) {
1620
2204
  push(' .mdc-tab:not(.mdc-tab--active) .mdc-tab__text-label', [
1621
- `color:${t['inactive-label-text-color']}!important;`,
2205
+ `color:${inactiveLabelColor}!important;`,
1622
2206
  ]);
1623
2207
  }
1624
- if (t['inactive-hover-label-text-color']) {
2208
+ const inactiveHoverLabelColor = this.sanitizeCssValue(t['inactive-hover-label-text-color']);
2209
+ if (inactiveHoverLabelColor) {
1625
2210
  push(' .mdc-tab:not(.mdc-tab--active):hover .mdc-tab__text-label', [
1626
- `color:${t['inactive-hover-label-text-color']}!important;`,
2211
+ `color:${inactiveHoverLabelColor}!important;`,
1627
2212
  ]);
1628
2213
  }
1629
- if (t['inactive-focus-label-text-color']) {
2214
+ const inactiveFocusLabelColor = this.sanitizeCssValue(t['inactive-focus-label-text-color']);
2215
+ if (inactiveFocusLabelColor) {
1630
2216
  push(' .mdc-tab:not(.mdc-tab--active):focus .mdc-tab__text-label', [
1631
- `color:${t['inactive-focus-label-text-color']}!important;`,
2217
+ `color:${inactiveFocusLabelColor}!important;`,
1632
2218
  ]);
1633
2219
  }
1634
2220
  // Pagination chevrons
1635
- if (t['pagination-icon-color']) {
2221
+ const paginationIconColor = this.sanitizeCssValue(t['pagination-icon-color']);
2222
+ if (paginationIconColor) {
1636
2223
  push(' .mat-mdc-tab-header-pagination-chevron', [
1637
- `color:${t['pagination-icon-color']}!important;`,
2224
+ `color:${paginationIconColor}!important;`,
1638
2225
  ]);
1639
2226
  }
1640
2227
  // Divider / background
1641
2228
  const containerDecls = [];
1642
- if (t['divider-color']) {
1643
- containerDecls.push(`border-bottom-color:${t['divider-color']}!important;`);
2229
+ const dividerColor = this.sanitizeCssValue(t['divider-color']);
2230
+ if (dividerColor) {
2231
+ containerDecls.push(`border-bottom-color:${dividerColor}!important;`);
1644
2232
  }
1645
2233
  if (containerDecls.length) {
1646
2234
  push(' .mat-mdc-tab-header', containerDecls);
2235
+ push(' .mat-mdc-tab-nav-bar', containerDecls);
1647
2236
  }
1648
- if (t['background-color']) {
1649
- push(' .mat-mdc-tab-header', [`background:${t['background-color']}!important;`]);
2237
+ const backgroundColor = this.sanitizeCssValue(t['background-color']);
2238
+ if (backgroundColor) {
2239
+ push(' .mat-mdc-tab-header', [`background:${backgroundColor}!important;`]);
2240
+ push(' .mat-mdc-tab-nav-bar', [`background:${backgroundColor}!important;`]);
1650
2241
  }
1651
2242
  return rules.length ? rules.join('\n') : null;
1652
2243
  }
1653
2244
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
1654
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: PraxisTabs, isStandalone: true, selector: "praxis-tabs", inputs: { config: "config", tabsId: "tabsId", editModeEnabled: "editModeEnabled", form: "form", context: "context" }, outputs: { animationDone: "animationDone", focusChange: "focusChange", selectedIndexChange: "selectedIndexChange", selectedTabChange: "selectedTabChange", indexFocused: "indexFocused", selectFocusedIndex: "selectFocusedIndex", widgetEvent: "widgetEvent" }, usesOnChanges: true, ngImport: i0, template: `
2245
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: PraxisTabs, isStandalone: true, selector: "praxis-tabs", inputs: { config: "config", tabsId: "tabsId", componentInstanceId: "componentInstanceId", editModeEnabled: "editModeEnabled", form: "form", context: "context" }, outputs: { animationDone: "animationDone", focusChange: "focusChange", selectedIndexChange: "selectedIndexChange", selectedTabChange: "selectedTabChange", indexFocused: "indexFocused", selectFocusedIndex: "selectFocusedIndex", widgetEvent: "widgetEvent" }, usesOnChanges: true, ngImport: i0, template: `
1655
2246
  <div
1656
2247
  class="praxis-tabs-root"
1657
2248
  [class.density-compact]="config?.appearance?.density === 'compact'"
@@ -1660,13 +2251,17 @@ class PraxisTabs {
1660
2251
  [class.high-contrast]="config?.accessibility?.highContrast"
1661
2252
  [class.reduce-motion]="config?.accessibility?.reduceMotion"
1662
2253
  [ngClass]="config?.appearance?.themeClass || ''"
1663
- [attr.data-tabs-id]="tabsId || 'default'"
2254
+ [attr.data-tabs-id]="styleScopeId()"
1664
2255
  >
1665
2256
  <!-- Inline custom CSS, if provided -->
1666
- <style *ngIf="config?.appearance?.customCss" [innerHTML]="config?.appearance?.customCss"></style>
2257
+ <style *ngIf="safeCustomCss() as css" [innerHTML]="css"></style>
1667
2258
  <!-- Runtime style derived from tokens -->
1668
2259
  <style *ngIf="styleCss() as s" [innerHTML]="s"></style>
1669
2260
 
2261
+ <div class="tabs-ai-assistant" *ngIf="editModeEnabled">
2262
+ <praxis-ai-assistant [adapter]="aiAdapter"></praxis-ai-assistant>
2263
+ </div>
2264
+
1670
2265
  <!-- Empty state (global) -->
1671
2266
  <ng-container *ngIf="isEmptyGlobal(); else notEmpty">
1672
2267
  <praxis-empty-state-card
@@ -1686,6 +2281,7 @@ class PraxisTabs {
1686
2281
  <ng-container *ngIf="isNavMode(); else groupMode">
1687
2282
  <nav
1688
2283
  mat-tab-nav-bar
2284
+ [tabPanel]="tabPanel"
1689
2285
  cdkDropList
1690
2286
  cdkDropListOrientation="horizontal"
1691
2287
  [cdkDropListDisabled]="!config?.behavior?.reorderable"
@@ -1722,39 +2318,41 @@ class PraxisTabs {
1722
2318
  </a>
1723
2319
  </nav>
1724
2320
 
1725
- <div class="praxis-tabnav-content" *ngIf="currentNavIndex() >= 0">
1726
- <ng-container
1727
- *ngIf="config?.nav?.links?.[currentNavIndex()] as l"
1728
- >
1729
- <ng-container *ngIf="(l.content?.length || l.widgets?.length) && navContentReady(currentNavIndex()); else emptyNav">
1730
- <ng-container *ngIf="l.content && form">
1731
- <ng-container
1732
- dynamicFieldLoader
1733
- [fields]="l.content || []"
1734
- [formGroup]="form!"
1735
- ></ng-container>
1736
- </ng-container>
1737
- <ng-container *ngIf="l.widgets?.length">
1738
- <ng-container *ngFor="let w of l.widgets; let wi = index">
2321
+ <mat-tab-nav-panel #tabPanel>
2322
+ <div class="praxis-tabnav-content" *ngIf="currentNavIndex() >= 0">
2323
+ <ng-container
2324
+ *ngIf="config?.nav?.links?.[currentNavIndex()] as l"
2325
+ >
2326
+ <ng-container *ngIf="(l.content?.length || l.widgets?.length) && navContentReady(currentNavIndex()); else emptyNav">
2327
+ <ng-container *ngIf="l.content && form">
1739
2328
  <ng-container
1740
- [dynamicWidgetLoader]="w"
1741
- [context]="context || {}"
1742
- (widgetEvent)="emitWidgetEvent({ linkId: l.id, linkIndex: currentNavIndex() }, $event)"
2329
+ dynamicFieldLoader
2330
+ [fields]="l.content || []"
2331
+ [formGroup]="form!"
1743
2332
  ></ng-container>
1744
2333
  </ng-container>
2334
+ <ng-container *ngIf="l.widgets?.length">
2335
+ <ng-container *ngFor="let w of l.widgets; let wi = index">
2336
+ <ng-container
2337
+ [dynamicWidgetLoader]="w"
2338
+ [context]="context || {}"
2339
+ (widgetEvent)="emitWidgetEvent({ linkId: l.id, linkIndex: currentNavIndex() }, $event)"
2340
+ ></ng-container>
2341
+ </ng-container>
2342
+ </ng-container>
1745
2343
  </ng-container>
2344
+ <ng-template #emptyNav>
2345
+ <praxis-empty-state-card
2346
+ [inline]="true"
2347
+ icon="add_to_queue"
2348
+ [title]="'Sem conteúdo neste link'"
2349
+ [description]="'Adicione conteúdo ou use o editor para configurar.'"
2350
+ [primaryAction]="{ label: 'Abrir editor', icon: 'tune', action: openEditor.bind(this) }"
2351
+ ></praxis-empty-state-card>
2352
+ </ng-template>
1746
2353
  </ng-container>
1747
- <ng-template #emptyNav>
1748
- <praxis-empty-state-card
1749
- [inline]="true"
1750
- icon="add_to_queue"
1751
- [title]="'Sem conteúdo neste link'"
1752
- [description]="'Adicione conteúdo ou use o editor para configurar.'"
1753
- [primaryAction]="{ label: 'Abrir editor', icon: 'tune', action: openEditor.bind(this) }"
1754
- ></praxis-empty-state-card>
1755
- </ng-template>
1756
- </ng-container>
1757
- </div>
2354
+ </div>
2355
+ </mat-tab-nav-panel>
1758
2356
  </ng-container>
1759
2357
 
1760
2358
  <!-- Tab Group variant -->
@@ -1796,15 +2394,33 @@ class PraxisTabs {
1796
2394
  mat-icon-button
1797
2395
  type="button"
1798
2396
  (click)="closeTab(i); $event.stopPropagation()"
2397
+ (keydown.enter)="$event.stopPropagation()"
2398
+ (keydown.space)="$event.stopPropagation()"
1799
2399
  aria-label="Fechar aba"
1800
2400
  >
1801
2401
  <mat-icon fontIcon="close"></mat-icon>
1802
2402
  </button>
1803
2403
  <ng-container *ngIf="config?.behavior?.reorderable">
1804
- <button mat-icon-button type="button" (click)="moveTab(i, -1); $event.stopPropagation()" [disabled]="i===0" aria-label="Mover aba para esquerda">
2404
+ <button
2405
+ mat-icon-button
2406
+ type="button"
2407
+ (click)="moveTab(i, -1); $event.stopPropagation()"
2408
+ (keydown.enter)="$event.stopPropagation()"
2409
+ (keydown.space)="$event.stopPropagation()"
2410
+ [disabled]="i===0"
2411
+ aria-label="Mover aba para esquerda"
2412
+ >
1805
2413
  <mat-icon fontIcon="arrow_back"></mat-icon>
1806
2414
  </button>
1807
- <button mat-icon-button type="button" (click)="moveTab(i, 1); $event.stopPropagation()" [disabled]="i===(config?.tabs?.length||1)-1" aria-label="Mover aba para direita">
2415
+ <button
2416
+ mat-icon-button
2417
+ type="button"
2418
+ (click)="moveTab(i, 1); $event.stopPropagation()"
2419
+ (keydown.enter)="$event.stopPropagation()"
2420
+ (keydown.space)="$event.stopPropagation()"
2421
+ [disabled]="i===(config?.tabs?.length||1)-1"
2422
+ aria-label="Mover aba para direita"
2423
+ >
1808
2424
  <mat-icon fontIcon="arrow_forward"></mat-icon>
1809
2425
  </button>
1810
2426
  </ng-container>
@@ -1857,8 +2473,7 @@ class PraxisTabs {
1857
2473
  <button
1858
2474
  *ngIf="editModeEnabled && tabsId"
1859
2475
  mat-mini-fab
1860
- class="edit-fab"
1861
- style="right: 56px"
2476
+ class="edit-fab edit-fab-secondary"
1862
2477
  aria-label="Redefinir preferências de tabs"
1863
2478
  (click)="resetPreferences()"
1864
2479
  matTooltip="Redefinir preferências de tabs"
@@ -1866,7 +2481,7 @@ class PraxisTabs {
1866
2481
  <mat-icon [praxisIcon]="'restart_alt'"></mat-icon>
1867
2482
  </button>
1868
2483
  </div>
1869
- `, isInline: true, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.tab-empty{padding:16px;color:#0000008a;font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i3$1.MatTabContent, selector: "[matTabContent]" }, { kind: "directive", type: i3$1.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.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: "component", type: i3$1.MatTabNav, selector: "[mat-tab-nav-bar]", inputs: ["fitInkBarToContent", "mat-stretch-tabs", "animationDuration", "backgroundColor", "disableRipple", "color", "tabPanel"], exportAs: ["matTabNavBar", "matTabNav"] }, { kind: "component", type: i3$1.MatTabLink, selector: "[mat-tab-link], [matTabLink]", inputs: ["active", "disabled", "disableRipple", "tabIndex", "id"], exportAs: ["matTabLink"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5$1.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i5$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i5$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "canvasMouseEnter", "canvasMouseLeave", "canvasClick"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2484
+ `, isInline: true, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.edit-fab-secondary{right:56px}.tab-empty{padding:16px;color:var(--md-sys-color-on-surface-variant);font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i3$1.MatTabContent, selector: "[matTabContent]" }, { kind: "directive", type: i3$1.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.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: "component", type: i3$1.MatTabNav, selector: "[mat-tab-nav-bar]", inputs: ["fitInkBarToContent", "mat-stretch-tabs", "animationDuration", "backgroundColor", "disableRipple", "color", "tabPanel"], exportAs: ["matTabNavBar", "matTabNav"] }, { kind: "component", type: i3$1.MatTabNavPanel, selector: "mat-tab-nav-panel", inputs: ["id"], exportAs: ["matTabNavPanel"] }, { kind: "component", type: i3$1.MatTabLink, selector: "[mat-tab-link], [matTabLink]", inputs: ["active", "disabled", "disableRipple", "tabIndex", "id"], exportAs: ["matTabLink"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i11.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6$1.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent"], exportAs: ["dynamicWidgetLoader"] }, { kind: "component", type: PraxisAiAssistantComponent, selector: "praxis-ai-assistant", inputs: ["adapter", "riskPolicy", "allowManualPatchEdit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1870
2485
  }
1871
2486
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisTabs, decorators: [{
1872
2487
  type: Component,
@@ -1875,12 +2490,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1875
2490
  ReactiveFormsModule,
1876
2491
  MatTabsModule,
1877
2492
  MatIconModule,
2493
+ MatTooltipModule,
1878
2494
  PraxisIconDirective,
1879
2495
  MatButtonModule,
1880
2496
  DragDropModule,
1881
2497
  EmptyStateCardComponent,
1882
2498
  DynamicFieldLoaderDirective,
1883
2499
  DynamicWidgetLoaderDirective,
2500
+ PraxisAiAssistantComponent,
1884
2501
  ], template: `
1885
2502
  <div
1886
2503
  class="praxis-tabs-root"
@@ -1890,13 +2507,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1890
2507
  [class.high-contrast]="config?.accessibility?.highContrast"
1891
2508
  [class.reduce-motion]="config?.accessibility?.reduceMotion"
1892
2509
  [ngClass]="config?.appearance?.themeClass || ''"
1893
- [attr.data-tabs-id]="tabsId || 'default'"
2510
+ [attr.data-tabs-id]="styleScopeId()"
1894
2511
  >
1895
2512
  <!-- Inline custom CSS, if provided -->
1896
- <style *ngIf="config?.appearance?.customCss" [innerHTML]="config?.appearance?.customCss"></style>
2513
+ <style *ngIf="safeCustomCss() as css" [innerHTML]="css"></style>
1897
2514
  <!-- Runtime style derived from tokens -->
1898
2515
  <style *ngIf="styleCss() as s" [innerHTML]="s"></style>
1899
2516
 
2517
+ <div class="tabs-ai-assistant" *ngIf="editModeEnabled">
2518
+ <praxis-ai-assistant [adapter]="aiAdapter"></praxis-ai-assistant>
2519
+ </div>
2520
+
1900
2521
  <!-- Empty state (global) -->
1901
2522
  <ng-container *ngIf="isEmptyGlobal(); else notEmpty">
1902
2523
  <praxis-empty-state-card
@@ -1916,6 +2537,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1916
2537
  <ng-container *ngIf="isNavMode(); else groupMode">
1917
2538
  <nav
1918
2539
  mat-tab-nav-bar
2540
+ [tabPanel]="tabPanel"
1919
2541
  cdkDropList
1920
2542
  cdkDropListOrientation="horizontal"
1921
2543
  [cdkDropListDisabled]="!config?.behavior?.reorderable"
@@ -1952,39 +2574,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
1952
2574
  </a>
1953
2575
  </nav>
1954
2576
 
1955
- <div class="praxis-tabnav-content" *ngIf="currentNavIndex() >= 0">
1956
- <ng-container
1957
- *ngIf="config?.nav?.links?.[currentNavIndex()] as l"
1958
- >
1959
- <ng-container *ngIf="(l.content?.length || l.widgets?.length) && navContentReady(currentNavIndex()); else emptyNav">
1960
- <ng-container *ngIf="l.content && form">
1961
- <ng-container
1962
- dynamicFieldLoader
1963
- [fields]="l.content || []"
1964
- [formGroup]="form!"
1965
- ></ng-container>
1966
- </ng-container>
1967
- <ng-container *ngIf="l.widgets?.length">
1968
- <ng-container *ngFor="let w of l.widgets; let wi = index">
2577
+ <mat-tab-nav-panel #tabPanel>
2578
+ <div class="praxis-tabnav-content" *ngIf="currentNavIndex() >= 0">
2579
+ <ng-container
2580
+ *ngIf="config?.nav?.links?.[currentNavIndex()] as l"
2581
+ >
2582
+ <ng-container *ngIf="(l.content?.length || l.widgets?.length) && navContentReady(currentNavIndex()); else emptyNav">
2583
+ <ng-container *ngIf="l.content && form">
1969
2584
  <ng-container
1970
- [dynamicWidgetLoader]="w"
1971
- [context]="context || {}"
1972
- (widgetEvent)="emitWidgetEvent({ linkId: l.id, linkIndex: currentNavIndex() }, $event)"
2585
+ dynamicFieldLoader
2586
+ [fields]="l.content || []"
2587
+ [formGroup]="form!"
1973
2588
  ></ng-container>
1974
2589
  </ng-container>
2590
+ <ng-container *ngIf="l.widgets?.length">
2591
+ <ng-container *ngFor="let w of l.widgets; let wi = index">
2592
+ <ng-container
2593
+ [dynamicWidgetLoader]="w"
2594
+ [context]="context || {}"
2595
+ (widgetEvent)="emitWidgetEvent({ linkId: l.id, linkIndex: currentNavIndex() }, $event)"
2596
+ ></ng-container>
2597
+ </ng-container>
2598
+ </ng-container>
1975
2599
  </ng-container>
2600
+ <ng-template #emptyNav>
2601
+ <praxis-empty-state-card
2602
+ [inline]="true"
2603
+ icon="add_to_queue"
2604
+ [title]="'Sem conteúdo neste link'"
2605
+ [description]="'Adicione conteúdo ou use o editor para configurar.'"
2606
+ [primaryAction]="{ label: 'Abrir editor', icon: 'tune', action: openEditor.bind(this) }"
2607
+ ></praxis-empty-state-card>
2608
+ </ng-template>
1976
2609
  </ng-container>
1977
- <ng-template #emptyNav>
1978
- <praxis-empty-state-card
1979
- [inline]="true"
1980
- icon="add_to_queue"
1981
- [title]="'Sem conteúdo neste link'"
1982
- [description]="'Adicione conteúdo ou use o editor para configurar.'"
1983
- [primaryAction]="{ label: 'Abrir editor', icon: 'tune', action: openEditor.bind(this) }"
1984
- ></praxis-empty-state-card>
1985
- </ng-template>
1986
- </ng-container>
1987
- </div>
2610
+ </div>
2611
+ </mat-tab-nav-panel>
1988
2612
  </ng-container>
1989
2613
 
1990
2614
  <!-- Tab Group variant -->
@@ -2026,15 +2650,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
2026
2650
  mat-icon-button
2027
2651
  type="button"
2028
2652
  (click)="closeTab(i); $event.stopPropagation()"
2653
+ (keydown.enter)="$event.stopPropagation()"
2654
+ (keydown.space)="$event.stopPropagation()"
2029
2655
  aria-label="Fechar aba"
2030
2656
  >
2031
2657
  <mat-icon fontIcon="close"></mat-icon>
2032
2658
  </button>
2033
2659
  <ng-container *ngIf="config?.behavior?.reorderable">
2034
- <button mat-icon-button type="button" (click)="moveTab(i, -1); $event.stopPropagation()" [disabled]="i===0" aria-label="Mover aba para esquerda">
2660
+ <button
2661
+ mat-icon-button
2662
+ type="button"
2663
+ (click)="moveTab(i, -1); $event.stopPropagation()"
2664
+ (keydown.enter)="$event.stopPropagation()"
2665
+ (keydown.space)="$event.stopPropagation()"
2666
+ [disabled]="i===0"
2667
+ aria-label="Mover aba para esquerda"
2668
+ >
2035
2669
  <mat-icon fontIcon="arrow_back"></mat-icon>
2036
2670
  </button>
2037
- <button mat-icon-button type="button" (click)="moveTab(i, 1); $event.stopPropagation()" [disabled]="i===(config?.tabs?.length||1)-1" aria-label="Mover aba para direita">
2671
+ <button
2672
+ mat-icon-button
2673
+ type="button"
2674
+ (click)="moveTab(i, 1); $event.stopPropagation()"
2675
+ (keydown.enter)="$event.stopPropagation()"
2676
+ (keydown.space)="$event.stopPropagation()"
2677
+ [disabled]="i===(config?.tabs?.length||1)-1"
2678
+ aria-label="Mover aba para direita"
2679
+ >
2038
2680
  <mat-icon fontIcon="arrow_forward"></mat-icon>
2039
2681
  </button>
2040
2682
  </ng-container>
@@ -2087,8 +2729,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
2087
2729
  <button
2088
2730
  *ngIf="editModeEnabled && tabsId"
2089
2731
  mat-mini-fab
2090
- class="edit-fab"
2091
- style="right: 56px"
2732
+ class="edit-fab edit-fab-secondary"
2092
2733
  aria-label="Redefinir preferências de tabs"
2093
2734
  (click)="resetPreferences()"
2094
2735
  matTooltip="Redefinir preferências de tabs"
@@ -2096,10 +2737,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
2096
2737
  <mat-icon [praxisIcon]="'restart_alt'"></mat-icon>
2097
2738
  </button>
2098
2739
  </div>
2099
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.tab-empty{padding:16px;color:#0000008a;font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"] }]
2740
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.edit-fab-secondary{right:56px}.tab-empty{padding:16px;color:var(--md-sys-color-on-surface-variant);font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"] }]
2100
2741
  }], propDecorators: { config: [{
2101
2742
  type: Input
2102
2743
  }], tabsId: [{
2744
+ type: Input,
2745
+ args: [{ required: true }]
2746
+ }], componentInstanceId: [{
2103
2747
  type: Input
2104
2748
  }], editModeEnabled: [{
2105
2749
  type: Input
@@ -2128,15 +2772,21 @@ const PRAXIS_TABS_COMPONENT_METADATA = {
2128
2772
  selector: 'praxis-tabs',
2129
2773
  component: PraxisTabs,
2130
2774
  friendlyName: 'Praxis Tabs',
2131
- description: 'Abas dinâmicas baseadas em metadata, suportando MatTabGroup e TabNav.',
2775
+ description: 'Abas dinâmicas baseadas em metadata, com MatTabGroup/TabNav e suporte a tokens M3 via appearance.',
2132
2776
  icon: 'tab',
2133
2777
  inputs: [
2134
- { name: 'config', type: 'TabsMetadata', label: 'Configuração', description: 'Configuração JSON (tabs/nav e widgets internos)' },
2778
+ { name: 'config', type: 'TabsMetadata', label: 'Configuração', description: 'Configuração JSON (tabs/nav, aparência e widgets internos)' },
2135
2779
  {
2136
2780
  name: 'tabsId',
2137
2781
  type: 'string',
2138
2782
  label: 'ID das Tabs',
2139
- description: 'Identificador para persistência local',
2783
+ description: 'Identificador para persistência (obrigatório)',
2784
+ },
2785
+ {
2786
+ name: 'componentInstanceId',
2787
+ type: 'string',
2788
+ label: 'ID da instância',
2789
+ description: 'Identificador opcional para múltiplas instâncias na mesma rota',
2140
2790
  },
2141
2791
  {
2142
2792
  name: 'editModeEnabled',
@@ -2151,6 +2801,12 @@ const PRAXIS_TABS_COMPONENT_METADATA = {
2151
2801
  label: 'FormGroup',
2152
2802
  description: 'FormGroup para conteúdo com campos dinâmicos',
2153
2803
  },
2804
+ {
2805
+ name: 'context',
2806
+ type: 'Record<string, any>',
2807
+ label: 'Contexto',
2808
+ description: 'Contexto entregue aos widgets internos',
2809
+ },
2154
2810
  ],
2155
2811
  outputs: [
2156
2812
  { name: 'selectedIndexChange', type: 'number', label: 'Índice selecionado' },
@@ -2166,6 +2822,78 @@ const PRAXIS_TABS_COMPONENT_METADATA = {
2166
2822
  description: 'Eventos reemitidos de componentes dinâmicos dentro da aba/link',
2167
2823
  },
2168
2824
  ],
2825
+ actions: [
2826
+ {
2827
+ id: 'select-tab',
2828
+ label: 'Selecionar aba',
2829
+ icon: 'tab',
2830
+ description: 'Emite alteração do índice selecionado',
2831
+ emit: 'selectedIndexChange',
2832
+ payloadSchema: {
2833
+ type: 'number',
2834
+ example: 0,
2835
+ },
2836
+ scope: 'any',
2837
+ },
2838
+ {
2839
+ id: 'tab-change',
2840
+ label: 'Troca de aba',
2841
+ icon: 'swap_horiz',
2842
+ description: 'Emite evento de troca de aba',
2843
+ emit: 'selectedTabChange',
2844
+ payloadSchema: {
2845
+ type: 'object',
2846
+ properties: {
2847
+ index: { type: 'number', description: 'Índice selecionado' },
2848
+ tab: { type: 'object', description: 'Referência da aba' },
2849
+ },
2850
+ required: ['index'],
2851
+ example: { index: 1 },
2852
+ },
2853
+ scope: 'any',
2854
+ },
2855
+ {
2856
+ id: 'focus-change',
2857
+ label: 'Foco alterado',
2858
+ icon: 'center_focus_strong',
2859
+ description: 'Emite evento quando o foco muda',
2860
+ emit: 'focusChange',
2861
+ payloadSchema: {
2862
+ type: 'object',
2863
+ properties: {
2864
+ index: { type: 'number', description: 'Índice focado' },
2865
+ tab: { type: 'object', description: 'Referência da aba' },
2866
+ },
2867
+ required: ['index'],
2868
+ example: { index: 1 },
2869
+ },
2870
+ scope: 'any',
2871
+ },
2872
+ {
2873
+ id: 'focus-index',
2874
+ label: 'Índice focado',
2875
+ icon: 'filter_center_focus',
2876
+ description: 'Emite o índice focado',
2877
+ emit: 'indexFocused',
2878
+ payloadSchema: {
2879
+ type: 'number',
2880
+ example: 0,
2881
+ },
2882
+ scope: 'any',
2883
+ },
2884
+ {
2885
+ id: 'select-focus',
2886
+ label: 'Selecionar foco',
2887
+ icon: 'gps_fixed',
2888
+ description: 'Emite seleção do índice focado',
2889
+ emit: 'selectFocusedIndex',
2890
+ payloadSchema: {
2891
+ type: 'number',
2892
+ example: 0,
2893
+ },
2894
+ scope: 'any',
2895
+ },
2896
+ ],
2169
2897
  tags: ['widget', 'tabs', 'configurable', 'stable'],
2170
2898
  lib: '@praxisui/tabs',
2171
2899
  };
@@ -2188,5 +2916,5 @@ function providePraxisTabsMetadata() {
2188
2916
  * Generated bundle index. Do not edit.
2189
2917
  */
2190
2918
 
2191
- export { PRAXIS_TABS_COMPONENT_METADATA, PraxisTabs, PraxisTabsConfigEditor, providePraxisTabsMetadata };
2919
+ export { PRAXIS_TABS_COMPONENT_METADATA, PraxisTabs, PraxisTabsConfigEditor, TABS_AI_CAPABILITIES, providePraxisTabsMetadata };
2192
2920
  //# sourceMappingURL=praxisui-tabs.mjs.map