@praxisui/stepper 1.0.0-beta.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +229 -0
- package/fesm2022/praxisui-stepper.mjs +2046 -0
- package/fesm2022/praxisui-stepper.mjs.map +1 -0
- package/index.d.ts +240 -0
- package/package.json +43 -0
|
@@ -0,0 +1,2046 @@
|
|
|
1
|
+
import * as i1$1 from '@angular/common';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { Inject, Component, inject, signal, EventEmitter, computed, TemplateRef, Output, Input, ContentChild, ChangeDetectionStrategy, ENVIRONMENT_INITIALIZER } from '@angular/core';
|
|
5
|
+
import * as i2$1 from '@angular/material/stepper';
|
|
6
|
+
import { MatStepperModule } from '@angular/material/stepper';
|
|
7
|
+
import * as i6 from '@angular/material/button';
|
|
8
|
+
import { MatButtonModule } from '@angular/material/button';
|
|
9
|
+
import * as i5$1 from '@angular/material/icon';
|
|
10
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
11
|
+
import { IconPickerService, PraxisIconDirective, DynamicWidgetLoaderDirective, EmptyStateCardComponent, ComponentMetadataRegistry } from '@praxisui/core';
|
|
12
|
+
import * as i2 from '@angular/forms';
|
|
13
|
+
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
14
|
+
import { PraxisDynamicFormConfigEditor, PraxisDynamicForm } from '@praxisui/dynamic-form';
|
|
15
|
+
import { SettingsPanelService, SETTINGS_PANEL_DATA } from '@praxisui/settings-panel';
|
|
16
|
+
import * as i3 from '@angular/material/form-field';
|
|
17
|
+
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
18
|
+
import * as i4 from '@angular/material/input';
|
|
19
|
+
import { MatInputModule } from '@angular/material/input';
|
|
20
|
+
import * as i7 from '@angular/material/slide-toggle';
|
|
21
|
+
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
|
22
|
+
import * as i8 from '@angular/cdk/drag-drop';
|
|
23
|
+
import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
|
|
24
|
+
import * as i9 from '@angular/material/tooltip';
|
|
25
|
+
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
26
|
+
import { MatChipsModule } from '@angular/material/chips';
|
|
27
|
+
import * as i10 from '@angular/material/card';
|
|
28
|
+
import { MatCardModule } from '@angular/material/card';
|
|
29
|
+
import * as i11 from '@angular/material/button-toggle';
|
|
30
|
+
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
|
31
|
+
import * as i1 from '@angular/material/dialog';
|
|
32
|
+
import { MAT_DIALOG_DATA, MatDialogModule, MatDialog } from '@angular/material/dialog';
|
|
33
|
+
import * as i12 from '@angular/material/tabs';
|
|
34
|
+
import { MatTabsModule } from '@angular/material/tabs';
|
|
35
|
+
import { BehaviorSubject } from 'rxjs';
|
|
36
|
+
import { PraxisListConfigEditor } from '@praxisui/list';
|
|
37
|
+
import { PraxisFilesUploadConfigEditor } from '@praxisui/files-upload';
|
|
38
|
+
import { ComponentPaletteDialogComponent } from '@praxisui/page-builder';
|
|
39
|
+
import * as i5 from '@angular/material/checkbox';
|
|
40
|
+
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
41
|
+
|
|
42
|
+
class SelectQuickConfigDialogComponent {
|
|
43
|
+
ref;
|
|
44
|
+
model = {
|
|
45
|
+
label: '',
|
|
46
|
+
name: '',
|
|
47
|
+
resourcePath: '',
|
|
48
|
+
optionLabelKey: 'label',
|
|
49
|
+
optionValueKey: 'id',
|
|
50
|
+
multiple: false,
|
|
51
|
+
required: false,
|
|
52
|
+
};
|
|
53
|
+
constructor(ref, data) {
|
|
54
|
+
this.ref = ref;
|
|
55
|
+
if (data?.from)
|
|
56
|
+
this.model = { ...this.model, ...data.from };
|
|
57
|
+
}
|
|
58
|
+
cancel() { this.ref.close(); }
|
|
59
|
+
confirm() { this.ref.close(this.model); }
|
|
60
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SelectQuickConfigDialogComponent, deps: [{ token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component });
|
|
61
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: SelectQuickConfigDialogComponent, isStandalone: true, selector: "praxis-select-quick-config-dialog", ngImport: i0, template: `
|
|
62
|
+
<h2 mat-dialog-title>Configurar Select (busca)</h2>
|
|
63
|
+
<div mat-dialog-content class="g gap-12">
|
|
64
|
+
<mat-form-field appearance="outline">
|
|
65
|
+
<mat-label>Rótulo</mat-label>
|
|
66
|
+
<input matInput [(ngModel)]="model.label" placeholder="Ex.: Cliente" />
|
|
67
|
+
</mat-form-field>
|
|
68
|
+
<mat-form-field appearance="outline">
|
|
69
|
+
<mat-label>Nome</mat-label>
|
|
70
|
+
<input matInput [(ngModel)]="model.name" placeholder="Ex.: customerId" />
|
|
71
|
+
</mat-form-field>
|
|
72
|
+
<mat-form-field appearance="outline" class="w-full">
|
|
73
|
+
<mat-label>resourcePath</mat-label>
|
|
74
|
+
<input matInput [(ngModel)]="model.resourcePath" placeholder="Ex.: customers" />
|
|
75
|
+
</mat-form-field>
|
|
76
|
+
<div class="g g-auto-220 gap-12">
|
|
77
|
+
<mat-form-field appearance="outline">
|
|
78
|
+
<mat-label>optionLabelKey</mat-label>
|
|
79
|
+
<input matInput [(ngModel)]="model.optionLabelKey" placeholder="Ex.: name" />
|
|
80
|
+
</mat-form-field>
|
|
81
|
+
<mat-form-field appearance="outline">
|
|
82
|
+
<mat-label>optionValueKey</mat-label>
|
|
83
|
+
<input matInput [(ngModel)]="model.optionValueKey" placeholder="Ex.: id" />
|
|
84
|
+
</mat-form-field>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="row gap-16 ai-center">
|
|
87
|
+
<mat-checkbox [(ngModel)]="model.multiple">Múltipla seleção</mat-checkbox>
|
|
88
|
+
<mat-checkbox [(ngModel)]="model.required">Obrigatório</mat-checkbox>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<div mat-dialog-actions align="end">
|
|
92
|
+
<button mat-button (click)="cancel()">Cancelar</button>
|
|
93
|
+
<button mat-flat-button color="primary" (click)="confirm()">Adicionar</button>
|
|
94
|
+
</div>
|
|
95
|
+
`, isInline: true, styles: [".g{display:grid}.gap-12{gap:12px}.w-full{width:100%}.row{display:flex}.ai-center{align-items:center}.gap-16{gap:16px}.g-auto-220{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "directive", type: i1.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i5.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.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"] }] });
|
|
96
|
+
}
|
|
97
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SelectQuickConfigDialogComponent, decorators: [{
|
|
98
|
+
type: Component,
|
|
99
|
+
args: [{ selector: 'praxis-select-quick-config-dialog', standalone: true, imports: [CommonModule, FormsModule, MatDialogModule, MatFormFieldModule, MatInputModule, MatCheckboxModule, MatButtonModule], template: `
|
|
100
|
+
<h2 mat-dialog-title>Configurar Select (busca)</h2>
|
|
101
|
+
<div mat-dialog-content class="g gap-12">
|
|
102
|
+
<mat-form-field appearance="outline">
|
|
103
|
+
<mat-label>Rótulo</mat-label>
|
|
104
|
+
<input matInput [(ngModel)]="model.label" placeholder="Ex.: Cliente" />
|
|
105
|
+
</mat-form-field>
|
|
106
|
+
<mat-form-field appearance="outline">
|
|
107
|
+
<mat-label>Nome</mat-label>
|
|
108
|
+
<input matInput [(ngModel)]="model.name" placeholder="Ex.: customerId" />
|
|
109
|
+
</mat-form-field>
|
|
110
|
+
<mat-form-field appearance="outline" class="w-full">
|
|
111
|
+
<mat-label>resourcePath</mat-label>
|
|
112
|
+
<input matInput [(ngModel)]="model.resourcePath" placeholder="Ex.: customers" />
|
|
113
|
+
</mat-form-field>
|
|
114
|
+
<div class="g g-auto-220 gap-12">
|
|
115
|
+
<mat-form-field appearance="outline">
|
|
116
|
+
<mat-label>optionLabelKey</mat-label>
|
|
117
|
+
<input matInput [(ngModel)]="model.optionLabelKey" placeholder="Ex.: name" />
|
|
118
|
+
</mat-form-field>
|
|
119
|
+
<mat-form-field appearance="outline">
|
|
120
|
+
<mat-label>optionValueKey</mat-label>
|
|
121
|
+
<input matInput [(ngModel)]="model.optionValueKey" placeholder="Ex.: id" />
|
|
122
|
+
</mat-form-field>
|
|
123
|
+
</div>
|
|
124
|
+
<div class="row gap-16 ai-center">
|
|
125
|
+
<mat-checkbox [(ngModel)]="model.multiple">Múltipla seleção</mat-checkbox>
|
|
126
|
+
<mat-checkbox [(ngModel)]="model.required">Obrigatório</mat-checkbox>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
<div mat-dialog-actions align="end">
|
|
130
|
+
<button mat-button (click)="cancel()">Cancelar</button>
|
|
131
|
+
<button mat-flat-button color="primary" (click)="confirm()">Adicionar</button>
|
|
132
|
+
</div>
|
|
133
|
+
`, styles: [".g{display:grid}.gap-12{gap:12px}.w-full{width:100%}.row{display:flex}.ai-center{align-items:center}.gap-16{gap:16px}.g-auto-220{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}\n"] }]
|
|
134
|
+
}], ctorParameters: () => [{ type: i1.MatDialogRef }, { type: undefined, decorators: [{
|
|
135
|
+
type: Inject,
|
|
136
|
+
args: [MAT_DIALOG_DATA]
|
|
137
|
+
}] }] });
|
|
138
|
+
|
|
139
|
+
class PraxisStepperConfigEditor {
|
|
140
|
+
config = { steps: [], orientation: 'horizontal', headerPosition: 'top', linear: false };
|
|
141
|
+
activeIndex = 0;
|
|
142
|
+
// SettingsValueProvider observables
|
|
143
|
+
isDirty$ = new BehaviorSubject(false);
|
|
144
|
+
isValid$ = new BehaviorSubject(true);
|
|
145
|
+
isBusy$ = new BehaviorSubject(false);
|
|
146
|
+
settings = inject(SettingsPanelService);
|
|
147
|
+
dialog = inject(MatDialog);
|
|
148
|
+
iconPicker = inject(IconPickerService);
|
|
149
|
+
constructor(data) {
|
|
150
|
+
let seed = data?.config ?? data;
|
|
151
|
+
// Normalize when seed is a JSON string
|
|
152
|
+
if (typeof seed === 'string') {
|
|
153
|
+
try {
|
|
154
|
+
seed = JSON.parse(seed);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
seed = undefined;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (seed && typeof seed === 'object') {
|
|
161
|
+
// shallow clone to detach references
|
|
162
|
+
this.config = JSON.parse(JSON.stringify(seed));
|
|
163
|
+
if (!Array.isArray(this.config.steps)) {
|
|
164
|
+
this.config.steps = [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
this.config.steps = [{ id: 'step1', label: 'Novo passo' }];
|
|
169
|
+
this.isDirty$.next(true);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
markDirty() { this.isDirty$.next(true); this.validate(); }
|
|
173
|
+
validate() {
|
|
174
|
+
const valid = Array.isArray(this.config.steps) && this.config.steps.every(s => !!s.label);
|
|
175
|
+
this.isValid$.next(valid);
|
|
176
|
+
}
|
|
177
|
+
// ------- Appearance (theming tokens) -------
|
|
178
|
+
get navigationCfg() {
|
|
179
|
+
const nav = (this.config.navigation ??= {});
|
|
180
|
+
return nav;
|
|
181
|
+
}
|
|
182
|
+
get appearance() {
|
|
183
|
+
const ap = (this.config.appearance ??= {});
|
|
184
|
+
(ap.tokens ??= {});
|
|
185
|
+
return ap;
|
|
186
|
+
}
|
|
187
|
+
stepperTokenKeys = [
|
|
188
|
+
'container-color',
|
|
189
|
+
'line-color',
|
|
190
|
+
'header-label-text-color',
|
|
191
|
+
'header-optional-label-text-color',
|
|
192
|
+
'header-icon-foreground-color',
|
|
193
|
+
'header-icon-background-color',
|
|
194
|
+
'header-done-state-icon-background-color',
|
|
195
|
+
'header-done-state-icon-foreground-color',
|
|
196
|
+
'header-edit-state-icon-background-color',
|
|
197
|
+
'header-edit-state-icon-foreground-color',
|
|
198
|
+
'header-error-state-icon-background-color',
|
|
199
|
+
'header-error-state-icon-foreground-color',
|
|
200
|
+
'header-error-state-label-text-color',
|
|
201
|
+
'header-focus-state-layer-color',
|
|
202
|
+
'header-hover-state-layer-color',
|
|
203
|
+
'header-label-text-font',
|
|
204
|
+
'header-label-text-size',
|
|
205
|
+
'header-label-text-weight',
|
|
206
|
+
'header-selected-state-label-text-size',
|
|
207
|
+
'header-selected-state-label-text-weight',
|
|
208
|
+
'container-text-font',
|
|
209
|
+
'header-height',
|
|
210
|
+
'header-focus-state-layer-shape',
|
|
211
|
+
'header-hover-state-layer-shape',
|
|
212
|
+
'header-selected-state-icon-background-color',
|
|
213
|
+
'header-selected-state-icon-foreground-color',
|
|
214
|
+
'header-selected-state-label-text-color',
|
|
215
|
+
];
|
|
216
|
+
onAppearanceChange() { this.markDirty(); }
|
|
217
|
+
onTokenChange(key, value) {
|
|
218
|
+
const tokens = (this.appearance.tokens = this.appearance.tokens || {});
|
|
219
|
+
if (!value) {
|
|
220
|
+
delete tokens[key];
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
tokens[key] = value;
|
|
224
|
+
}
|
|
225
|
+
this.onAppearanceChange();
|
|
226
|
+
}
|
|
227
|
+
clearTokens() {
|
|
228
|
+
if (!this.config.appearance)
|
|
229
|
+
return;
|
|
230
|
+
this.config.appearance.tokens = {};
|
|
231
|
+
this.onAppearanceChange();
|
|
232
|
+
}
|
|
233
|
+
applyPreset(id) {
|
|
234
|
+
const presets = {
|
|
235
|
+
primary: {
|
|
236
|
+
'container-color': 'var(--md-sys-color-surface-container)',
|
|
237
|
+
'header-label-text-color': 'var(--md-sys-color-primary)',
|
|
238
|
+
'header-selected-state-icon-background-color': 'var(--md-sys-color-primary)',
|
|
239
|
+
'header-selected-state-icon-foreground-color': '#fff',
|
|
240
|
+
'line-color': 'color-mix(in oklab, var(--md-sys-color-primary) 40%, transparent)',
|
|
241
|
+
},
|
|
242
|
+
neutral: {
|
|
243
|
+
'container-color': 'var(--md-sys-color-surface)',
|
|
244
|
+
'header-label-text-color': 'var(--md-sys-color-on-surface)',
|
|
245
|
+
'line-color': 'var(--md-sys-color-outline-variant)',
|
|
246
|
+
},
|
|
247
|
+
'high-contrast': {
|
|
248
|
+
'container-color': '#000',
|
|
249
|
+
'header-label-text-color': '#fff',
|
|
250
|
+
'header-selected-state-icon-background-color': 'var(--md-sys-color-on-primary, #ffffff)',
|
|
251
|
+
'header-selected-state-icon-foreground-color': '#000',
|
|
252
|
+
'line-color': '#fff',
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
const preset = presets[id] || {};
|
|
256
|
+
const tokens = (this.appearance.tokens = this.appearance.tokens || {});
|
|
257
|
+
for (const [k, v] of Object.entries(preset)) {
|
|
258
|
+
tokens[k] = v;
|
|
259
|
+
}
|
|
260
|
+
this.onAppearanceChange();
|
|
261
|
+
}
|
|
262
|
+
scssSnippet() {
|
|
263
|
+
const tokens = this.config.appearance?.tokens || {};
|
|
264
|
+
const entries = Object.entries(tokens).filter(([, v]) => !!v);
|
|
265
|
+
const selector = this.config.appearance?.themeClass ? `.${this.config.appearance.themeClass}` : ':root';
|
|
266
|
+
const map = entries.map(([k, v]) => ` ${k}: ${v},`).join('\n');
|
|
267
|
+
return `${selector} {\n @include mat.stepper-overrides((\n${map}\n ));\n}`;
|
|
268
|
+
}
|
|
269
|
+
// ===== Quick editors for common tokens =====
|
|
270
|
+
get headerHeightPx() {
|
|
271
|
+
const raw = this.appearance.tokens?.['header-height'];
|
|
272
|
+
if (!raw)
|
|
273
|
+
return undefined;
|
|
274
|
+
const m = /([0-9]+)\s*px/.exec(raw);
|
|
275
|
+
return m ? Number(m[1]) : undefined;
|
|
276
|
+
}
|
|
277
|
+
setHeaderHeightPx(v) {
|
|
278
|
+
const n = Number(v);
|
|
279
|
+
if (!n) {
|
|
280
|
+
delete this.appearance.tokens['header-height'];
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
this.appearance.tokens['header-height'] = `${n}px`;
|
|
284
|
+
}
|
|
285
|
+
this.onAppearanceChange();
|
|
286
|
+
}
|
|
287
|
+
get headerLabelTextSizePx() {
|
|
288
|
+
const raw = this.appearance.tokens?.['header-label-text-size'];
|
|
289
|
+
if (!raw)
|
|
290
|
+
return undefined;
|
|
291
|
+
const m = /([0-9]+)\s*px/.exec(raw);
|
|
292
|
+
return m ? Number(m[1]) : undefined;
|
|
293
|
+
}
|
|
294
|
+
setHeaderLabelTextSizePx(v) {
|
|
295
|
+
const n = Number(v);
|
|
296
|
+
if (!n) {
|
|
297
|
+
delete this.appearance.tokens['header-label-text-size'];
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
this.appearance.tokens['header-label-text-size'] = `${n}px`;
|
|
301
|
+
}
|
|
302
|
+
this.onAppearanceChange();
|
|
303
|
+
}
|
|
304
|
+
get headerLabelTextWeight() {
|
|
305
|
+
return this.appearance.tokens['header-label-text-weight'];
|
|
306
|
+
}
|
|
307
|
+
setHeaderLabelTextWeight(v) {
|
|
308
|
+
if (!v) {
|
|
309
|
+
delete this.appearance.tokens['header-label-text-weight'];
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
this.appearance.tokens['header-label-text-weight'] = v;
|
|
313
|
+
}
|
|
314
|
+
this.onAppearanceChange();
|
|
315
|
+
}
|
|
316
|
+
// ===== Icons (using IconPickerService) =====
|
|
317
|
+
get icons() {
|
|
318
|
+
const ap = this.appearance;
|
|
319
|
+
ap.icons ||= {};
|
|
320
|
+
return ap.icons;
|
|
321
|
+
}
|
|
322
|
+
async pickNavIcon(field) {
|
|
323
|
+
const picked = await this.iconPicker.openDialog({});
|
|
324
|
+
if (picked) {
|
|
325
|
+
(this.config.navigation ||= {})[field] = picked;
|
|
326
|
+
this.onAppearanceChange();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async pickIcon(kind) {
|
|
330
|
+
const picked = await this.iconPicker.openDialog({});
|
|
331
|
+
if (picked) {
|
|
332
|
+
this.icons[kind] = picked;
|
|
333
|
+
this.onAppearanceChange();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// ===== Icon set suggestions =====
|
|
337
|
+
symbolsLikely(name) {
|
|
338
|
+
if (!name)
|
|
339
|
+
return false;
|
|
340
|
+
// Heurística simples: counter_1..10 e replace_image são típicos do Material Symbols.
|
|
341
|
+
if (/^counter_\d+$/.test(name))
|
|
342
|
+
return true;
|
|
343
|
+
if (name === 'replace_image')
|
|
344
|
+
return true;
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
get symbolsLikelySelected() {
|
|
348
|
+
const names = [this.icons.number, this.icons.done, this.icons.edit, this.icons.error].filter(Boolean);
|
|
349
|
+
return names.filter((n) => this.symbolsLikely(n));
|
|
350
|
+
}
|
|
351
|
+
setIconsSetToSymbols() {
|
|
352
|
+
this.appearance.iconsSet = 'material-symbols-outlined';
|
|
353
|
+
this.onAppearanceChange();
|
|
354
|
+
}
|
|
355
|
+
drop(ev) {
|
|
356
|
+
moveItemInArray(this.config.steps, ev.previousIndex, ev.currentIndex);
|
|
357
|
+
this.markDirty();
|
|
358
|
+
}
|
|
359
|
+
setActive(i) { this.activeIndex = i; }
|
|
360
|
+
onActiveIndexChange() { this.activeIndex = Math.max(0, Math.min(this.activeIndex, (this.config.steps.length || 1) - 1)); }
|
|
361
|
+
addStep() {
|
|
362
|
+
const n = this.config.steps.length + 1;
|
|
363
|
+
this.config.steps.push({ id: `step${n}`, label: `Passo ${n}` });
|
|
364
|
+
this.markDirty();
|
|
365
|
+
}
|
|
366
|
+
removeStep(idx) {
|
|
367
|
+
this.config.steps.splice(idx, 1);
|
|
368
|
+
this.markDirty();
|
|
369
|
+
}
|
|
370
|
+
// ------- Active step helpers -------
|
|
371
|
+
get activeStep() { return this.config.steps?.[this.activeIndex] || null; }
|
|
372
|
+
ensureWidgets() { const s = this.activeStep; if (!s.widgets)
|
|
373
|
+
s.widgets = []; return s.widgets; }
|
|
374
|
+
displayWidgetName(w) {
|
|
375
|
+
switch (w.id) {
|
|
376
|
+
case 'praxis-list': return 'Lista (seleção)';
|
|
377
|
+
case 'pdx-material-searchable-select': return 'Select (busca)';
|
|
378
|
+
case 'praxis-files-upload': return 'Upload de arquivos';
|
|
379
|
+
case 'praxis-dynamic-form': return 'Formulário';
|
|
380
|
+
default: return w.inputs?.title || w.inputs?.label || 'Widget';
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
dropWidget(ev) {
|
|
384
|
+
const arr = this.ensureWidgets();
|
|
385
|
+
moveItemInArray(arr, ev.previousIndex, ev.currentIndex);
|
|
386
|
+
this.markDirty();
|
|
387
|
+
}
|
|
388
|
+
removeWidget(idx) {
|
|
389
|
+
const arr = this.ensureWidgets();
|
|
390
|
+
arr.splice(idx, 1);
|
|
391
|
+
this.markDirty();
|
|
392
|
+
}
|
|
393
|
+
// ------- Main form -------
|
|
394
|
+
addMainForm() {
|
|
395
|
+
const step = this.activeStep;
|
|
396
|
+
if (!step)
|
|
397
|
+
return;
|
|
398
|
+
const ref = this.settings.open({ id: `stepper:form:${step.id || this.activeIndex}`, title: 'Configurar Formulário da Etapa', content: { component: PraxisDynamicFormConfigEditor, inputs: { formConfig: step.form?.config || { sections: [] }, formId: step.form?.formId || step.id, mode: step.form?.mode || 'create' } } });
|
|
399
|
+
const apply = (val) => {
|
|
400
|
+
const v = val || {};
|
|
401
|
+
step.form = {
|
|
402
|
+
config: v.formConfig || step.form?.config || { sections: [] },
|
|
403
|
+
formId: v?.formId || step.form?.formId || step.id,
|
|
404
|
+
mode: v?.inputsPatch?.mode || step.form?.mode || 'create',
|
|
405
|
+
};
|
|
406
|
+
this.markDirty();
|
|
407
|
+
};
|
|
408
|
+
ref.applied$.subscribe(apply);
|
|
409
|
+
ref.saved$.subscribe(apply);
|
|
410
|
+
}
|
|
411
|
+
editMainForm() { this.addMainForm(); }
|
|
412
|
+
removeMainForm() { const s = this.activeStep; if (!s)
|
|
413
|
+
return; delete s.form; this.markDirty(); }
|
|
414
|
+
// ------- Curated widgets -------
|
|
415
|
+
addListSelection() {
|
|
416
|
+
const step = this.activeStep;
|
|
417
|
+
if (!step)
|
|
418
|
+
return;
|
|
419
|
+
const current = { ...(this.ensureWidgets().find(w => w.id === 'praxis-list')?.inputs?.config || {}) };
|
|
420
|
+
// Ensure minimal selection setup
|
|
421
|
+
current.selection = current.selection || { mode: 'single', return: 'value' };
|
|
422
|
+
const ref = this.settings.open({ id: `stepper:list:${step.id || this.activeIndex}`, title: 'Configurar Lista (seleção)', content: { component: PraxisListConfigEditor, inputs: { config: current, listId: step.id || 'list' } } });
|
|
423
|
+
const apply = (val) => {
|
|
424
|
+
const cfg = (val && (val.config || val)) || current;
|
|
425
|
+
const wd = { id: 'praxis-list', inputs: { config: cfg } };
|
|
426
|
+
const arr = this.ensureWidgets();
|
|
427
|
+
arr.push(wd);
|
|
428
|
+
this.markDirty();
|
|
429
|
+
};
|
|
430
|
+
ref.applied$.subscribe(apply);
|
|
431
|
+
ref.saved$.subscribe(apply);
|
|
432
|
+
}
|
|
433
|
+
addSearchableSelect() {
|
|
434
|
+
const step = this.activeStep;
|
|
435
|
+
if (!step)
|
|
436
|
+
return;
|
|
437
|
+
const dlg = this.dialog.open(SelectQuickConfigDialogComponent, { width: '520px', data: {} });
|
|
438
|
+
dlg.afterClosed().subscribe((res) => {
|
|
439
|
+
if (!res)
|
|
440
|
+
return;
|
|
441
|
+
const meta = {
|
|
442
|
+
name: res.name || 'selection',
|
|
443
|
+
label: res.label || 'Seleção',
|
|
444
|
+
required: !!res.required,
|
|
445
|
+
searchable: true,
|
|
446
|
+
multiple: !!res.multiple,
|
|
447
|
+
resourcePath: res.resourcePath || undefined,
|
|
448
|
+
optionLabelKey: res.optionLabelKey || 'label',
|
|
449
|
+
optionValueKey: res.optionValueKey || 'id',
|
|
450
|
+
loadOn: 'open',
|
|
451
|
+
};
|
|
452
|
+
const wd = { id: 'pdx-material-searchable-select', inputs: { metadata: meta } };
|
|
453
|
+
this.ensureWidgets().push(wd);
|
|
454
|
+
this.markDirty();
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
addFilesUpload() {
|
|
458
|
+
const step = this.activeStep;
|
|
459
|
+
if (!step)
|
|
460
|
+
return;
|
|
461
|
+
const ref = this.settings.open({ id: `stepper:upload:${step.id || this.activeIndex}`, title: 'Configurar Upload de Arquivos', content: { component: PraxisFilesUploadConfigEditor, inputs: {} } });
|
|
462
|
+
const apply = (cfg) => {
|
|
463
|
+
const wd = { id: 'praxis-files-upload', inputs: { config: cfg, componentId: `${step.id || 'step'}-upload-${Date.now()}` } };
|
|
464
|
+
this.ensureWidgets().push(wd);
|
|
465
|
+
this.markDirty();
|
|
466
|
+
};
|
|
467
|
+
ref.applied$.subscribe(apply);
|
|
468
|
+
ref.saved$.subscribe(apply);
|
|
469
|
+
}
|
|
470
|
+
openMoreComponents() {
|
|
471
|
+
const dlg = this.dialog.open(ComponentPaletteDialogComponent, { width: '560px', data: { title: 'Adicionar componente à etapa', allowedWidgetIds: ['praxis-dynamic-form', 'praxis-list', 'pdx-material-searchable-select', 'praxis-files-upload'] } });
|
|
472
|
+
dlg.afterClosed().subscribe((id) => {
|
|
473
|
+
if (!id)
|
|
474
|
+
return;
|
|
475
|
+
switch (id) {
|
|
476
|
+
case 'praxis-dynamic-form':
|
|
477
|
+
this.addMainForm();
|
|
478
|
+
break;
|
|
479
|
+
case 'praxis-list':
|
|
480
|
+
this.addListSelection();
|
|
481
|
+
break;
|
|
482
|
+
case 'pdx-material-searchable-select':
|
|
483
|
+
this.addSearchableSelect();
|
|
484
|
+
break;
|
|
485
|
+
case 'praxis-files-upload':
|
|
486
|
+
this.addFilesUpload();
|
|
487
|
+
break;
|
|
488
|
+
default:
|
|
489
|
+
// Fallback: insert minimal widget with no inputs
|
|
490
|
+
this.ensureWidgets().push({ id });
|
|
491
|
+
this.markDirty();
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
// Quick CTAs
|
|
496
|
+
addTreeList() {
|
|
497
|
+
const step = this.activeStep;
|
|
498
|
+
if (!step)
|
|
499
|
+
return;
|
|
500
|
+
const cfg = { mode: 'tree', selection: { mode: 'single' } };
|
|
501
|
+
const wd = { id: 'praxis-list', inputs: { config: cfg } };
|
|
502
|
+
this.ensureWidgets().push(wd);
|
|
503
|
+
this.markDirty();
|
|
504
|
+
}
|
|
505
|
+
addTransferListQuick() {
|
|
506
|
+
const step = this.activeStep;
|
|
507
|
+
if (!step)
|
|
508
|
+
return;
|
|
509
|
+
// Seed a simple form with a transfer list field
|
|
510
|
+
const formConfig = {
|
|
511
|
+
sections: [
|
|
512
|
+
{ rows: [[{ name: 'items', label: 'Itens', controlType: 'TRANSFER_LIST', transferOptions: { source: [], target: [] } }]] }
|
|
513
|
+
]
|
|
514
|
+
};
|
|
515
|
+
const ref = this.settings.open({ id: `stepper:transfer:${step.id || this.activeIndex}`, title: 'Configurar Transferência de Itens', content: { component: PraxisDynamicFormConfigEditor, inputs: { formConfig, formId: step.id || 'transfer' } } });
|
|
516
|
+
const apply = (val) => {
|
|
517
|
+
const v = val || {};
|
|
518
|
+
step.form = { config: v.formConfig || formConfig, formId: v?.formId || step.id, mode: v?.inputsPatch?.mode || 'create' };
|
|
519
|
+
this.markDirty();
|
|
520
|
+
};
|
|
521
|
+
ref.applied$.subscribe(apply);
|
|
522
|
+
ref.saved$.subscribe(apply);
|
|
523
|
+
}
|
|
524
|
+
editWidget(w, index) {
|
|
525
|
+
switch (w.id) {
|
|
526
|
+
case 'praxis-dynamic-form':
|
|
527
|
+
// treat as embedded form
|
|
528
|
+
this.addMainForm();
|
|
529
|
+
break;
|
|
530
|
+
case 'praxis-list': {
|
|
531
|
+
const cfg = w.inputs?.config || {};
|
|
532
|
+
const step = this.activeStep;
|
|
533
|
+
if (!step)
|
|
534
|
+
return;
|
|
535
|
+
const ref = this.settings.open({ id: `stepper:list:${step.id || this.activeIndex}`, title: 'Configurar Lista (seleção)', content: { component: PraxisListConfigEditor, inputs: { config: cfg, listId: step.id || 'list' } } });
|
|
536
|
+
const apply = (val) => {
|
|
537
|
+
const next = (val && (val.config || val)) || cfg;
|
|
538
|
+
w.inputs = { ...(w.inputs || {}), config: next };
|
|
539
|
+
this.markDirty();
|
|
540
|
+
};
|
|
541
|
+
ref.applied$.subscribe(apply);
|
|
542
|
+
ref.saved$.subscribe(apply);
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
case 'pdx-material-searchable-select': {
|
|
546
|
+
const meta = w.inputs?.metadata || {};
|
|
547
|
+
const dlg = this.dialog.open(SelectQuickConfigDialogComponent, { width: '520px', data: { from: meta } });
|
|
548
|
+
dlg.afterClosed().subscribe((res) => {
|
|
549
|
+
if (!res)
|
|
550
|
+
return;
|
|
551
|
+
const next = { ...meta, ...res, searchable: true };
|
|
552
|
+
w.inputs = { ...(w.inputs || {}), metadata: next };
|
|
553
|
+
this.markDirty();
|
|
554
|
+
});
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
case 'praxis-files-upload': {
|
|
558
|
+
const cfg = w.inputs?.config || {};
|
|
559
|
+
const ref = this.settings.open({ id: `stepper:upload:edit`, title: 'Configurar Upload de Arquivos', content: { component: PraxisFilesUploadConfigEditor, inputs: { ...cfg } } });
|
|
560
|
+
const apply = (val) => { w.inputs = { ...(w.inputs || {}), config: val }; this.markDirty(); };
|
|
561
|
+
ref.applied$.subscribe(apply);
|
|
562
|
+
ref.saved$.subscribe(apply);
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
default:
|
|
566
|
+
// No editor available
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
// SettingsValueProvider
|
|
571
|
+
getSettingsValue() { return this.config; }
|
|
572
|
+
onSave() { return this.getSettingsValue(); }
|
|
573
|
+
reset() {
|
|
574
|
+
this.isBusy$.next(true);
|
|
575
|
+
try {
|
|
576
|
+
this.config = { steps: [], orientation: 'horizontal', headerPosition: 'top', linear: false };
|
|
577
|
+
this.isDirty$.next(true);
|
|
578
|
+
this.isValid$.next(true);
|
|
579
|
+
}
|
|
580
|
+
finally {
|
|
581
|
+
this.isBusy$.next(false);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisStepperConfigEditor, deps: [{ token: SETTINGS_PANEL_DATA }], target: i0.ɵɵFactoryTarget.Component });
|
|
585
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: PraxisStepperConfigEditor, isStandalone: true, selector: "praxis-stepper-config-editor", ngImport: i0, template: `
|
|
586
|
+
<div class="pdx-editor">
|
|
587
|
+
<mat-tab-group>
|
|
588
|
+
<mat-tab label="Geral">
|
|
589
|
+
<div class="tab-pad">
|
|
590
|
+
<div class="help">Configure o funcionamento e a aparência básica do passo a passo.</div>
|
|
591
|
+
<div class="pdx-grid">
|
|
592
|
+
<mat-form-field appearance="outline">
|
|
593
|
+
<mat-label>Orientação</mat-label>
|
|
594
|
+
<select matNativeControl [(ngModel)]="config.orientation" (ngModelChange)="markDirty()">
|
|
595
|
+
<option value="horizontal">Horizontal</option>
|
|
596
|
+
<option value="vertical">Vertical</option>
|
|
597
|
+
</select>
|
|
598
|
+
<mat-hint>Horizontal: cabeçalho no topo • Vertical: lista à esquerda</mat-hint>
|
|
599
|
+
</mat-form-field>
|
|
600
|
+
<mat-form-field appearance="outline">
|
|
601
|
+
<mat-label>Posição dos títulos</mat-label>
|
|
602
|
+
<select matNativeControl [(ngModel)]="config.headerPosition" (ngModelChange)="markDirty()">
|
|
603
|
+
<option value="top">Acima</option>
|
|
604
|
+
<option value="bottom">Abaixo</option>
|
|
605
|
+
</select>
|
|
606
|
+
<mat-hint>Válido na orientação horizontal</mat-hint>
|
|
607
|
+
</mat-form-field>
|
|
608
|
+
<mat-form-field appearance="outline">
|
|
609
|
+
<mat-label>Posição do rótulo</mat-label>
|
|
610
|
+
<select matNativeControl [(ngModel)]="config.labelPosition" (ngModelChange)="markDirty()" [disabled]="config.orientation !== 'horizontal'">
|
|
611
|
+
<option value="end">Ao lado</option>
|
|
612
|
+
<option value="bottom">Abaixo</option>
|
|
613
|
+
</select>
|
|
614
|
+
<mat-hint>Como o texto aparece no cabeçalho</mat-hint>
|
|
615
|
+
</mat-form-field>
|
|
616
|
+
<mat-form-field appearance="outline">
|
|
617
|
+
<mat-label>Cor</mat-label>
|
|
618
|
+
<select matNativeControl [(ngModel)]="config.color" (ngModelChange)="markDirty()">
|
|
619
|
+
<option [ngValue]="undefined">Padrão</option>
|
|
620
|
+
<option value="primary">Primária</option>
|
|
621
|
+
<option value="accent">Acento</option>
|
|
622
|
+
<option value="warn">Alerta</option>
|
|
623
|
+
</select>
|
|
624
|
+
<mat-hint>Aplicável a temas Material 2 (M2)</mat-hint>
|
|
625
|
+
</mat-form-field>
|
|
626
|
+
<mat-form-field appearance="outline">
|
|
627
|
+
<mat-label>Duração da animação</mat-label>
|
|
628
|
+
<input matInput [(ngModel)]="config.animationDuration" (ngModelChange)="markDirty()" placeholder="Ex.: 300ms" />
|
|
629
|
+
<mat-hint>Tempo da transição entre etapas</mat-hint>
|
|
630
|
+
</mat-form-field>
|
|
631
|
+
<mat-form-field appearance="outline">
|
|
632
|
+
<mat-label>Etapa selecionada</mat-label>
|
|
633
|
+
<input matInput type="number" [(ngModel)]="config.selectedIndex" (ngModelChange)="markDirty()" />
|
|
634
|
+
<mat-hint>Índice inicia em 0 (primeira etapa)</mat-hint>
|
|
635
|
+
</mat-form-field>
|
|
636
|
+
<mat-form-field appearance="outline">
|
|
637
|
+
<mat-label>Densidade</mat-label>
|
|
638
|
+
<select matNativeControl [(ngModel)]="config.density" (ngModelChange)="markDirty()">
|
|
639
|
+
<option [ngValue]="undefined">Padrão</option>
|
|
640
|
+
<option value="comfortable">Confortável</option>
|
|
641
|
+
<option value="compact">Compacta</option>
|
|
642
|
+
</select>
|
|
643
|
+
<mat-hint>Controla espaçamentos e alturas</mat-hint>
|
|
644
|
+
</mat-form-field>
|
|
645
|
+
<mat-form-field appearance="outline" class="w-full">
|
|
646
|
+
<mat-label>Classe do stepper (opcional)</mat-label>
|
|
647
|
+
<input matInput [(ngModel)]="config.stepperClass" (ngModelChange)="markDirty()" placeholder="Ex.: meu-stepper" />
|
|
648
|
+
<mat-hint>Permite estilizar com CSS escopado</mat-hint>
|
|
649
|
+
</mat-form-field>
|
|
650
|
+
<mat-form-field appearance="outline" class="w-full">
|
|
651
|
+
<mat-label>Classe do cabeçalho (opcional)</mat-label>
|
|
652
|
+
<input matInput [(ngModel)]="config.headerClass" (ngModelChange)="markDirty()" placeholder="Ex.: stepper-header" />
|
|
653
|
+
<mat-hint>Use para ajustes finos de aparência</mat-hint>
|
|
654
|
+
</mat-form-field>
|
|
655
|
+
<mat-form-field appearance="outline" class="w-full">
|
|
656
|
+
<mat-label>Classe do conteúdo (opcional)</mat-label>
|
|
657
|
+
<input matInput [(ngModel)]="config.contentClass" (ngModelChange)="markDirty()" placeholder="Ex.: stepper-content" />
|
|
658
|
+
<mat-hint>Aplica uma classe à área de conteúdo</mat-hint>
|
|
659
|
+
</mat-form-field>
|
|
660
|
+
</div>
|
|
661
|
+
<div class="pdx-toggles">
|
|
662
|
+
<mat-slide-toggle [(ngModel)]="config.linear" (ngModelChange)="markDirty()">Respeitar ordem (linear)</mat-slide-toggle>
|
|
663
|
+
<mat-slide-toggle [(ngModel)]="config.disableRipple" (ngModelChange)="markDirty()">Desativar efeitos de clique</mat-slide-toggle>
|
|
664
|
+
</div>
|
|
665
|
+
</div>
|
|
666
|
+
</mat-tab>
|
|
667
|
+
|
|
668
|
+
<mat-tab label="Etapas">
|
|
669
|
+
<div class="tab-pad">
|
|
670
|
+
<div class="help">Gerencie as etapas: nomes, mensagens e conteúdo. Arraste para reordenar.</div>
|
|
671
|
+
<div class="pdx-steps">
|
|
672
|
+
<div class="pdx-steps-header">
|
|
673
|
+
<div class="title">Etapas ({{ config.steps.length }})</div>
|
|
674
|
+
<button mat-button (click)="addStep()"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar etapa</button>
|
|
675
|
+
</div>
|
|
676
|
+
<div cdkDropList (cdkDropListDropped)="drop($event)" class="pdx-step-list">
|
|
677
|
+
<div class="pdx-step-item" *ngFor="let s of config.steps; let i = index" cdkDrag [class.active]="activeIndex === i" (click)="setActive(i)">
|
|
678
|
+
<div class="drag-handle" cdkDragHandle><mat-icon [praxisIcon]="'drag_indicator'"></mat-icon></div>
|
|
679
|
+
<div class="pdx-fields">
|
|
680
|
+
<mat-form-field appearance="outline" style="min-width: 180px;">
|
|
681
|
+
<mat-label>Título da etapa</mat-label>
|
|
682
|
+
<input matInput [(ngModel)]="s.label" (ngModelChange)="markDirty()" />
|
|
683
|
+
<mat-hint>Como aparecerá no cabeçalho</mat-hint>
|
|
684
|
+
</mat-form-field>
|
|
685
|
+
<mat-form-field appearance="outline">
|
|
686
|
+
<mat-label>Identificador (opcional)</mat-label>
|
|
687
|
+
<input matInput [(ngModel)]="s.id" (ngModelChange)="markDirty()" />
|
|
688
|
+
<mat-hint>Útil para automações</mat-hint>
|
|
689
|
+
</mat-form-field>
|
|
690
|
+
<mat-form-field appearance="outline">
|
|
691
|
+
<mat-label>Texto alternativo (acessibilidade)</mat-label>
|
|
692
|
+
<input matInput [(ngModel)]="s.ariaLabel" (ngModelChange)="markDirty()" />
|
|
693
|
+
<mat-hint>Lido por leitores de tela</mat-hint>
|
|
694
|
+
</mat-form-field>
|
|
695
|
+
<mat-form-field appearance="outline">
|
|
696
|
+
<mat-label>ID que descreve (opcional)</mat-label>
|
|
697
|
+
<input matInput [(ngModel)]="s.ariaLabelledby" (ngModelChange)="markDirty()" />
|
|
698
|
+
<mat-hint>ID de um elemento que descreve a etapa</mat-hint>
|
|
699
|
+
</mat-form-field>
|
|
700
|
+
<mat-form-field appearance="outline">
|
|
701
|
+
<mat-label>Ícone/estado do marcador</mat-label>
|
|
702
|
+
<input matInput [(ngModel)]="s.state" (ngModelChange)="markDirty()" placeholder="Ex.: number, done, edit" />
|
|
703
|
+
<mat-hint>Controla o ícone do passo (quando aplicável)</mat-hint>
|
|
704
|
+
</mat-form-field>
|
|
705
|
+
<mat-form-field appearance="outline">
|
|
706
|
+
<mat-label>Mensagem de erro</mat-label>
|
|
707
|
+
<input matInput [(ngModel)]="s.errorMessage" (ngModelChange)="markDirty()" />
|
|
708
|
+
<mat-hint>Mostrada quando há erro na etapa</mat-hint>
|
|
709
|
+
</mat-form-field>
|
|
710
|
+
<div class="pdx-flag-group">
|
|
711
|
+
<mat-slide-toggle [(ngModel)]="s.optional" (ngModelChange)="markDirty()">Etapa opcional</mat-slide-toggle>
|
|
712
|
+
<mat-slide-toggle [(ngModel)]="s.editable" (ngModelChange)="markDirty()">Permitir voltar e editar</mat-slide-toggle>
|
|
713
|
+
<mat-slide-toggle [(ngModel)]="s.completed" (ngModelChange)="markDirty()">Marcar como concluída</mat-slide-toggle>
|
|
714
|
+
<mat-slide-toggle [(ngModel)]="s.hasError" (ngModelChange)="markDirty()">Marcar como com erro</mat-slide-toggle>
|
|
715
|
+
</div>
|
|
716
|
+
</div>
|
|
717
|
+
<div class="pdx-actions">
|
|
718
|
+
<button mat-icon-button color="warn" (click)="removeStep(i)" matTooltip="Remover">
|
|
719
|
+
<mat-icon [praxisIcon]="'delete'"></mat-icon>
|
|
720
|
+
</button>
|
|
721
|
+
</div>
|
|
722
|
+
</div>
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
725
|
+
|
|
726
|
+
<div class="pdx-active-step" *ngIf="activeStep as step; else noSteps">
|
|
727
|
+
<div class="hdr">
|
|
728
|
+
<div class="title">Editando: {{ step.label }}</div>
|
|
729
|
+
<mat-button-toggle-group [(ngModel)]="activeIndex" (ngModelChange)="onActiveIndexChange()" [hideSingleSelectionIndicator]="true">
|
|
730
|
+
<mat-button-toggle *ngFor="let s of config.steps; let i = index" [value]="i">{{ i + 1 }}</mat-button-toggle>
|
|
731
|
+
</mat-button-toggle-group>
|
|
732
|
+
</div>
|
|
733
|
+
|
|
734
|
+
<div class="pdx-content-editor">
|
|
735
|
+
<div class="section">
|
|
736
|
+
<div class="section-title">Formulário principal</div>
|
|
737
|
+
<div class="section-body">
|
|
738
|
+
<ng-container *ngIf="step.form; else addFormBtn">
|
|
739
|
+
<mat-card appearance="outlined" class="mini-card">
|
|
740
|
+
<mat-card-header>
|
|
741
|
+
<mat-icon mat-card-avatar [praxisIcon]="'dynamic_form'"></mat-icon>
|
|
742
|
+
<mat-card-title>Formulário principal</mat-card-title>
|
|
743
|
+
<mat-card-subtitle>{{ step.form.formId || step.id || '—' }}</mat-card-subtitle>
|
|
744
|
+
</mat-card-header>
|
|
745
|
+
<mat-card-actions align="end">
|
|
746
|
+
<button mat-button (click)="editMainForm()"><mat-icon [praxisIcon]="'tune'"></mat-icon> Editar</button>
|
|
747
|
+
<button mat-button color="warn" (click)="removeMainForm()"><mat-icon [praxisIcon]="'delete'"></mat-icon> Remover</button>
|
|
748
|
+
</mat-card-actions>
|
|
749
|
+
</mat-card>
|
|
750
|
+
</ng-container>
|
|
751
|
+
<ng-template #addFormBtn>
|
|
752
|
+
<button mat-stroked-button color="primary" (click)="addMainForm()"><mat-icon [praxisIcon]="'add'"></mat-icon> Adicionar formulário</button>
|
|
753
|
+
</ng-template>
|
|
754
|
+
</div>
|
|
755
|
+
</div>
|
|
756
|
+
|
|
757
|
+
<div class="section">
|
|
758
|
+
<div class="section-title">Conteúdos e componentes</div>
|
|
759
|
+
<div class="section-body">
|
|
760
|
+
<div class="cta-grid">
|
|
761
|
+
<div class="cta-card mat-elevation-z2">
|
|
762
|
+
<div class="cta-head">
|
|
763
|
+
<mat-icon [praxisIcon]="'dynamic_form'"></mat-icon>
|
|
764
|
+
<div class="cta-title">Formulário dinâmico</div>
|
|
765
|
+
</div>
|
|
766
|
+
<div class="cta-desc">Crie campos e validações personalizadas nesta etapa.</div>
|
|
767
|
+
<div class="cta-actions">
|
|
768
|
+
<button mat-flat-button color="primary" (click)="addMainForm()">
|
|
769
|
+
<mat-icon [praxisIcon]="'add'"></mat-icon>
|
|
770
|
+
Inserir formulário
|
|
771
|
+
</button>
|
|
772
|
+
</div>
|
|
773
|
+
</div>
|
|
774
|
+
<div class="cta-card mat-elevation-z2">
|
|
775
|
+
<div class="cta-head">
|
|
776
|
+
<mat-icon [praxisIcon]="'account_tree'"></mat-icon>
|
|
777
|
+
<div class="cta-title">Lista em árvore</div>
|
|
778
|
+
</div>
|
|
779
|
+
<div class="cta-desc">Exiba dados hierárquicos com seleção simples.</div>
|
|
780
|
+
<div class="cta-actions">
|
|
781
|
+
<button mat-stroked-button (click)="addTreeList()">
|
|
782
|
+
<mat-icon [praxisIcon]="'add'"></mat-icon>
|
|
783
|
+
Inserir árvore
|
|
784
|
+
</button>
|
|
785
|
+
</div>
|
|
786
|
+
</div>
|
|
787
|
+
<div class="cta-card mat-elevation-z2">
|
|
788
|
+
<div class="cta-head">
|
|
789
|
+
<mat-icon [praxisIcon]="'swap_horiz'"></mat-icon>
|
|
790
|
+
<div class="cta-title">Transferência de itens</div>
|
|
791
|
+
</div>
|
|
792
|
+
<div class="cta-desc">Mover itens entre listas (ideal para múltipla seleção).</div>
|
|
793
|
+
<div class="cta-actions">
|
|
794
|
+
<button mat-stroked-button (click)="addTransferListQuick()">
|
|
795
|
+
<mat-icon [praxisIcon]="'add'"></mat-icon>
|
|
796
|
+
Inserir transferência
|
|
797
|
+
</button>
|
|
798
|
+
</div>
|
|
799
|
+
</div>
|
|
800
|
+
</div>
|
|
801
|
+
<div cdkDropList (cdkDropListDropped)="dropWidget($event)" class="widget-list">
|
|
802
|
+
<div class="widget-item" *ngFor="let w of (step.widgets || []); let wi = index" cdkDrag>
|
|
803
|
+
<div class="drag-handle" cdkDragHandle><mat-icon [praxisIcon]="'drag_indicator'"></mat-icon></div>
|
|
804
|
+
<div class="info">
|
|
805
|
+
<div class="name">{{ displayWidgetName(w) }}</div>
|
|
806
|
+
<div class="sub">{{ w.id }}</div>
|
|
807
|
+
</div>
|
|
808
|
+
<div class="actions">
|
|
809
|
+
<button mat-button (click)="editWidget(w, wi)"><mat-icon [praxisIcon]="'tune'"></mat-icon> Editar</button>
|
|
810
|
+
<button mat-button color="warn" (click)="removeWidget(wi)"><mat-icon [praxisIcon]="'delete'"></mat-icon> Remover</button>
|
|
811
|
+
</div>
|
|
812
|
+
</div>
|
|
813
|
+
<div class="empty" *ngIf="!step.widgets || !step.widgets.length">Nenhum componente adicionado</div>
|
|
814
|
+
</div>
|
|
815
|
+
</div>
|
|
816
|
+
</div>
|
|
817
|
+
</div>
|
|
818
|
+
</div>
|
|
819
|
+
<ng-template #noSteps>
|
|
820
|
+
<div class="muted">Nenhuma etapa definida.</div>
|
|
821
|
+
</ng-template>
|
|
822
|
+
</div>
|
|
823
|
+
</mat-tab>
|
|
824
|
+
|
|
825
|
+
<mat-tab label="Navegação">
|
|
826
|
+
<div class="tab-pad">
|
|
827
|
+
<div class="help">Personalize os botões de avançar e voltar exibidos em cada etapa.</div>
|
|
828
|
+
<div class="pdx-grid">
|
|
829
|
+
<mat-form-field appearance="outline">
|
|
830
|
+
<mat-label>Estilo dos botões</mat-label>
|
|
831
|
+
<select matNativeControl [(ngModel)]="navigationCfg.variant" (ngModelChange)="markDirty()">
|
|
832
|
+
<option [ngValue]="undefined">Padrão (elevado)</option>
|
|
833
|
+
<option value="basic">Texto</option>
|
|
834
|
+
<option value="flat">Plano</option>
|
|
835
|
+
<option value="stroked">Contornado</option>
|
|
836
|
+
<option value="raised">Elevado</option>
|
|
837
|
+
</select>
|
|
838
|
+
</mat-form-field>
|
|
839
|
+
<mat-form-field appearance="outline">
|
|
840
|
+
<mat-label>Cor</mat-label>
|
|
841
|
+
<select matNativeControl [(ngModel)]="navigationCfg.color" (ngModelChange)="markDirty()">
|
|
842
|
+
<option [ngValue]="undefined">Padrão</option>
|
|
843
|
+
<option value="primary">Primária</option>
|
|
844
|
+
<option value="accent">Acento</option>
|
|
845
|
+
<option value="warn">Alerta</option>
|
|
846
|
+
</select>
|
|
847
|
+
</mat-form-field>
|
|
848
|
+
<mat-form-field appearance="outline">
|
|
849
|
+
<mat-label>Alinhamento</mat-label>
|
|
850
|
+
<select matNativeControl [(ngModel)]="navigationCfg.align" (ngModelChange)="markDirty()">
|
|
851
|
+
<option [ngValue]="undefined">Direita</option>
|
|
852
|
+
<option value="start">Esquerda</option>
|
|
853
|
+
<option value="center">Centro</option>
|
|
854
|
+
<option value="space-between">Espaçado</option>
|
|
855
|
+
<option value="end">Direita</option>
|
|
856
|
+
</select>
|
|
857
|
+
</mat-form-field>
|
|
858
|
+
</div>
|
|
859
|
+
|
|
860
|
+
<div class="pdx-toggles">
|
|
861
|
+
<mat-slide-toggle [(ngModel)]="navigationCfg.visible" (ngModelChange)="markDirty()">Mostrar navegação padrão</mat-slide-toggle>
|
|
862
|
+
</div>
|
|
863
|
+
|
|
864
|
+
<div class="pdx-grid">
|
|
865
|
+
<mat-form-field appearance="outline">
|
|
866
|
+
<mat-label>Texto do botão Voltar</mat-label>
|
|
867
|
+
<input matInput [(ngModel)]="navigationCfg.prevLabel" (ngModelChange)="markDirty()" placeholder="Ex.: Voltar" />
|
|
868
|
+
</mat-form-field>
|
|
869
|
+
<mat-form-field appearance="outline">
|
|
870
|
+
<mat-label>Texto do botão Próximo</mat-label>
|
|
871
|
+
<input matInput [(ngModel)]="navigationCfg.nextLabel" (ngModelChange)="markDirty()" placeholder="Ex.: Próximo" />
|
|
872
|
+
</mat-form-field>
|
|
873
|
+
</div>
|
|
874
|
+
|
|
875
|
+
<div class="icons-grid">
|
|
876
|
+
<div class="icon-item">
|
|
877
|
+
<div class="icon-head">Ícone de Voltar</div>
|
|
878
|
+
<button mat-stroked-button type="button" (click)="pickNavIcon('prevIcon')">
|
|
879
|
+
<mat-icon *ngIf="navigationCfg.prevIcon" [praxisIcon]="navigationCfg.prevIcon"></mat-icon>
|
|
880
|
+
<ng-container *ngIf="!navigationCfg.prevIcon">Escolher</ng-container>
|
|
881
|
+
</button>
|
|
882
|
+
</div>
|
|
883
|
+
<div class="icon-item">
|
|
884
|
+
<div class="icon-head">Ícone de Próximo</div>
|
|
885
|
+
<button mat-stroked-button type="button" (click)="pickNavIcon('nextIcon')">
|
|
886
|
+
<mat-icon *ngIf="navigationCfg.nextIcon" [praxisIcon]="navigationCfg.nextIcon"></mat-icon>
|
|
887
|
+
<ng-container *ngIf="!navigationCfg.nextIcon">Escolher</ng-container>
|
|
888
|
+
</button>
|
|
889
|
+
</div>
|
|
890
|
+
</div>
|
|
891
|
+
</div>
|
|
892
|
+
</mat-tab>
|
|
893
|
+
|
|
894
|
+
<mat-tab label="Aparência">
|
|
895
|
+
<div class="tab-pad">
|
|
896
|
+
<div class="help">Personalize cores, tipografia e ícones. Para ajustes comuns, use os campos abaixo; para maior controle, edite os tokens. Copie o SCSS e cole no arquivo de tema.</div>
|
|
897
|
+
<div class="quick-grid">
|
|
898
|
+
<mat-form-field appearance="outline">
|
|
899
|
+
<mat-label>Altura do cabeçalho (px)</mat-label>
|
|
900
|
+
<input matInput type="number" [ngModel]="headerHeightPx" (ngModelChange)="setHeaderHeightPx($event)" placeholder="Ex.: 48" />
|
|
901
|
+
<mat-hint>Altura do item do cabeçalho</mat-hint>
|
|
902
|
+
</mat-form-field>
|
|
903
|
+
<mat-form-field appearance="outline">
|
|
904
|
+
<mat-label>Tamanho do texto (px)</mat-label>
|
|
905
|
+
<input matInput type="number" [ngModel]="headerLabelTextSizePx" (ngModelChange)="setHeaderLabelTextSizePx($event)" placeholder="Ex.: 14" />
|
|
906
|
+
<mat-hint>Tamanho do título da etapa</mat-hint>
|
|
907
|
+
</mat-form-field>
|
|
908
|
+
<mat-form-field appearance="outline">
|
|
909
|
+
<mat-label>Peso do texto</mat-label>
|
|
910
|
+
<select matNativeControl [ngModel]="headerLabelTextWeight" (ngModelChange)="setHeaderLabelTextWeight($event)">
|
|
911
|
+
<option [ngValue]="undefined">Padrão</option>
|
|
912
|
+
<option value="400">Normal</option>
|
|
913
|
+
<option value="500">Médio</option>
|
|
914
|
+
<option value="600">Seminegrito</option>
|
|
915
|
+
<option value="700">Negrito</option>
|
|
916
|
+
</select>
|
|
917
|
+
</mat-form-field>
|
|
918
|
+
</div>
|
|
919
|
+
<div class="pdx-grid">
|
|
920
|
+
<mat-form-field appearance="outline" class="w-full">
|
|
921
|
+
<mat-label>Classe de tema (opcional)</mat-label>
|
|
922
|
+
<input matInput [(ngModel)]="appearance.themeClass" (ngModelChange)="onAppearanceChange()" placeholder="Ex.: theme-stepper-custom" />
|
|
923
|
+
<mat-hint>Aplica as cores apenas dentro desse seletor</mat-hint>
|
|
924
|
+
</mat-form-field>
|
|
925
|
+
<mat-form-field appearance="outline">
|
|
926
|
+
<mat-label>Conjunto de ícones</mat-label>
|
|
927
|
+
<select matNativeControl [(ngModel)]="appearance.iconsSet" (ngModelChange)="onAppearanceChange()">
|
|
928
|
+
<option [ngValue]="undefined">Material Icons (padrão)</option>
|
|
929
|
+
<option value="material-symbols-outlined">Material Symbols (Outlined)</option>
|
|
930
|
+
<option value="material-symbols-rounded">Material Symbols (Rounded)</option>
|
|
931
|
+
<option value="material-symbols-sharp">Material Symbols (Sharp)</option>
|
|
932
|
+
</select>
|
|
933
|
+
<mat-hint>Selecione se usar ícones do Material Symbols</mat-hint>
|
|
934
|
+
</mat-form-field>
|
|
935
|
+
</div>
|
|
936
|
+
<div class="icons-grid">
|
|
937
|
+
<div class="icon-item">
|
|
938
|
+
<div class="icon-head">Ícone do número</div>
|
|
939
|
+
<button mat-stroked-button type="button" (click)="pickIcon('number')">
|
|
940
|
+
<mat-icon *ngIf="icons.number; else pick" [praxisIcon]="icons.number"></mat-icon>
|
|
941
|
+
<ng-template #pick>Escolher</ng-template>
|
|
942
|
+
</button>
|
|
943
|
+
</div>
|
|
944
|
+
<div class="icon-item">
|
|
945
|
+
<div class="icon-head">Ícone concluído</div>
|
|
946
|
+
<button mat-stroked-button type="button" (click)="pickIcon('done')">
|
|
947
|
+
<mat-icon *ngIf="icons.done; else pick2" [praxisIcon]="icons.done"></mat-icon>
|
|
948
|
+
<ng-template #pick2>Escolher</ng-template>
|
|
949
|
+
</button>
|
|
950
|
+
</div>
|
|
951
|
+
<div class="icon-item">
|
|
952
|
+
<div class="icon-head">Ícone edição</div>
|
|
953
|
+
<button mat-stroked-button type="button" (click)="pickIcon('edit')">
|
|
954
|
+
<mat-icon *ngIf="icons.edit; else pick3" [praxisIcon]="icons.edit"></mat-icon>
|
|
955
|
+
<ng-template #pick3>Escolher</ng-template>
|
|
956
|
+
</button>
|
|
957
|
+
</div>
|
|
958
|
+
<div class="icon-item">
|
|
959
|
+
<div class="icon-head">Ícone de erro</div>
|
|
960
|
+
<button mat-stroked-button type="button" (click)="pickIcon('error')">
|
|
961
|
+
<mat-icon *ngIf="icons.error; else pick4" [praxisIcon]="icons.error"></mat-icon>
|
|
962
|
+
<ng-template #pick4>Escolher</ng-template>
|
|
963
|
+
</button>
|
|
964
|
+
</div>
|
|
965
|
+
</div>
|
|
966
|
+
<div class="icons-hint" *ngIf="symbolsLikelySelected.length && !appearance.iconsSet">
|
|
967
|
+
<div class="muted">
|
|
968
|
+
Dica: {{ symbolsLikelySelected.join(', ') }} são ícones do Material Symbols. Selecione um conjunto Symbols abaixo ou clique em
|
|
969
|
+
<button mat-button color="primary" type="button" (click)="setIconsSetToSymbols()">Usar Material Symbols (Outlined)</button>.
|
|
970
|
+
</div>
|
|
971
|
+
</div>
|
|
972
|
+
<div class="tokens-grid">
|
|
973
|
+
<div class="token-item" *ngFor="let key of stepperTokenKeys">
|
|
974
|
+
<mat-form-field appearance="outline" class="w-full">
|
|
975
|
+
<mat-label>{{ key }}</mat-label>
|
|
976
|
+
<input matInput [ngModel]="appearance.tokens?.[key]" (ngModelChange)="onTokenChange(key, $event)" placeholder="valor CSS ou var(--token)" />
|
|
977
|
+
</mat-form-field>
|
|
978
|
+
</div>
|
|
979
|
+
</div>
|
|
980
|
+
<div class="pdx-appearance-actions">
|
|
981
|
+
<button mat-stroked-button color="primary" type="button" (click)="applyPreset('neutral')">Preset: neutro</button>
|
|
982
|
+
<button mat-stroked-button color="primary" type="button" (click)="applyPreset('primary')">Preset: primário</button>
|
|
983
|
+
<button mat-stroked-button color="primary" type="button" (click)="applyPreset('high-contrast')">Preset: alto contraste</button>
|
|
984
|
+
<button mat-button type="button" (click)="clearTokens()">Limpar tokens</button>
|
|
985
|
+
</div>
|
|
986
|
+
<mat-card appearance="outlined" class="code-card">
|
|
987
|
+
<div class="code-head">Snippet SCSS</div>
|
|
988
|
+
<pre class="code"><code>{{ scssSnippet() }}</code></pre>
|
|
989
|
+
</mat-card>
|
|
990
|
+
</div>
|
|
991
|
+
</mat-tab>
|
|
992
|
+
</mat-tab-group>
|
|
993
|
+
</div>
|
|
994
|
+
`, isInline: true, styles: [".pdx-editor{display:grid;gap:16px}.pdx-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}.pdx-toggles{display:flex;gap:12px;flex-wrap:wrap}.tab-pad{padding:12px 4px;display:grid;gap:12px}.help{color:#000000b3;font-size:13px}.quick-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin-bottom:6px}.icons-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin:8px 0}.icon-item{display:grid;gap:6px;align-content:start}.icons-hint{margin-top:4px}.pdx-steps{display:grid;gap:8px}.pdx-steps-header{display:flex;justify-content:space-between;align-items:center}.pdx-step-list{display:grid;gap:8px}.pdx-step-item{display:grid;grid-template-columns:24px 1fr auto;gap:8px;align-items:start;padding:8px;border:1px solid rgba(0,0,0,.12);border-radius:8px;cursor:pointer}.pdx-step-item.active{border-color:color-mix(in oklab,var(--md-sys-color-primary) 40%,rgba(0,0,0,.12));box-shadow:var(--mat-elevation-transition),var(--mat-elevation-level2, 0 2px 6px rgba(0,0,0,.12))}.drag-handle{display:flex;align-items:center;color:#0000008a}.pdx-fields{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.pdx-flag-group{display:flex;gap:10px;align-items:center}.pdx-active-step{display:grid;gap:12px;padding-top:8px;border-top:1px dashed rgba(0,0,0,.12)}.pdx-active-step .hdr{display:flex;gap:12px;align-items:center;justify-content:space-between}.pdx-content-editor{display:grid;gap:16px}.section{display:grid;gap:8px}.section-title{font-weight:600;opacity:.85}.mini-card{display:block}.widget-list{display:grid;gap:8px}.widget-item{display:grid;grid-template-columns:24px 1fr auto;align-items:center;gap:8px;padding:8px;border:1px solid rgba(0,0,0,.12);border-radius:8px}.widget-item .info .name{font-weight:600}.widget-item .info .sub{font-size:12px;opacity:.7}.widget-item .actions{display:flex;gap:8px}.cta-row{display:flex;gap:8px;align-items:center}.spacer{flex:1 1 auto}.muted{color:#0009}.cta-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px;margin-bottom:8px}.cta-card{display:grid;gap:8px;padding:12px;border-radius:12px;border:1px solid var(--md-sys-color-outline-variant, rgba(0,0,0,.12));background:var(--md-sys-color-surface)}.cta-card .cta-head{display:flex;align-items:center;gap:8px}.cta-card .cta-title{font-weight:600}.cta-card .cta-desc{font-size:12px;opacity:.78}.cta-card .cta-actions{display:flex;gap:8px;align-items:center}.tokens-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;margin:8px 0}.token-item{display:contents}.pdx-appearance{margin-top:8px;display:grid;gap:8px}.pdx-appearance-actions{display:flex;gap:8px;flex-wrap:wrap}.code-card{margin-top:8px}.code-head{font-weight:600;margin-bottom:4px}.code{white-space:pre;overflow:auto}\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: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.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: i2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$1.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.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.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i7.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: i8.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: i8.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: i8.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i9.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatChipsModule }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i10.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i10.MatCardActions, selector: "mat-card-actions", inputs: ["align"], exportAs: ["matCardActions"] }, { kind: "directive", type: i10.MatCardAvatar, selector: "[mat-card-avatar], [matCardAvatar]" }, { kind: "component", type: i10.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i10.MatCardSubtitle, selector: "mat-card-subtitle, [mat-card-subtitle], [matCardSubtitle]" }, { kind: "directive", type: i10.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i11.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i11.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatDialogModule }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i12.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i12.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"] }] });
|
|
995
|
+
}
|
|
996
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisStepperConfigEditor, decorators: [{
|
|
997
|
+
type: Component,
|
|
998
|
+
args: [{ selector: 'praxis-stepper-config-editor', standalone: true, imports: [
|
|
999
|
+
CommonModule,
|
|
1000
|
+
FormsModule,
|
|
1001
|
+
MatFormFieldModule,
|
|
1002
|
+
MatInputModule,
|
|
1003
|
+
MatIconModule,
|
|
1004
|
+
PraxisIconDirective,
|
|
1005
|
+
MatButtonModule,
|
|
1006
|
+
MatSlideToggleModule,
|
|
1007
|
+
DragDropModule,
|
|
1008
|
+
MatTooltipModule,
|
|
1009
|
+
MatChipsModule,
|
|
1010
|
+
MatCardModule,
|
|
1011
|
+
MatButtonToggleModule,
|
|
1012
|
+
MatDialogModule,
|
|
1013
|
+
MatTabsModule,
|
|
1014
|
+
], template: `
|
|
1015
|
+
<div class="pdx-editor">
|
|
1016
|
+
<mat-tab-group>
|
|
1017
|
+
<mat-tab label="Geral">
|
|
1018
|
+
<div class="tab-pad">
|
|
1019
|
+
<div class="help">Configure o funcionamento e a aparência básica do passo a passo.</div>
|
|
1020
|
+
<div class="pdx-grid">
|
|
1021
|
+
<mat-form-field appearance="outline">
|
|
1022
|
+
<mat-label>Orientação</mat-label>
|
|
1023
|
+
<select matNativeControl [(ngModel)]="config.orientation" (ngModelChange)="markDirty()">
|
|
1024
|
+
<option value="horizontal">Horizontal</option>
|
|
1025
|
+
<option value="vertical">Vertical</option>
|
|
1026
|
+
</select>
|
|
1027
|
+
<mat-hint>Horizontal: cabeçalho no topo • Vertical: lista à esquerda</mat-hint>
|
|
1028
|
+
</mat-form-field>
|
|
1029
|
+
<mat-form-field appearance="outline">
|
|
1030
|
+
<mat-label>Posição dos títulos</mat-label>
|
|
1031
|
+
<select matNativeControl [(ngModel)]="config.headerPosition" (ngModelChange)="markDirty()">
|
|
1032
|
+
<option value="top">Acima</option>
|
|
1033
|
+
<option value="bottom">Abaixo</option>
|
|
1034
|
+
</select>
|
|
1035
|
+
<mat-hint>Válido na orientação horizontal</mat-hint>
|
|
1036
|
+
</mat-form-field>
|
|
1037
|
+
<mat-form-field appearance="outline">
|
|
1038
|
+
<mat-label>Posição do rótulo</mat-label>
|
|
1039
|
+
<select matNativeControl [(ngModel)]="config.labelPosition" (ngModelChange)="markDirty()" [disabled]="config.orientation !== 'horizontal'">
|
|
1040
|
+
<option value="end">Ao lado</option>
|
|
1041
|
+
<option value="bottom">Abaixo</option>
|
|
1042
|
+
</select>
|
|
1043
|
+
<mat-hint>Como o texto aparece no cabeçalho</mat-hint>
|
|
1044
|
+
</mat-form-field>
|
|
1045
|
+
<mat-form-field appearance="outline">
|
|
1046
|
+
<mat-label>Cor</mat-label>
|
|
1047
|
+
<select matNativeControl [(ngModel)]="config.color" (ngModelChange)="markDirty()">
|
|
1048
|
+
<option [ngValue]="undefined">Padrão</option>
|
|
1049
|
+
<option value="primary">Primária</option>
|
|
1050
|
+
<option value="accent">Acento</option>
|
|
1051
|
+
<option value="warn">Alerta</option>
|
|
1052
|
+
</select>
|
|
1053
|
+
<mat-hint>Aplicável a temas Material 2 (M2)</mat-hint>
|
|
1054
|
+
</mat-form-field>
|
|
1055
|
+
<mat-form-field appearance="outline">
|
|
1056
|
+
<mat-label>Duração da animação</mat-label>
|
|
1057
|
+
<input matInput [(ngModel)]="config.animationDuration" (ngModelChange)="markDirty()" placeholder="Ex.: 300ms" />
|
|
1058
|
+
<mat-hint>Tempo da transição entre etapas</mat-hint>
|
|
1059
|
+
</mat-form-field>
|
|
1060
|
+
<mat-form-field appearance="outline">
|
|
1061
|
+
<mat-label>Etapa selecionada</mat-label>
|
|
1062
|
+
<input matInput type="number" [(ngModel)]="config.selectedIndex" (ngModelChange)="markDirty()" />
|
|
1063
|
+
<mat-hint>Índice inicia em 0 (primeira etapa)</mat-hint>
|
|
1064
|
+
</mat-form-field>
|
|
1065
|
+
<mat-form-field appearance="outline">
|
|
1066
|
+
<mat-label>Densidade</mat-label>
|
|
1067
|
+
<select matNativeControl [(ngModel)]="config.density" (ngModelChange)="markDirty()">
|
|
1068
|
+
<option [ngValue]="undefined">Padrão</option>
|
|
1069
|
+
<option value="comfortable">Confortável</option>
|
|
1070
|
+
<option value="compact">Compacta</option>
|
|
1071
|
+
</select>
|
|
1072
|
+
<mat-hint>Controla espaçamentos e alturas</mat-hint>
|
|
1073
|
+
</mat-form-field>
|
|
1074
|
+
<mat-form-field appearance="outline" class="w-full">
|
|
1075
|
+
<mat-label>Classe do stepper (opcional)</mat-label>
|
|
1076
|
+
<input matInput [(ngModel)]="config.stepperClass" (ngModelChange)="markDirty()" placeholder="Ex.: meu-stepper" />
|
|
1077
|
+
<mat-hint>Permite estilizar com CSS escopado</mat-hint>
|
|
1078
|
+
</mat-form-field>
|
|
1079
|
+
<mat-form-field appearance="outline" class="w-full">
|
|
1080
|
+
<mat-label>Classe do cabeçalho (opcional)</mat-label>
|
|
1081
|
+
<input matInput [(ngModel)]="config.headerClass" (ngModelChange)="markDirty()" placeholder="Ex.: stepper-header" />
|
|
1082
|
+
<mat-hint>Use para ajustes finos de aparência</mat-hint>
|
|
1083
|
+
</mat-form-field>
|
|
1084
|
+
<mat-form-field appearance="outline" class="w-full">
|
|
1085
|
+
<mat-label>Classe do conteúdo (opcional)</mat-label>
|
|
1086
|
+
<input matInput [(ngModel)]="config.contentClass" (ngModelChange)="markDirty()" placeholder="Ex.: stepper-content" />
|
|
1087
|
+
<mat-hint>Aplica uma classe à área de conteúdo</mat-hint>
|
|
1088
|
+
</mat-form-field>
|
|
1089
|
+
</div>
|
|
1090
|
+
<div class="pdx-toggles">
|
|
1091
|
+
<mat-slide-toggle [(ngModel)]="config.linear" (ngModelChange)="markDirty()">Respeitar ordem (linear)</mat-slide-toggle>
|
|
1092
|
+
<mat-slide-toggle [(ngModel)]="config.disableRipple" (ngModelChange)="markDirty()">Desativar efeitos de clique</mat-slide-toggle>
|
|
1093
|
+
</div>
|
|
1094
|
+
</div>
|
|
1095
|
+
</mat-tab>
|
|
1096
|
+
|
|
1097
|
+
<mat-tab label="Etapas">
|
|
1098
|
+
<div class="tab-pad">
|
|
1099
|
+
<div class="help">Gerencie as etapas: nomes, mensagens e conteúdo. Arraste para reordenar.</div>
|
|
1100
|
+
<div class="pdx-steps">
|
|
1101
|
+
<div class="pdx-steps-header">
|
|
1102
|
+
<div class="title">Etapas ({{ config.steps.length }})</div>
|
|
1103
|
+
<button mat-button (click)="addStep()"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar etapa</button>
|
|
1104
|
+
</div>
|
|
1105
|
+
<div cdkDropList (cdkDropListDropped)="drop($event)" class="pdx-step-list">
|
|
1106
|
+
<div class="pdx-step-item" *ngFor="let s of config.steps; let i = index" cdkDrag [class.active]="activeIndex === i" (click)="setActive(i)">
|
|
1107
|
+
<div class="drag-handle" cdkDragHandle><mat-icon [praxisIcon]="'drag_indicator'"></mat-icon></div>
|
|
1108
|
+
<div class="pdx-fields">
|
|
1109
|
+
<mat-form-field appearance="outline" style="min-width: 180px;">
|
|
1110
|
+
<mat-label>Título da etapa</mat-label>
|
|
1111
|
+
<input matInput [(ngModel)]="s.label" (ngModelChange)="markDirty()" />
|
|
1112
|
+
<mat-hint>Como aparecerá no cabeçalho</mat-hint>
|
|
1113
|
+
</mat-form-field>
|
|
1114
|
+
<mat-form-field appearance="outline">
|
|
1115
|
+
<mat-label>Identificador (opcional)</mat-label>
|
|
1116
|
+
<input matInput [(ngModel)]="s.id" (ngModelChange)="markDirty()" />
|
|
1117
|
+
<mat-hint>Útil para automações</mat-hint>
|
|
1118
|
+
</mat-form-field>
|
|
1119
|
+
<mat-form-field appearance="outline">
|
|
1120
|
+
<mat-label>Texto alternativo (acessibilidade)</mat-label>
|
|
1121
|
+
<input matInput [(ngModel)]="s.ariaLabel" (ngModelChange)="markDirty()" />
|
|
1122
|
+
<mat-hint>Lido por leitores de tela</mat-hint>
|
|
1123
|
+
</mat-form-field>
|
|
1124
|
+
<mat-form-field appearance="outline">
|
|
1125
|
+
<mat-label>ID que descreve (opcional)</mat-label>
|
|
1126
|
+
<input matInput [(ngModel)]="s.ariaLabelledby" (ngModelChange)="markDirty()" />
|
|
1127
|
+
<mat-hint>ID de um elemento que descreve a etapa</mat-hint>
|
|
1128
|
+
</mat-form-field>
|
|
1129
|
+
<mat-form-field appearance="outline">
|
|
1130
|
+
<mat-label>Ícone/estado do marcador</mat-label>
|
|
1131
|
+
<input matInput [(ngModel)]="s.state" (ngModelChange)="markDirty()" placeholder="Ex.: number, done, edit" />
|
|
1132
|
+
<mat-hint>Controla o ícone do passo (quando aplicável)</mat-hint>
|
|
1133
|
+
</mat-form-field>
|
|
1134
|
+
<mat-form-field appearance="outline">
|
|
1135
|
+
<mat-label>Mensagem de erro</mat-label>
|
|
1136
|
+
<input matInput [(ngModel)]="s.errorMessage" (ngModelChange)="markDirty()" />
|
|
1137
|
+
<mat-hint>Mostrada quando há erro na etapa</mat-hint>
|
|
1138
|
+
</mat-form-field>
|
|
1139
|
+
<div class="pdx-flag-group">
|
|
1140
|
+
<mat-slide-toggle [(ngModel)]="s.optional" (ngModelChange)="markDirty()">Etapa opcional</mat-slide-toggle>
|
|
1141
|
+
<mat-slide-toggle [(ngModel)]="s.editable" (ngModelChange)="markDirty()">Permitir voltar e editar</mat-slide-toggle>
|
|
1142
|
+
<mat-slide-toggle [(ngModel)]="s.completed" (ngModelChange)="markDirty()">Marcar como concluída</mat-slide-toggle>
|
|
1143
|
+
<mat-slide-toggle [(ngModel)]="s.hasError" (ngModelChange)="markDirty()">Marcar como com erro</mat-slide-toggle>
|
|
1144
|
+
</div>
|
|
1145
|
+
</div>
|
|
1146
|
+
<div class="pdx-actions">
|
|
1147
|
+
<button mat-icon-button color="warn" (click)="removeStep(i)" matTooltip="Remover">
|
|
1148
|
+
<mat-icon [praxisIcon]="'delete'"></mat-icon>
|
|
1149
|
+
</button>
|
|
1150
|
+
</div>
|
|
1151
|
+
</div>
|
|
1152
|
+
</div>
|
|
1153
|
+
</div>
|
|
1154
|
+
|
|
1155
|
+
<div class="pdx-active-step" *ngIf="activeStep as step; else noSteps">
|
|
1156
|
+
<div class="hdr">
|
|
1157
|
+
<div class="title">Editando: {{ step.label }}</div>
|
|
1158
|
+
<mat-button-toggle-group [(ngModel)]="activeIndex" (ngModelChange)="onActiveIndexChange()" [hideSingleSelectionIndicator]="true">
|
|
1159
|
+
<mat-button-toggle *ngFor="let s of config.steps; let i = index" [value]="i">{{ i + 1 }}</mat-button-toggle>
|
|
1160
|
+
</mat-button-toggle-group>
|
|
1161
|
+
</div>
|
|
1162
|
+
|
|
1163
|
+
<div class="pdx-content-editor">
|
|
1164
|
+
<div class="section">
|
|
1165
|
+
<div class="section-title">Formulário principal</div>
|
|
1166
|
+
<div class="section-body">
|
|
1167
|
+
<ng-container *ngIf="step.form; else addFormBtn">
|
|
1168
|
+
<mat-card appearance="outlined" class="mini-card">
|
|
1169
|
+
<mat-card-header>
|
|
1170
|
+
<mat-icon mat-card-avatar [praxisIcon]="'dynamic_form'"></mat-icon>
|
|
1171
|
+
<mat-card-title>Formulário principal</mat-card-title>
|
|
1172
|
+
<mat-card-subtitle>{{ step.form.formId || step.id || '—' }}</mat-card-subtitle>
|
|
1173
|
+
</mat-card-header>
|
|
1174
|
+
<mat-card-actions align="end">
|
|
1175
|
+
<button mat-button (click)="editMainForm()"><mat-icon [praxisIcon]="'tune'"></mat-icon> Editar</button>
|
|
1176
|
+
<button mat-button color="warn" (click)="removeMainForm()"><mat-icon [praxisIcon]="'delete'"></mat-icon> Remover</button>
|
|
1177
|
+
</mat-card-actions>
|
|
1178
|
+
</mat-card>
|
|
1179
|
+
</ng-container>
|
|
1180
|
+
<ng-template #addFormBtn>
|
|
1181
|
+
<button mat-stroked-button color="primary" (click)="addMainForm()"><mat-icon [praxisIcon]="'add'"></mat-icon> Adicionar formulário</button>
|
|
1182
|
+
</ng-template>
|
|
1183
|
+
</div>
|
|
1184
|
+
</div>
|
|
1185
|
+
|
|
1186
|
+
<div class="section">
|
|
1187
|
+
<div class="section-title">Conteúdos e componentes</div>
|
|
1188
|
+
<div class="section-body">
|
|
1189
|
+
<div class="cta-grid">
|
|
1190
|
+
<div class="cta-card mat-elevation-z2">
|
|
1191
|
+
<div class="cta-head">
|
|
1192
|
+
<mat-icon [praxisIcon]="'dynamic_form'"></mat-icon>
|
|
1193
|
+
<div class="cta-title">Formulário dinâmico</div>
|
|
1194
|
+
</div>
|
|
1195
|
+
<div class="cta-desc">Crie campos e validações personalizadas nesta etapa.</div>
|
|
1196
|
+
<div class="cta-actions">
|
|
1197
|
+
<button mat-flat-button color="primary" (click)="addMainForm()">
|
|
1198
|
+
<mat-icon [praxisIcon]="'add'"></mat-icon>
|
|
1199
|
+
Inserir formulário
|
|
1200
|
+
</button>
|
|
1201
|
+
</div>
|
|
1202
|
+
</div>
|
|
1203
|
+
<div class="cta-card mat-elevation-z2">
|
|
1204
|
+
<div class="cta-head">
|
|
1205
|
+
<mat-icon [praxisIcon]="'account_tree'"></mat-icon>
|
|
1206
|
+
<div class="cta-title">Lista em árvore</div>
|
|
1207
|
+
</div>
|
|
1208
|
+
<div class="cta-desc">Exiba dados hierárquicos com seleção simples.</div>
|
|
1209
|
+
<div class="cta-actions">
|
|
1210
|
+
<button mat-stroked-button (click)="addTreeList()">
|
|
1211
|
+
<mat-icon [praxisIcon]="'add'"></mat-icon>
|
|
1212
|
+
Inserir árvore
|
|
1213
|
+
</button>
|
|
1214
|
+
</div>
|
|
1215
|
+
</div>
|
|
1216
|
+
<div class="cta-card mat-elevation-z2">
|
|
1217
|
+
<div class="cta-head">
|
|
1218
|
+
<mat-icon [praxisIcon]="'swap_horiz'"></mat-icon>
|
|
1219
|
+
<div class="cta-title">Transferência de itens</div>
|
|
1220
|
+
</div>
|
|
1221
|
+
<div class="cta-desc">Mover itens entre listas (ideal para múltipla seleção).</div>
|
|
1222
|
+
<div class="cta-actions">
|
|
1223
|
+
<button mat-stroked-button (click)="addTransferListQuick()">
|
|
1224
|
+
<mat-icon [praxisIcon]="'add'"></mat-icon>
|
|
1225
|
+
Inserir transferência
|
|
1226
|
+
</button>
|
|
1227
|
+
</div>
|
|
1228
|
+
</div>
|
|
1229
|
+
</div>
|
|
1230
|
+
<div cdkDropList (cdkDropListDropped)="dropWidget($event)" class="widget-list">
|
|
1231
|
+
<div class="widget-item" *ngFor="let w of (step.widgets || []); let wi = index" cdkDrag>
|
|
1232
|
+
<div class="drag-handle" cdkDragHandle><mat-icon [praxisIcon]="'drag_indicator'"></mat-icon></div>
|
|
1233
|
+
<div class="info">
|
|
1234
|
+
<div class="name">{{ displayWidgetName(w) }}</div>
|
|
1235
|
+
<div class="sub">{{ w.id }}</div>
|
|
1236
|
+
</div>
|
|
1237
|
+
<div class="actions">
|
|
1238
|
+
<button mat-button (click)="editWidget(w, wi)"><mat-icon [praxisIcon]="'tune'"></mat-icon> Editar</button>
|
|
1239
|
+
<button mat-button color="warn" (click)="removeWidget(wi)"><mat-icon [praxisIcon]="'delete'"></mat-icon> Remover</button>
|
|
1240
|
+
</div>
|
|
1241
|
+
</div>
|
|
1242
|
+
<div class="empty" *ngIf="!step.widgets || !step.widgets.length">Nenhum componente adicionado</div>
|
|
1243
|
+
</div>
|
|
1244
|
+
</div>
|
|
1245
|
+
</div>
|
|
1246
|
+
</div>
|
|
1247
|
+
</div>
|
|
1248
|
+
<ng-template #noSteps>
|
|
1249
|
+
<div class="muted">Nenhuma etapa definida.</div>
|
|
1250
|
+
</ng-template>
|
|
1251
|
+
</div>
|
|
1252
|
+
</mat-tab>
|
|
1253
|
+
|
|
1254
|
+
<mat-tab label="Navegação">
|
|
1255
|
+
<div class="tab-pad">
|
|
1256
|
+
<div class="help">Personalize os botões de avançar e voltar exibidos em cada etapa.</div>
|
|
1257
|
+
<div class="pdx-grid">
|
|
1258
|
+
<mat-form-field appearance="outline">
|
|
1259
|
+
<mat-label>Estilo dos botões</mat-label>
|
|
1260
|
+
<select matNativeControl [(ngModel)]="navigationCfg.variant" (ngModelChange)="markDirty()">
|
|
1261
|
+
<option [ngValue]="undefined">Padrão (elevado)</option>
|
|
1262
|
+
<option value="basic">Texto</option>
|
|
1263
|
+
<option value="flat">Plano</option>
|
|
1264
|
+
<option value="stroked">Contornado</option>
|
|
1265
|
+
<option value="raised">Elevado</option>
|
|
1266
|
+
</select>
|
|
1267
|
+
</mat-form-field>
|
|
1268
|
+
<mat-form-field appearance="outline">
|
|
1269
|
+
<mat-label>Cor</mat-label>
|
|
1270
|
+
<select matNativeControl [(ngModel)]="navigationCfg.color" (ngModelChange)="markDirty()">
|
|
1271
|
+
<option [ngValue]="undefined">Padrão</option>
|
|
1272
|
+
<option value="primary">Primária</option>
|
|
1273
|
+
<option value="accent">Acento</option>
|
|
1274
|
+
<option value="warn">Alerta</option>
|
|
1275
|
+
</select>
|
|
1276
|
+
</mat-form-field>
|
|
1277
|
+
<mat-form-field appearance="outline">
|
|
1278
|
+
<mat-label>Alinhamento</mat-label>
|
|
1279
|
+
<select matNativeControl [(ngModel)]="navigationCfg.align" (ngModelChange)="markDirty()">
|
|
1280
|
+
<option [ngValue]="undefined">Direita</option>
|
|
1281
|
+
<option value="start">Esquerda</option>
|
|
1282
|
+
<option value="center">Centro</option>
|
|
1283
|
+
<option value="space-between">Espaçado</option>
|
|
1284
|
+
<option value="end">Direita</option>
|
|
1285
|
+
</select>
|
|
1286
|
+
</mat-form-field>
|
|
1287
|
+
</div>
|
|
1288
|
+
|
|
1289
|
+
<div class="pdx-toggles">
|
|
1290
|
+
<mat-slide-toggle [(ngModel)]="navigationCfg.visible" (ngModelChange)="markDirty()">Mostrar navegação padrão</mat-slide-toggle>
|
|
1291
|
+
</div>
|
|
1292
|
+
|
|
1293
|
+
<div class="pdx-grid">
|
|
1294
|
+
<mat-form-field appearance="outline">
|
|
1295
|
+
<mat-label>Texto do botão Voltar</mat-label>
|
|
1296
|
+
<input matInput [(ngModel)]="navigationCfg.prevLabel" (ngModelChange)="markDirty()" placeholder="Ex.: Voltar" />
|
|
1297
|
+
</mat-form-field>
|
|
1298
|
+
<mat-form-field appearance="outline">
|
|
1299
|
+
<mat-label>Texto do botão Próximo</mat-label>
|
|
1300
|
+
<input matInput [(ngModel)]="navigationCfg.nextLabel" (ngModelChange)="markDirty()" placeholder="Ex.: Próximo" />
|
|
1301
|
+
</mat-form-field>
|
|
1302
|
+
</div>
|
|
1303
|
+
|
|
1304
|
+
<div class="icons-grid">
|
|
1305
|
+
<div class="icon-item">
|
|
1306
|
+
<div class="icon-head">Ícone de Voltar</div>
|
|
1307
|
+
<button mat-stroked-button type="button" (click)="pickNavIcon('prevIcon')">
|
|
1308
|
+
<mat-icon *ngIf="navigationCfg.prevIcon" [praxisIcon]="navigationCfg.prevIcon"></mat-icon>
|
|
1309
|
+
<ng-container *ngIf="!navigationCfg.prevIcon">Escolher</ng-container>
|
|
1310
|
+
</button>
|
|
1311
|
+
</div>
|
|
1312
|
+
<div class="icon-item">
|
|
1313
|
+
<div class="icon-head">Ícone de Próximo</div>
|
|
1314
|
+
<button mat-stroked-button type="button" (click)="pickNavIcon('nextIcon')">
|
|
1315
|
+
<mat-icon *ngIf="navigationCfg.nextIcon" [praxisIcon]="navigationCfg.nextIcon"></mat-icon>
|
|
1316
|
+
<ng-container *ngIf="!navigationCfg.nextIcon">Escolher</ng-container>
|
|
1317
|
+
</button>
|
|
1318
|
+
</div>
|
|
1319
|
+
</div>
|
|
1320
|
+
</div>
|
|
1321
|
+
</mat-tab>
|
|
1322
|
+
|
|
1323
|
+
<mat-tab label="Aparência">
|
|
1324
|
+
<div class="tab-pad">
|
|
1325
|
+
<div class="help">Personalize cores, tipografia e ícones. Para ajustes comuns, use os campos abaixo; para maior controle, edite os tokens. Copie o SCSS e cole no arquivo de tema.</div>
|
|
1326
|
+
<div class="quick-grid">
|
|
1327
|
+
<mat-form-field appearance="outline">
|
|
1328
|
+
<mat-label>Altura do cabeçalho (px)</mat-label>
|
|
1329
|
+
<input matInput type="number" [ngModel]="headerHeightPx" (ngModelChange)="setHeaderHeightPx($event)" placeholder="Ex.: 48" />
|
|
1330
|
+
<mat-hint>Altura do item do cabeçalho</mat-hint>
|
|
1331
|
+
</mat-form-field>
|
|
1332
|
+
<mat-form-field appearance="outline">
|
|
1333
|
+
<mat-label>Tamanho do texto (px)</mat-label>
|
|
1334
|
+
<input matInput type="number" [ngModel]="headerLabelTextSizePx" (ngModelChange)="setHeaderLabelTextSizePx($event)" placeholder="Ex.: 14" />
|
|
1335
|
+
<mat-hint>Tamanho do título da etapa</mat-hint>
|
|
1336
|
+
</mat-form-field>
|
|
1337
|
+
<mat-form-field appearance="outline">
|
|
1338
|
+
<mat-label>Peso do texto</mat-label>
|
|
1339
|
+
<select matNativeControl [ngModel]="headerLabelTextWeight" (ngModelChange)="setHeaderLabelTextWeight($event)">
|
|
1340
|
+
<option [ngValue]="undefined">Padrão</option>
|
|
1341
|
+
<option value="400">Normal</option>
|
|
1342
|
+
<option value="500">Médio</option>
|
|
1343
|
+
<option value="600">Seminegrito</option>
|
|
1344
|
+
<option value="700">Negrito</option>
|
|
1345
|
+
</select>
|
|
1346
|
+
</mat-form-field>
|
|
1347
|
+
</div>
|
|
1348
|
+
<div class="pdx-grid">
|
|
1349
|
+
<mat-form-field appearance="outline" class="w-full">
|
|
1350
|
+
<mat-label>Classe de tema (opcional)</mat-label>
|
|
1351
|
+
<input matInput [(ngModel)]="appearance.themeClass" (ngModelChange)="onAppearanceChange()" placeholder="Ex.: theme-stepper-custom" />
|
|
1352
|
+
<mat-hint>Aplica as cores apenas dentro desse seletor</mat-hint>
|
|
1353
|
+
</mat-form-field>
|
|
1354
|
+
<mat-form-field appearance="outline">
|
|
1355
|
+
<mat-label>Conjunto de ícones</mat-label>
|
|
1356
|
+
<select matNativeControl [(ngModel)]="appearance.iconsSet" (ngModelChange)="onAppearanceChange()">
|
|
1357
|
+
<option [ngValue]="undefined">Material Icons (padrão)</option>
|
|
1358
|
+
<option value="material-symbols-outlined">Material Symbols (Outlined)</option>
|
|
1359
|
+
<option value="material-symbols-rounded">Material Symbols (Rounded)</option>
|
|
1360
|
+
<option value="material-symbols-sharp">Material Symbols (Sharp)</option>
|
|
1361
|
+
</select>
|
|
1362
|
+
<mat-hint>Selecione se usar ícones do Material Symbols</mat-hint>
|
|
1363
|
+
</mat-form-field>
|
|
1364
|
+
</div>
|
|
1365
|
+
<div class="icons-grid">
|
|
1366
|
+
<div class="icon-item">
|
|
1367
|
+
<div class="icon-head">Ícone do número</div>
|
|
1368
|
+
<button mat-stroked-button type="button" (click)="pickIcon('number')">
|
|
1369
|
+
<mat-icon *ngIf="icons.number; else pick" [praxisIcon]="icons.number"></mat-icon>
|
|
1370
|
+
<ng-template #pick>Escolher</ng-template>
|
|
1371
|
+
</button>
|
|
1372
|
+
</div>
|
|
1373
|
+
<div class="icon-item">
|
|
1374
|
+
<div class="icon-head">Ícone concluído</div>
|
|
1375
|
+
<button mat-stroked-button type="button" (click)="pickIcon('done')">
|
|
1376
|
+
<mat-icon *ngIf="icons.done; else pick2" [praxisIcon]="icons.done"></mat-icon>
|
|
1377
|
+
<ng-template #pick2>Escolher</ng-template>
|
|
1378
|
+
</button>
|
|
1379
|
+
</div>
|
|
1380
|
+
<div class="icon-item">
|
|
1381
|
+
<div class="icon-head">Ícone edição</div>
|
|
1382
|
+
<button mat-stroked-button type="button" (click)="pickIcon('edit')">
|
|
1383
|
+
<mat-icon *ngIf="icons.edit; else pick3" [praxisIcon]="icons.edit"></mat-icon>
|
|
1384
|
+
<ng-template #pick3>Escolher</ng-template>
|
|
1385
|
+
</button>
|
|
1386
|
+
</div>
|
|
1387
|
+
<div class="icon-item">
|
|
1388
|
+
<div class="icon-head">Ícone de erro</div>
|
|
1389
|
+
<button mat-stroked-button type="button" (click)="pickIcon('error')">
|
|
1390
|
+
<mat-icon *ngIf="icons.error; else pick4" [praxisIcon]="icons.error"></mat-icon>
|
|
1391
|
+
<ng-template #pick4>Escolher</ng-template>
|
|
1392
|
+
</button>
|
|
1393
|
+
</div>
|
|
1394
|
+
</div>
|
|
1395
|
+
<div class="icons-hint" *ngIf="symbolsLikelySelected.length && !appearance.iconsSet">
|
|
1396
|
+
<div class="muted">
|
|
1397
|
+
Dica: {{ symbolsLikelySelected.join(', ') }} são ícones do Material Symbols. Selecione um conjunto Symbols abaixo ou clique em
|
|
1398
|
+
<button mat-button color="primary" type="button" (click)="setIconsSetToSymbols()">Usar Material Symbols (Outlined)</button>.
|
|
1399
|
+
</div>
|
|
1400
|
+
</div>
|
|
1401
|
+
<div class="tokens-grid">
|
|
1402
|
+
<div class="token-item" *ngFor="let key of stepperTokenKeys">
|
|
1403
|
+
<mat-form-field appearance="outline" class="w-full">
|
|
1404
|
+
<mat-label>{{ key }}</mat-label>
|
|
1405
|
+
<input matInput [ngModel]="appearance.tokens?.[key]" (ngModelChange)="onTokenChange(key, $event)" placeholder="valor CSS ou var(--token)" />
|
|
1406
|
+
</mat-form-field>
|
|
1407
|
+
</div>
|
|
1408
|
+
</div>
|
|
1409
|
+
<div class="pdx-appearance-actions">
|
|
1410
|
+
<button mat-stroked-button color="primary" type="button" (click)="applyPreset('neutral')">Preset: neutro</button>
|
|
1411
|
+
<button mat-stroked-button color="primary" type="button" (click)="applyPreset('primary')">Preset: primário</button>
|
|
1412
|
+
<button mat-stroked-button color="primary" type="button" (click)="applyPreset('high-contrast')">Preset: alto contraste</button>
|
|
1413
|
+
<button mat-button type="button" (click)="clearTokens()">Limpar tokens</button>
|
|
1414
|
+
</div>
|
|
1415
|
+
<mat-card appearance="outlined" class="code-card">
|
|
1416
|
+
<div class="code-head">Snippet SCSS</div>
|
|
1417
|
+
<pre class="code"><code>{{ scssSnippet() }}</code></pre>
|
|
1418
|
+
</mat-card>
|
|
1419
|
+
</div>
|
|
1420
|
+
</mat-tab>
|
|
1421
|
+
</mat-tab-group>
|
|
1422
|
+
</div>
|
|
1423
|
+
`, styles: [".pdx-editor{display:grid;gap:16px}.pdx-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}.pdx-toggles{display:flex;gap:12px;flex-wrap:wrap}.tab-pad{padding:12px 4px;display:grid;gap:12px}.help{color:#000000b3;font-size:13px}.quick-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin-bottom:6px}.icons-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin:8px 0}.icon-item{display:grid;gap:6px;align-content:start}.icons-hint{margin-top:4px}.pdx-steps{display:grid;gap:8px}.pdx-steps-header{display:flex;justify-content:space-between;align-items:center}.pdx-step-list{display:grid;gap:8px}.pdx-step-item{display:grid;grid-template-columns:24px 1fr auto;gap:8px;align-items:start;padding:8px;border:1px solid rgba(0,0,0,.12);border-radius:8px;cursor:pointer}.pdx-step-item.active{border-color:color-mix(in oklab,var(--md-sys-color-primary) 40%,rgba(0,0,0,.12));box-shadow:var(--mat-elevation-transition),var(--mat-elevation-level2, 0 2px 6px rgba(0,0,0,.12))}.drag-handle{display:flex;align-items:center;color:#0000008a}.pdx-fields{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}.pdx-flag-group{display:flex;gap:10px;align-items:center}.pdx-active-step{display:grid;gap:12px;padding-top:8px;border-top:1px dashed rgba(0,0,0,.12)}.pdx-active-step .hdr{display:flex;gap:12px;align-items:center;justify-content:space-between}.pdx-content-editor{display:grid;gap:16px}.section{display:grid;gap:8px}.section-title{font-weight:600;opacity:.85}.mini-card{display:block}.widget-list{display:grid;gap:8px}.widget-item{display:grid;grid-template-columns:24px 1fr auto;align-items:center;gap:8px;padding:8px;border:1px solid rgba(0,0,0,.12);border-radius:8px}.widget-item .info .name{font-weight:600}.widget-item .info .sub{font-size:12px;opacity:.7}.widget-item .actions{display:flex;gap:8px}.cta-row{display:flex;gap:8px;align-items:center}.spacer{flex:1 1 auto}.muted{color:#0009}.cta-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px;margin-bottom:8px}.cta-card{display:grid;gap:8px;padding:12px;border-radius:12px;border:1px solid var(--md-sys-color-outline-variant, rgba(0,0,0,.12));background:var(--md-sys-color-surface)}.cta-card .cta-head{display:flex;align-items:center;gap:8px}.cta-card .cta-title{font-weight:600}.cta-card .cta-desc{font-size:12px;opacity:.78}.cta-card .cta-actions{display:flex;gap:8px;align-items:center}.tokens-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;margin:8px 0}.token-item{display:contents}.pdx-appearance{margin-top:8px;display:grid;gap:8px}.pdx-appearance-actions{display:flex;gap:8px;flex-wrap:wrap}.code-card{margin-top:8px}.code-head{font-weight:600;margin-bottom:4px}.code{white-space:pre;overflow:auto}\n"] }]
|
|
1424
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1425
|
+
type: Inject,
|
|
1426
|
+
args: [SETTINGS_PANEL_DATA]
|
|
1427
|
+
}] }] });
|
|
1428
|
+
|
|
1429
|
+
class PraxisStepper {
|
|
1430
|
+
// Templates projetados
|
|
1431
|
+
stepLabelTpl;
|
|
1432
|
+
// Dados e estado
|
|
1433
|
+
_config = signal(null, ...(ngDevMode ? [{ debugName: "_config" }] : []));
|
|
1434
|
+
_selectedIndex = signal(0, ...(ngDevMode ? [{ debugName: "_selectedIndex" }] : []));
|
|
1435
|
+
_formGroups = new Map();
|
|
1436
|
+
_formValidity = new Map();
|
|
1437
|
+
// CTA models for empty state
|
|
1438
|
+
primaryCta = { label: 'Configurar Stepper', icon: 'tune', color: 'primary', action: () => this.openEditor() };
|
|
1439
|
+
secondaryCtas = [{ label: 'Adicionar etapa inicial', icon: 'add', action: () => this.addFirstStep() }];
|
|
1440
|
+
// API de entrada
|
|
1441
|
+
set config(c) {
|
|
1442
|
+
let next = null;
|
|
1443
|
+
if (typeof c === 'string') {
|
|
1444
|
+
try {
|
|
1445
|
+
next = JSON.parse(c);
|
|
1446
|
+
}
|
|
1447
|
+
catch {
|
|
1448
|
+
next = null;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
else {
|
|
1452
|
+
next = c;
|
|
1453
|
+
}
|
|
1454
|
+
this._config.set(next);
|
|
1455
|
+
if (next?.selectedIndex != null)
|
|
1456
|
+
this._selectedIndex.set(next.selectedIndex);
|
|
1457
|
+
}
|
|
1458
|
+
set selectedIndexInput(i) {
|
|
1459
|
+
if (i != null)
|
|
1460
|
+
this._selectedIndex.set(i);
|
|
1461
|
+
}
|
|
1462
|
+
// Suporte a two-way binding [(selectedIndex)] como no Material
|
|
1463
|
+
set selectedIndex(i) {
|
|
1464
|
+
if (i != null)
|
|
1465
|
+
this._selectedIndex.set(i);
|
|
1466
|
+
}
|
|
1467
|
+
disableRippleInput = false;
|
|
1468
|
+
editModeEnabled = false;
|
|
1469
|
+
// Paridade com API do Material
|
|
1470
|
+
labelPosition;
|
|
1471
|
+
color;
|
|
1472
|
+
/**
|
|
1473
|
+
* Validação remota opcional por etapa. Retorne ok=false com fieldErrors/formErrors para bloquear avanço.
|
|
1474
|
+
*/
|
|
1475
|
+
serverValidate;
|
|
1476
|
+
// Emissões
|
|
1477
|
+
selectedIndexChange = new EventEmitter();
|
|
1478
|
+
widgetEvent = new EventEmitter();
|
|
1479
|
+
stepperContext;
|
|
1480
|
+
settings = inject(SettingsPanelService);
|
|
1481
|
+
animationDone = new EventEmitter();
|
|
1482
|
+
// Computed getters
|
|
1483
|
+
steps = computed(() => this._config()?.steps || [], ...(ngDevMode ? [{ debugName: "steps" }] : []));
|
|
1484
|
+
orientation = computed(() => this._config()?.orientation || 'horizontal', ...(ngDevMode ? [{ debugName: "orientation" }] : []));
|
|
1485
|
+
headerPosition = computed(() => this._config()?.headerPosition || 'top', ...(ngDevMode ? [{ debugName: "headerPosition" }] : []));
|
|
1486
|
+
// labelPosition: default 'end' no Material
|
|
1487
|
+
labelPos = computed(() => this._config()?.labelPosition ?? this.labelPosition ?? 'end', ...(ngDevMode ? [{ debugName: "labelPos" }] : []));
|
|
1488
|
+
linear = computed(() => !!this._config()?.linear, ...(ngDevMode ? [{ debugName: "linear" }] : []));
|
|
1489
|
+
disableRipple = computed(() => this._config()?.disableRipple ?? this.disableRippleInput, ...(ngDevMode ? [{ debugName: "disableRipple" }] : []));
|
|
1490
|
+
animationDuration = computed(() => this._config()?.animationDuration || '300ms', ...(ngDevMode ? [{ debugName: "animationDuration" }] : []));
|
|
1491
|
+
stepperColor = computed(() => this._config()?.color ?? this.color, ...(ngDevMode ? [{ debugName: "stepperColor" }] : []));
|
|
1492
|
+
icons = computed(() => this._config()?.appearance?.icons, ...(ngDevMode ? [{ debugName: "icons" }] : []));
|
|
1493
|
+
iconsSet = computed(() => this._config()?.appearance?.iconsSet, ...(ngDevMode ? [{ debugName: "iconsSet" }] : []));
|
|
1494
|
+
// Navigation helpers
|
|
1495
|
+
nav = computed(() => this._config()?.navigation || {}, ...(ngDevMode ? [{ debugName: "nav" }] : []));
|
|
1496
|
+
navVisible = () => this.nav()?.visible !== false; // default true
|
|
1497
|
+
navVariant = () => (this.nav()?.variant || 'raised');
|
|
1498
|
+
navColor = () => (this.nav()?.color || 'primary');
|
|
1499
|
+
navPrevLabel = () => this.nav()?.prevLabel || 'Voltar';
|
|
1500
|
+
navNextLabel = () => this.nav()?.nextLabel || 'Próximo';
|
|
1501
|
+
navPrevIcon = () => this.nav()?.prevIcon;
|
|
1502
|
+
navNextIcon = () => this.nav()?.nextIcon;
|
|
1503
|
+
navAlignClass = () => {
|
|
1504
|
+
const a = this.nav()?.align || 'end';
|
|
1505
|
+
switch (a) {
|
|
1506
|
+
case 'start': return 'nav-align-start';
|
|
1507
|
+
case 'center': return 'nav-align-center';
|
|
1508
|
+
case 'space-between': return 'nav-align-space-between';
|
|
1509
|
+
case 'end':
|
|
1510
|
+
default: return 'nav-align-end';
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
isNextDisabled(i) {
|
|
1514
|
+
return this.linear() && this._formValidity.get(i) === false;
|
|
1515
|
+
}
|
|
1516
|
+
clampIndex(idx) {
|
|
1517
|
+
const len = this.steps().length;
|
|
1518
|
+
const max = Math.max(0, len - 1);
|
|
1519
|
+
if (Number.isNaN(idx))
|
|
1520
|
+
return 0;
|
|
1521
|
+
return Math.min(Math.max(0, idx), max);
|
|
1522
|
+
}
|
|
1523
|
+
selectedIndexComputed = computed(() => this.clampIndex(this._selectedIndex()), ...(ngDevMode ? [{ debugName: "selectedIndexComputed" }] : []));
|
|
1524
|
+
densityClass = () => {
|
|
1525
|
+
const d = this._config()?.density;
|
|
1526
|
+
if (d === 'compact')
|
|
1527
|
+
return 'density-compact';
|
|
1528
|
+
if (d === 'comfortable')
|
|
1529
|
+
return 'density-comfortable';
|
|
1530
|
+
return '';
|
|
1531
|
+
};
|
|
1532
|
+
// Handlers
|
|
1533
|
+
selectionChange = new EventEmitter();
|
|
1534
|
+
trackStep = (_, s) => s.id || _;
|
|
1535
|
+
onSelectionChange(ev) {
|
|
1536
|
+
const idx = this.clampIndex(ev?.selectedIndex ?? 0);
|
|
1537
|
+
this._selectedIndex.set(idx);
|
|
1538
|
+
this.selectedIndexChange.emit(idx);
|
|
1539
|
+
this.selectionChange.emit(ev);
|
|
1540
|
+
}
|
|
1541
|
+
// API programática básica
|
|
1542
|
+
goTo(index) {
|
|
1543
|
+
const clamped = this.clampIndex(index);
|
|
1544
|
+
this._selectedIndex.set(clamped);
|
|
1545
|
+
this.selectedIndexChange.emit(clamped);
|
|
1546
|
+
}
|
|
1547
|
+
next() {
|
|
1548
|
+
const curr = this._selectedIndex();
|
|
1549
|
+
const len = this.steps().length;
|
|
1550
|
+
if (curr < len - 1)
|
|
1551
|
+
this.goTo(curr + 1);
|
|
1552
|
+
}
|
|
1553
|
+
prev() {
|
|
1554
|
+
const curr = this._selectedIndex();
|
|
1555
|
+
if (curr > 0)
|
|
1556
|
+
this.goTo(curr - 1);
|
|
1557
|
+
}
|
|
1558
|
+
reset() { this.goTo(0); }
|
|
1559
|
+
// Alias para compatibilidade nominal com MatStepper
|
|
1560
|
+
previous() { this.prev(); }
|
|
1561
|
+
// Dynamic Form integration
|
|
1562
|
+
onFormReady(stepIndex, ev) {
|
|
1563
|
+
this._formGroups.set(stepIndex, ev.formGroup);
|
|
1564
|
+
this._formValidity.set(stepIndex, ev.formGroup.valid);
|
|
1565
|
+
}
|
|
1566
|
+
onFormValueChange(stepIndex, ev) {
|
|
1567
|
+
this._formValidity.set(stepIndex, ev.isValid);
|
|
1568
|
+
}
|
|
1569
|
+
formGroupFor(i) {
|
|
1570
|
+
return this._formGroups.get(i);
|
|
1571
|
+
}
|
|
1572
|
+
computedCompleted(i, step) {
|
|
1573
|
+
if (typeof step.completed === 'boolean')
|
|
1574
|
+
return step.completed;
|
|
1575
|
+
const v = this._formValidity.get(i);
|
|
1576
|
+
return v === true;
|
|
1577
|
+
}
|
|
1578
|
+
computedHasError(i, step) {
|
|
1579
|
+
if (typeof step.hasError === 'boolean')
|
|
1580
|
+
return step.hasError;
|
|
1581
|
+
const v = this._formValidity.get(i);
|
|
1582
|
+
return v === false;
|
|
1583
|
+
}
|
|
1584
|
+
onChildWidgetEvent(stepIndex, wd, e) {
|
|
1585
|
+
this.widgetEvent.emit({ stepIndex, stepId: this.steps()[stepIndex]?.id, sourceId: e.sourceId, output: e.output, payload: e.payload });
|
|
1586
|
+
}
|
|
1587
|
+
isStepEmpty(step) {
|
|
1588
|
+
const hasForm = !!step.form;
|
|
1589
|
+
const hasWidgets = Array.isArray(step.widgets) && step.widgets.length > 0;
|
|
1590
|
+
return !hasForm && !hasWidgets;
|
|
1591
|
+
}
|
|
1592
|
+
openEditor() {
|
|
1593
|
+
const cfg = this._config() || { steps: [], orientation: 'horizontal', headerPosition: 'top', linear: false };
|
|
1594
|
+
const ref = this.settings.open({
|
|
1595
|
+
id: 'praxis-stepper-editor',
|
|
1596
|
+
title: 'Configurar Stepper',
|
|
1597
|
+
content: { component: PraxisStepperConfigEditor, inputs: { config: cfg } },
|
|
1598
|
+
});
|
|
1599
|
+
const apply = (value) => {
|
|
1600
|
+
const nextCfg = value?.config || value;
|
|
1601
|
+
if (nextCfg) {
|
|
1602
|
+
// Deep clone to ensure change detection and avoid mutating editor instance
|
|
1603
|
+
const cloned = JSON.parse(JSON.stringify(nextCfg));
|
|
1604
|
+
this._config.set(cloned);
|
|
1605
|
+
// Clamp selection after changes
|
|
1606
|
+
const sel = typeof cloned.selectedIndex === 'number' ? cloned.selectedIndex : this._selectedIndex();
|
|
1607
|
+
this._selectedIndex.set(this.clampIndex(sel));
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
ref.applied$.subscribe(apply);
|
|
1611
|
+
ref.saved$.subscribe(apply);
|
|
1612
|
+
}
|
|
1613
|
+
// CTA helpers
|
|
1614
|
+
addFirstStep() {
|
|
1615
|
+
const current = this._config() || { steps: [], orientation: 'horizontal', headerPosition: 'top', linear: false };
|
|
1616
|
+
if ((current.steps || []).length > 0)
|
|
1617
|
+
return;
|
|
1618
|
+
const next = {
|
|
1619
|
+
...current,
|
|
1620
|
+
steps: [
|
|
1621
|
+
{ id: 'step1', label: 'Etapa 1', description: 'Clique em Configurar para editar esta etapa.' },
|
|
1622
|
+
],
|
|
1623
|
+
};
|
|
1624
|
+
this._config.set(next);
|
|
1625
|
+
// Seleciona a primeira etapa
|
|
1626
|
+
this._selectedIndex.set(0);
|
|
1627
|
+
}
|
|
1628
|
+
// Navegação com validação remota opcional
|
|
1629
|
+
async onNext(i) {
|
|
1630
|
+
// Em modo linear, bloquear quando inválido
|
|
1631
|
+
if (this.linear() && this._formValidity.get(i) === false) {
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
const step = this.steps()[i];
|
|
1635
|
+
const fg = this._formGroups.get(i);
|
|
1636
|
+
const data = fg?.getRawValue();
|
|
1637
|
+
if (this.serverValidate) {
|
|
1638
|
+
const res = await this.serverValidate({ step, stepIndex: i, formGroup: fg, formData: data });
|
|
1639
|
+
if (!res.ok) {
|
|
1640
|
+
this.applyServerErrors(fg, res.fieldErrors, res.formErrors);
|
|
1641
|
+
// Atualizar erro visual
|
|
1642
|
+
step.errorMessage = res.errorMessage || (res.formErrors && res.formErrors[0]) || 'Erro de validação';
|
|
1643
|
+
this._formValidity.set(i, false);
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
this.next();
|
|
1648
|
+
}
|
|
1649
|
+
onPrev() { this.prev(); }
|
|
1650
|
+
onAnimationDone() { this.animationDone.emit(); }
|
|
1651
|
+
applyServerErrors(fg, fieldErrors, formErrors) {
|
|
1652
|
+
if (!fg)
|
|
1653
|
+
return;
|
|
1654
|
+
if (fieldErrors) {
|
|
1655
|
+
for (const [key, messages] of Object.entries(fieldErrors)) {
|
|
1656
|
+
const ctrl = fg.get(key);
|
|
1657
|
+
if (ctrl) {
|
|
1658
|
+
const prev = ctrl.errors || {};
|
|
1659
|
+
ctrl.setErrors({ ...prev, server: messages });
|
|
1660
|
+
ctrl.markAsTouched();
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
if (formErrors && formErrors.length) {
|
|
1665
|
+
const prev = fg.errors || {};
|
|
1666
|
+
fg.setErrors({ ...prev, server: formErrors });
|
|
1667
|
+
}
|
|
1668
|
+
fg.updateValueAndValidity({ emitEvent: true });
|
|
1669
|
+
}
|
|
1670
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisStepper, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1671
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.4", type: PraxisStepper, isStandalone: true, selector: "praxis-stepper", inputs: { config: "config", selectedIndexInput: "selectedIndexInput", selectedIndex: "selectedIndex", disableRippleInput: "disableRippleInput", editModeEnabled: "editModeEnabled", labelPosition: "labelPosition", color: "color", serverValidate: "serverValidate", stepperContext: "stepperContext" }, outputs: { selectedIndexChange: "selectedIndexChange", widgetEvent: "widgetEvent", animationDone: "animationDone", selectionChange: "selectionChange" }, queries: [{ propertyName: "stepLabelTpl", first: true, predicate: ["stepLabelTpl"], descendants: true, read: TemplateRef }], ngImport: i0, template: `
|
|
1672
|
+
<ng-container *ngIf="steps().length > 0; else emptyState">
|
|
1673
|
+
<mat-stepper
|
|
1674
|
+
[linear]="linear()"
|
|
1675
|
+
[orientation]="orientation()"
|
|
1676
|
+
[headerPosition]="headerPosition()"
|
|
1677
|
+
[labelPosition]="labelPos()"
|
|
1678
|
+
[disableRipple]="disableRipple()"
|
|
1679
|
+
[color]="stepperColor()"
|
|
1680
|
+
[animationDuration]="animationDuration()"
|
|
1681
|
+
[selectedIndex]="selectedIndexComputed()"
|
|
1682
|
+
(selectionChange)="onSelectionChange($event)"
|
|
1683
|
+
(animationDone)="onAnimationDone()"
|
|
1684
|
+
class="praxis-stepper"
|
|
1685
|
+
[ngClass]="[densityClass(), _config()?.stepperClass || '']"
|
|
1686
|
+
>
|
|
1687
|
+
<!-- Projeção de ícones customizados do header: <ng-template matStepperIcon="done"> ... </ng-template> -->
|
|
1688
|
+
<ng-content></ng-content>
|
|
1689
|
+
<!-- Overrides de ícones via configuração (appearance.icons) -->
|
|
1690
|
+
<ng-template *ngIf="icons()?.number as icn" matStepperIcon="number">
|
|
1691
|
+
<mat-icon [praxisIcon]="icn"></mat-icon>
|
|
1692
|
+
</ng-template>
|
|
1693
|
+
<ng-template *ngIf="icons()?.done as icn" matStepperIcon="done">
|
|
1694
|
+
<mat-icon [praxisIcon]="icn"></mat-icon>
|
|
1695
|
+
</ng-template>
|
|
1696
|
+
<ng-template *ngIf="icons()?.edit as icn" matStepperIcon="edit">
|
|
1697
|
+
<mat-icon [praxisIcon]="icn"></mat-icon>
|
|
1698
|
+
</ng-template>
|
|
1699
|
+
<ng-template *ngIf="icons()?.error as icn" matStepperIcon="error">
|
|
1700
|
+
<mat-icon [praxisIcon]="icn"></mat-icon>
|
|
1701
|
+
</ng-template>
|
|
1702
|
+
<mat-step *ngFor="let step of steps(); let i = index; trackBy: trackStep"
|
|
1703
|
+
[stepControl]="$any(formGroupFor(i))"
|
|
1704
|
+
[id]="step.id || ''"
|
|
1705
|
+
[aria-label]="step.ariaLabel || ''"
|
|
1706
|
+
[aria-labelledby]="step.ariaLabelledby || ''"
|
|
1707
|
+
[label]="step.label"
|
|
1708
|
+
[optional]="step.optional || false"
|
|
1709
|
+
[editable]="step.editable ?? true"
|
|
1710
|
+
[completed]="computedCompleted(i, step)"
|
|
1711
|
+
[hasError]="computedHasError(i, step)"
|
|
1712
|
+
[errorMessage]="step.errorMessage || ''"
|
|
1713
|
+
[state]="step.state || step.stateIcon || ''"
|
|
1714
|
+
>
|
|
1715
|
+
<ng-template matStepLabel>
|
|
1716
|
+
<ng-container *ngIf="stepLabelTpl; else plainLabel"
|
|
1717
|
+
[ngTemplateOutlet]="stepLabelTpl"
|
|
1718
|
+
[ngTemplateOutletContext]="{ $implicit: step, index: i }"
|
|
1719
|
+
></ng-container>
|
|
1720
|
+
<ng-template #plainLabel>{{ step.label }}</ng-template>
|
|
1721
|
+
</ng-template>
|
|
1722
|
+
<div class="pdx-step-content" [ngClass]="_config()?.contentClass || ''">
|
|
1723
|
+
<praxis-dynamic-form *ngIf="step.form as f"
|
|
1724
|
+
[resourcePath]="f.resourcePath"
|
|
1725
|
+
[resourceId]="$any(f.resourceId)"
|
|
1726
|
+
[mode]="f.mode || 'create'"
|
|
1727
|
+
[config]="f.config || { sections: [] }"
|
|
1728
|
+
[schemaSource]="f.schemaSource || 'resource'"
|
|
1729
|
+
[formId]="f.formId"
|
|
1730
|
+
(formReady)="onFormReady(i, $event)"
|
|
1731
|
+
(valueChange)="onFormValueChange(i, $event)"
|
|
1732
|
+
></praxis-dynamic-form>
|
|
1733
|
+
<ng-container *ngFor="let wd of step.widgets || []"
|
|
1734
|
+
[dynamicWidgetLoader]="wd"
|
|
1735
|
+
[context]="stepperContext"
|
|
1736
|
+
[strictValidation]="true"
|
|
1737
|
+
[autoWireOutputs]="true"
|
|
1738
|
+
(widgetEvent)="onChildWidgetEvent(i, wd, $event)"></ng-container>
|
|
1739
|
+
<ng-container *ngIf="isStepEmpty(step)">
|
|
1740
|
+
<praxis-empty-state-card
|
|
1741
|
+
[inline]="true"
|
|
1742
|
+
icon="dashboard_customize"
|
|
1743
|
+
title="Sem conteúdo nesta etapa"
|
|
1744
|
+
description="Adicione um formulário ou widgets a esta etapa, ou abra o editor para configurar."
|
|
1745
|
+
[primaryAction]="{ label: 'Abrir editor', icon: 'tune', action: openEditor.bind(this) }"
|
|
1746
|
+
></praxis-empty-state-card>
|
|
1747
|
+
</ng-container>
|
|
1748
|
+
</div>
|
|
1749
|
+
<div class="pdx-step-actions" *ngIf="navVisible()" [ngClass]="navAlignClass()">
|
|
1750
|
+
<!-- PREV button (hidden on first step) -->
|
|
1751
|
+
<ng-container *ngIf="selectedIndexComputed() > 0">
|
|
1752
|
+
<ng-container [ngSwitch]="navVariant()">
|
|
1753
|
+
<button *ngSwitchCase="'flat'" mat-flat-button [color]="navColor()" type="button" (click)="onPrev()">
|
|
1754
|
+
<mat-icon *ngIf="navPrevIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1755
|
+
{{ navPrevLabel() }}
|
|
1756
|
+
</button>
|
|
1757
|
+
<button *ngSwitchCase="'stroked'" mat-stroked-button [color]="navColor()" type="button" (click)="onPrev()">
|
|
1758
|
+
<mat-icon *ngIf="navPrevIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1759
|
+
{{ navPrevLabel() }}
|
|
1760
|
+
</button>
|
|
1761
|
+
<button *ngSwitchCase="'raised'" mat-raised-button [color]="navColor()" type="button" (click)="onPrev()">
|
|
1762
|
+
<mat-icon *ngIf="navPrevIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1763
|
+
{{ navPrevLabel() }}
|
|
1764
|
+
</button>
|
|
1765
|
+
<button *ngSwitchDefault mat-button [color]="navColor()" type="button" (click)="onPrev()">
|
|
1766
|
+
<mat-icon *ngIf="navPrevIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1767
|
+
{{ navPrevLabel() }}
|
|
1768
|
+
</button>
|
|
1769
|
+
</ng-container>
|
|
1770
|
+
</ng-container>
|
|
1771
|
+
|
|
1772
|
+
<!-- NEXT button -->
|
|
1773
|
+
<ng-container [ngSwitch]="navVariant()">
|
|
1774
|
+
<button *ngSwitchCase="'flat'" mat-flat-button [color]="navColor()" type="button" (click)="onNext(i)" [disabled]="isNextDisabled(i)">
|
|
1775
|
+
{{ navNextLabel() }}
|
|
1776
|
+
<mat-icon *ngIf="navNextIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1777
|
+
</button>
|
|
1778
|
+
<button *ngSwitchCase="'stroked'" mat-stroked-button [color]="navColor()" type="button" (click)="onNext(i)" [disabled]="isNextDisabled(i)">
|
|
1779
|
+
{{ navNextLabel() }}
|
|
1780
|
+
<mat-icon *ngIf="navNextIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1781
|
+
</button>
|
|
1782
|
+
<button *ngSwitchCase="'raised'" mat-raised-button [color]="navColor()" type="button" (click)="onNext(i)" [disabled]="isNextDisabled(i)">
|
|
1783
|
+
{{ navNextLabel() }}
|
|
1784
|
+
<mat-icon *ngIf="navNextIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1785
|
+
</button>
|
|
1786
|
+
<button *ngSwitchDefault mat-button [color]="navColor()" type="button" (click)="onNext(i)" [disabled]="isNextDisabled(i)">
|
|
1787
|
+
{{ navNextLabel() }}
|
|
1788
|
+
<mat-icon *ngIf="navNextIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1789
|
+
</button>
|
|
1790
|
+
</ng-container>
|
|
1791
|
+
</div>
|
|
1792
|
+
</mat-step>
|
|
1793
|
+
</mat-stepper>
|
|
1794
|
+
</ng-container>
|
|
1795
|
+
<ng-template #emptyState>
|
|
1796
|
+
<praxis-empty-state-card
|
|
1797
|
+
icon="view_timeline"
|
|
1798
|
+
[inline]="true"
|
|
1799
|
+
title="Stepper sem etapas"
|
|
1800
|
+
description="Adicione etapas para começar. Você pode configurar formulários ou widgets em cada etapa."
|
|
1801
|
+
[primaryAction]="primaryCta"
|
|
1802
|
+
[secondaryActions]="secondaryCtas"
|
|
1803
|
+
></praxis-empty-state-card>
|
|
1804
|
+
</ng-template>
|
|
1805
|
+
<button
|
|
1806
|
+
*ngIf="editModeEnabled"
|
|
1807
|
+
mat-fab
|
|
1808
|
+
class="edit-fab"
|
|
1809
|
+
aria-label="Editar stepper"
|
|
1810
|
+
(click)="openEditor()"
|
|
1811
|
+
>
|
|
1812
|
+
<mat-icon fontIcon="edit"></mat-icon>
|
|
1813
|
+
</button>
|
|
1814
|
+
`, isInline: true, styles: [":host{display:block;position:relative}.praxis-stepper{width:100%}:host(.density-compact) .mat-step-header{min-height:36px}:host(.density-compact) .pdx-step-actions{padding-top:4px;gap:6px}:host(.density-comfortable) .mat-step-header{min-height:44px}.pdx-step-content{padding:8px 0}.pdx-step-actions{display:flex;gap:8px;padding-top:8px}.nav-align-start{justify-content:flex-start}.nav-align-center{justify-content:center}.nav-align-end{justify-content:flex-end}.nav-align-space-between{justify-content:space-between}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}\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: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1$1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1$1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: MatStepperModule }, { kind: "component", type: i2$1.MatStep, selector: "mat-step", inputs: ["color"], exportAs: ["matStep"] }, { kind: "directive", type: i2$1.MatStepLabel, selector: "[matStepLabel]" }, { kind: "component", type: i2$1.MatStepper, selector: "mat-stepper, mat-vertical-stepper, mat-horizontal-stepper, [matStepper]", inputs: ["disableRipple", "color", "labelPosition", "headerPosition", "animationDuration"], outputs: ["animationDone"], exportAs: ["matStepper", "matVerticalStepper", "matHorizontalStepper"] }, { kind: "directive", type: i2$1.MatStepperIcon, selector: "ng-template[matStepperIcon]", inputs: ["matStepperIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6.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.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "component", type: PraxisDynamicForm, selector: "praxis-dynamic-form", inputs: ["resourcePath", "resourceId", "mode", "config", "schemaSource", "editModeEnabled", "formId", "layout", "backConfig", "hooks", "removeEmptyContainersOnSave", "reactiveValidation", "reactiveValidationDebounceMs", "notifyIfOutdated", "snoozeMs", "autoOpenSettingsOnOutdated", "readonlyModeGlobal", "disabledModeGlobal", "presentationModeGlobal", "visibleGlobal", "customEndpoints"], outputs: ["formSubmit", "formCancel", "formReset", "configChange", "formReady", "valueChange", "syncCompleted", "initializationError", "editModeEnabledChange", "customAction", "actionConfirmation", "schemaStatusChange"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1815
|
+
}
|
|
1816
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: PraxisStepper, decorators: [{
|
|
1817
|
+
type: Component,
|
|
1818
|
+
args: [{ selector: 'praxis-stepper', standalone: true, imports: [CommonModule, ReactiveFormsModule, MatStepperModule, MatButtonModule, MatIconModule, PraxisIconDirective, PraxisDynamicForm, DynamicWidgetLoaderDirective, EmptyStateCardComponent], template: `
|
|
1819
|
+
<ng-container *ngIf="steps().length > 0; else emptyState">
|
|
1820
|
+
<mat-stepper
|
|
1821
|
+
[linear]="linear()"
|
|
1822
|
+
[orientation]="orientation()"
|
|
1823
|
+
[headerPosition]="headerPosition()"
|
|
1824
|
+
[labelPosition]="labelPos()"
|
|
1825
|
+
[disableRipple]="disableRipple()"
|
|
1826
|
+
[color]="stepperColor()"
|
|
1827
|
+
[animationDuration]="animationDuration()"
|
|
1828
|
+
[selectedIndex]="selectedIndexComputed()"
|
|
1829
|
+
(selectionChange)="onSelectionChange($event)"
|
|
1830
|
+
(animationDone)="onAnimationDone()"
|
|
1831
|
+
class="praxis-stepper"
|
|
1832
|
+
[ngClass]="[densityClass(), _config()?.stepperClass || '']"
|
|
1833
|
+
>
|
|
1834
|
+
<!-- Projeção de ícones customizados do header: <ng-template matStepperIcon="done"> ... </ng-template> -->
|
|
1835
|
+
<ng-content></ng-content>
|
|
1836
|
+
<!-- Overrides de ícones via configuração (appearance.icons) -->
|
|
1837
|
+
<ng-template *ngIf="icons()?.number as icn" matStepperIcon="number">
|
|
1838
|
+
<mat-icon [praxisIcon]="icn"></mat-icon>
|
|
1839
|
+
</ng-template>
|
|
1840
|
+
<ng-template *ngIf="icons()?.done as icn" matStepperIcon="done">
|
|
1841
|
+
<mat-icon [praxisIcon]="icn"></mat-icon>
|
|
1842
|
+
</ng-template>
|
|
1843
|
+
<ng-template *ngIf="icons()?.edit as icn" matStepperIcon="edit">
|
|
1844
|
+
<mat-icon [praxisIcon]="icn"></mat-icon>
|
|
1845
|
+
</ng-template>
|
|
1846
|
+
<ng-template *ngIf="icons()?.error as icn" matStepperIcon="error">
|
|
1847
|
+
<mat-icon [praxisIcon]="icn"></mat-icon>
|
|
1848
|
+
</ng-template>
|
|
1849
|
+
<mat-step *ngFor="let step of steps(); let i = index; trackBy: trackStep"
|
|
1850
|
+
[stepControl]="$any(formGroupFor(i))"
|
|
1851
|
+
[id]="step.id || ''"
|
|
1852
|
+
[aria-label]="step.ariaLabel || ''"
|
|
1853
|
+
[aria-labelledby]="step.ariaLabelledby || ''"
|
|
1854
|
+
[label]="step.label"
|
|
1855
|
+
[optional]="step.optional || false"
|
|
1856
|
+
[editable]="step.editable ?? true"
|
|
1857
|
+
[completed]="computedCompleted(i, step)"
|
|
1858
|
+
[hasError]="computedHasError(i, step)"
|
|
1859
|
+
[errorMessage]="step.errorMessage || ''"
|
|
1860
|
+
[state]="step.state || step.stateIcon || ''"
|
|
1861
|
+
>
|
|
1862
|
+
<ng-template matStepLabel>
|
|
1863
|
+
<ng-container *ngIf="stepLabelTpl; else plainLabel"
|
|
1864
|
+
[ngTemplateOutlet]="stepLabelTpl"
|
|
1865
|
+
[ngTemplateOutletContext]="{ $implicit: step, index: i }"
|
|
1866
|
+
></ng-container>
|
|
1867
|
+
<ng-template #plainLabel>{{ step.label }}</ng-template>
|
|
1868
|
+
</ng-template>
|
|
1869
|
+
<div class="pdx-step-content" [ngClass]="_config()?.contentClass || ''">
|
|
1870
|
+
<praxis-dynamic-form *ngIf="step.form as f"
|
|
1871
|
+
[resourcePath]="f.resourcePath"
|
|
1872
|
+
[resourceId]="$any(f.resourceId)"
|
|
1873
|
+
[mode]="f.mode || 'create'"
|
|
1874
|
+
[config]="f.config || { sections: [] }"
|
|
1875
|
+
[schemaSource]="f.schemaSource || 'resource'"
|
|
1876
|
+
[formId]="f.formId"
|
|
1877
|
+
(formReady)="onFormReady(i, $event)"
|
|
1878
|
+
(valueChange)="onFormValueChange(i, $event)"
|
|
1879
|
+
></praxis-dynamic-form>
|
|
1880
|
+
<ng-container *ngFor="let wd of step.widgets || []"
|
|
1881
|
+
[dynamicWidgetLoader]="wd"
|
|
1882
|
+
[context]="stepperContext"
|
|
1883
|
+
[strictValidation]="true"
|
|
1884
|
+
[autoWireOutputs]="true"
|
|
1885
|
+
(widgetEvent)="onChildWidgetEvent(i, wd, $event)"></ng-container>
|
|
1886
|
+
<ng-container *ngIf="isStepEmpty(step)">
|
|
1887
|
+
<praxis-empty-state-card
|
|
1888
|
+
[inline]="true"
|
|
1889
|
+
icon="dashboard_customize"
|
|
1890
|
+
title="Sem conteúdo nesta etapa"
|
|
1891
|
+
description="Adicione um formulário ou widgets a esta etapa, ou abra o editor para configurar."
|
|
1892
|
+
[primaryAction]="{ label: 'Abrir editor', icon: 'tune', action: openEditor.bind(this) }"
|
|
1893
|
+
></praxis-empty-state-card>
|
|
1894
|
+
</ng-container>
|
|
1895
|
+
</div>
|
|
1896
|
+
<div class="pdx-step-actions" *ngIf="navVisible()" [ngClass]="navAlignClass()">
|
|
1897
|
+
<!-- PREV button (hidden on first step) -->
|
|
1898
|
+
<ng-container *ngIf="selectedIndexComputed() > 0">
|
|
1899
|
+
<ng-container [ngSwitch]="navVariant()">
|
|
1900
|
+
<button *ngSwitchCase="'flat'" mat-flat-button [color]="navColor()" type="button" (click)="onPrev()">
|
|
1901
|
+
<mat-icon *ngIf="navPrevIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1902
|
+
{{ navPrevLabel() }}
|
|
1903
|
+
</button>
|
|
1904
|
+
<button *ngSwitchCase="'stroked'" mat-stroked-button [color]="navColor()" type="button" (click)="onPrev()">
|
|
1905
|
+
<mat-icon *ngIf="navPrevIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1906
|
+
{{ navPrevLabel() }}
|
|
1907
|
+
</button>
|
|
1908
|
+
<button *ngSwitchCase="'raised'" mat-raised-button [color]="navColor()" type="button" (click)="onPrev()">
|
|
1909
|
+
<mat-icon *ngIf="navPrevIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1910
|
+
{{ navPrevLabel() }}
|
|
1911
|
+
</button>
|
|
1912
|
+
<button *ngSwitchDefault mat-button [color]="navColor()" type="button" (click)="onPrev()">
|
|
1913
|
+
<mat-icon *ngIf="navPrevIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1914
|
+
{{ navPrevLabel() }}
|
|
1915
|
+
</button>
|
|
1916
|
+
</ng-container>
|
|
1917
|
+
</ng-container>
|
|
1918
|
+
|
|
1919
|
+
<!-- NEXT button -->
|
|
1920
|
+
<ng-container [ngSwitch]="navVariant()">
|
|
1921
|
+
<button *ngSwitchCase="'flat'" mat-flat-button [color]="navColor()" type="button" (click)="onNext(i)" [disabled]="isNextDisabled(i)">
|
|
1922
|
+
{{ navNextLabel() }}
|
|
1923
|
+
<mat-icon *ngIf="navNextIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1924
|
+
</button>
|
|
1925
|
+
<button *ngSwitchCase="'stroked'" mat-stroked-button [color]="navColor()" type="button" (click)="onNext(i)" [disabled]="isNextDisabled(i)">
|
|
1926
|
+
{{ navNextLabel() }}
|
|
1927
|
+
<mat-icon *ngIf="navNextIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1928
|
+
</button>
|
|
1929
|
+
<button *ngSwitchCase="'raised'" mat-raised-button [color]="navColor()" type="button" (click)="onNext(i)" [disabled]="isNextDisabled(i)">
|
|
1930
|
+
{{ navNextLabel() }}
|
|
1931
|
+
<mat-icon *ngIf="navNextIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1932
|
+
</button>
|
|
1933
|
+
<button *ngSwitchDefault mat-button [color]="navColor()" type="button" (click)="onNext(i)" [disabled]="isNextDisabled(i)">
|
|
1934
|
+
{{ navNextLabel() }}
|
|
1935
|
+
<mat-icon *ngIf="navNextIcon() as ic" [praxisIcon]="ic"></mat-icon>
|
|
1936
|
+
</button>
|
|
1937
|
+
</ng-container>
|
|
1938
|
+
</div>
|
|
1939
|
+
</mat-step>
|
|
1940
|
+
</mat-stepper>
|
|
1941
|
+
</ng-container>
|
|
1942
|
+
<ng-template #emptyState>
|
|
1943
|
+
<praxis-empty-state-card
|
|
1944
|
+
icon="view_timeline"
|
|
1945
|
+
[inline]="true"
|
|
1946
|
+
title="Stepper sem etapas"
|
|
1947
|
+
description="Adicione etapas para começar. Você pode configurar formulários ou widgets em cada etapa."
|
|
1948
|
+
[primaryAction]="primaryCta"
|
|
1949
|
+
[secondaryActions]="secondaryCtas"
|
|
1950
|
+
></praxis-empty-state-card>
|
|
1951
|
+
</ng-template>
|
|
1952
|
+
<button
|
|
1953
|
+
*ngIf="editModeEnabled"
|
|
1954
|
+
mat-fab
|
|
1955
|
+
class="edit-fab"
|
|
1956
|
+
aria-label="Editar stepper"
|
|
1957
|
+
(click)="openEditor()"
|
|
1958
|
+
>
|
|
1959
|
+
<mat-icon fontIcon="edit"></mat-icon>
|
|
1960
|
+
</button>
|
|
1961
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;position:relative}.praxis-stepper{width:100%}:host(.density-compact) .mat-step-header{min-height:36px}:host(.density-compact) .pdx-step-actions{padding-top:4px;gap:6px}:host(.density-comfortable) .mat-step-header{min-height:44px}.pdx-step-content{padding:8px 0}.pdx-step-actions{display:flex;gap:8px;padding-top:8px}.nav-align-start{justify-content:flex-start}.nav-align-center{justify-content:center}.nav-align-end{justify-content:flex-end}.nav-align-space-between{justify-content:space-between}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}\n"] }]
|
|
1962
|
+
}], propDecorators: { stepLabelTpl: [{
|
|
1963
|
+
type: ContentChild,
|
|
1964
|
+
args: ['stepLabelTpl', { read: TemplateRef }]
|
|
1965
|
+
}], config: [{
|
|
1966
|
+
type: Input
|
|
1967
|
+
}], selectedIndexInput: [{
|
|
1968
|
+
type: Input
|
|
1969
|
+
}], selectedIndex: [{
|
|
1970
|
+
type: Input
|
|
1971
|
+
}], disableRippleInput: [{
|
|
1972
|
+
type: Input
|
|
1973
|
+
}], editModeEnabled: [{
|
|
1974
|
+
type: Input
|
|
1975
|
+
}], labelPosition: [{
|
|
1976
|
+
type: Input
|
|
1977
|
+
}], color: [{
|
|
1978
|
+
type: Input
|
|
1979
|
+
}], serverValidate: [{
|
|
1980
|
+
type: Input
|
|
1981
|
+
}], selectedIndexChange: [{
|
|
1982
|
+
type: Output
|
|
1983
|
+
}], widgetEvent: [{
|
|
1984
|
+
type: Output
|
|
1985
|
+
}], stepperContext: [{
|
|
1986
|
+
type: Input
|
|
1987
|
+
}], animationDone: [{
|
|
1988
|
+
type: Output
|
|
1989
|
+
}], selectionChange: [{
|
|
1990
|
+
type: Output
|
|
1991
|
+
}] } });
|
|
1992
|
+
|
|
1993
|
+
const PRAXIS_STEPPER_COMPONENT_METADATA = {
|
|
1994
|
+
id: 'praxis-stepper',
|
|
1995
|
+
selector: 'praxis-stepper',
|
|
1996
|
+
component: PraxisStepper,
|
|
1997
|
+
friendlyName: 'Praxis Stepper',
|
|
1998
|
+
description: 'Stepper configurável (horizontal/vertical/linear) com passos e conteúdo dinâmico.',
|
|
1999
|
+
icon: 'format_list_numbered',
|
|
2000
|
+
inputs: [
|
|
2001
|
+
{ name: 'config', type: 'StepperMetadata', label: 'Configuração', description: 'Configuração JSON com passos e opções' },
|
|
2002
|
+
{ name: 'selectedIndex', type: 'number', label: '[(selectedIndex)] Índice selecionado' },
|
|
2003
|
+
{ name: 'selectedIndexInput', type: 'number', label: 'Índice selecionado (legacy)' },
|
|
2004
|
+
{ name: 'disableRippleInput', type: 'boolean', default: false, label: 'Desabilitar ripple' },
|
|
2005
|
+
{ name: 'labelPosition', type: "'bottom' | 'end'", label: 'Posição do rótulo (horizontal)' },
|
|
2006
|
+
{ name: 'color', type: 'ThemePalette', label: 'Cor (M2)' },
|
|
2007
|
+
{ name: 'appearance', type: '{ themeClass?: string; tokens?: Record<string,string>; icons?: { number?: string; done?: string; edit?: string; error?: string }; iconsSet?: string }', label: 'Aparência (tokens, themeClass, ícones, conjunto)' },
|
|
2008
|
+
{ name: 'serverValidate', type: '(args) => Promise<{ ok: boolean; fieldErrors?: Record<string,string[]>; formErrors?: string[] }>', label: 'Validação remota (opt-in)' },
|
|
2009
|
+
{ name: 'density', type: "'default' | 'comfortable' | 'compact'", label: 'Densidade' },
|
|
2010
|
+
{ name: 'stepperClass', type: 'string', label: 'Classe CSS (stepper)' },
|
|
2011
|
+
{ name: 'headerClass', type: 'string', label: 'Classe CSS (header)' },
|
|
2012
|
+
{ name: 'contentClass', type: 'string', label: 'Classe CSS (conteúdo)' },
|
|
2013
|
+
{ name: 'navigation', type: "{ visible?: boolean; prevLabel?: string; nextLabel?: string; prevIcon?: string; nextIcon?: string; variant?: 'basic'|'flat'|'stroked'|'raised'; color?: ThemePalette; align?: 'start'|'center'|'end'|'space-between' }", label: 'Navegação (botões)'
|
|
2014
|
+
},
|
|
2015
|
+
],
|
|
2016
|
+
outputs: [
|
|
2017
|
+
{ name: 'selectionChange', type: 'any', label: 'Troca de seleção' },
|
|
2018
|
+
{ name: 'selectedIndexChange', type: 'number', label: 'Índice selecionado (change)' },
|
|
2019
|
+
{ name: 'animationDone', type: 'void', label: 'Animação concluída' },
|
|
2020
|
+
{ name: 'widgetEvent', type: '{ stepId?: string; stepIndex?: number; sourceId: string; output?: string; payload?: any }', label: 'Evento interno' },
|
|
2021
|
+
],
|
|
2022
|
+
tags: ['widget', 'container', 'stepper', 'configurable'],
|
|
2023
|
+
lib: '@praxisui/stepper',
|
|
2024
|
+
layoutHints: { recommendedCols: 8, recommendedRows: 6, minCols: 4, minRows: 4 },
|
|
2025
|
+
};
|
|
2026
|
+
function providePraxisStepperMetadata() {
|
|
2027
|
+
return {
|
|
2028
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
2029
|
+
multi: true,
|
|
2030
|
+
useFactory: (registry) => () => {
|
|
2031
|
+
registry.register(PRAXIS_STEPPER_COMPONENT_METADATA);
|
|
2032
|
+
},
|
|
2033
|
+
deps: [ComponentMetadataRegistry],
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
/*
|
|
2038
|
+
* Public API Surface of praxis-stepper
|
|
2039
|
+
*/
|
|
2040
|
+
|
|
2041
|
+
/**
|
|
2042
|
+
* Generated bundle index. Do not edit.
|
|
2043
|
+
*/
|
|
2044
|
+
|
|
2045
|
+
export { PRAXIS_STEPPER_COMPONENT_METADATA, PraxisStepper, PraxisStepperConfigEditor, providePraxisStepperMetadata };
|
|
2046
|
+
//# sourceMappingURL=praxisui-stepper.mjs.map
|